2019/01/31

[c/c++][CERT]配列のポインタとかポインタの配列とか紛らわしい

C言語再履修シリーズ。

今日は軽めにしておこう。


ARR34-C. 式中の配列の型は適合していることを保証する


タイトルと内容に差違はなく、特にいうことはない。

私は、ポインタの配列、とか、配列のポインタ、とか、そういう紛らわしいものが苦手だから、違反コードと適合コードを真面目に見ておこう(私が書くんだったら、2次元配列ではなく1次元にして、添字で2次元を制御するだろう)。


int a[ROWS][COLS];

メモリとしては、a[row]から始まるアドレスにint型がCOLS個つながっている。
確認しておこう。

01: #include <stdio.h>
02: 
03: enum { ROWS = 10, COLS = 15 };
04: 
05: int main(void)
06: {
07:     int a[ROWS][COLS];
08: 
09:     printf("sizeof(int)=%ld\n", sizeof(int));
10:     for (int lp = 0; lp < COLS; lp++) {
11:         printf("[%d]%p\n", lp, &a[0][lp]);
12:     }
13:     return 0;
14: }

結果

sizeof(int)=4
[0]0x7fffef42ab30
[1]0x7fffef42ab34
[2]0x7fffef42ab38
[3]0x7fffef42ab3c
[4]0x7fffef42ab40
[5]0x7fffef42ab44
[6]0x7fffef42ab48
[7]0x7fffef42ab4c
[8]0x7fffef42ab50
[9]0x7fffef42ab54
[10]0x7fffef42ab58
[11]0x7fffef42ab5c
[12]0x7fffef42ab60
[13]0x7fffef42ab64
[14]0x7fffef42ab68

アドレスが4ずつずれているので、2番目の添字に対してメモリが順番に並んでいることが分かる。
だから見かけ(?)と違い、int[COLS]が[ROWS]ある、ということになる。


では、適合コードを確認しよう。

01: #include <stdio.h>
02: 
03: enum { ROWS = 10, COLS = 15 };
04: 
05: int main(void)
06: {
07:     int a[ROWS][COLS];
08:     int (*b)[COLS] = a;
09: 
10:     for (int lp2 = 0; lp2 < ROWS; lp2++) {
11:         for (int lp1 = 0; lp1 < COLS; lp1++) {
12:             a[lp2][lp1] = lp2 * 100 + lp1;
13:             //printf("[%d][%d] = %d\n", lp2, lp1, a[lp2][lp1]);
14:         }
15:     }
16:     for (int lp = 0; lp < COLS; lp++) {
17:         printf("[%2d]a=%d, b=%d\n", lp, a[5][lp], (*(b + 5))[lp]);
18:     }
19:     return 0;
20: }

結果

[ 0]a=500, b=500
[ 1]a=501, b=501
[ 2]a=502, b=502
[ 3]a=503, b=503
[ 4]a=504, b=504
[ 5]a=505, b=505
[ 6]a=506, b=506
[ 7]a=507, b=507
[ 8]a=508, b=508
[ 9]a=509, b=509
[10]a=510, b=510
[11]a=511, b=511
[12]a=512, b=512
[13]a=513, b=513
[14]a=514, b=514

1000-100の位をrow、10-1の位をcolで表した。
row=5なので、bはポインタに加算することになる。


なお、最初試したときは*bに括弧を付け忘れたので、値があわなかった。

printf("[%2d]a=%d, b=%d\n", lp, a[0][lp], *b[lp]);

とすると、

[ 0]a=0, b=0
[ 1]a=1, b=100
[ 2]a=2, b=200
[ 3]a=3, b=300
[ 4]a=4, b=400
[ 5]a=5, b=500
[ 6]a=6, b=600
[ 7]a=7, b=700
[ 8]a=8, b=800
[ 9]a=9, b=900
[10]a=10, b=-1765474560
[11]a=11, b=0
[12]a=12, b=0
[13]a=13, b=0
[14]a=14, b=-1051150057

みたいに、全然違うところを指している。
Segmentation Faultにならなかっただけ、というところか。

*b[lp]

は、

*(b + lp)

という扱いになるので、0~9までは大丈夫なのだな。


で、私が試したかったのはそういうのではなく、もうちょっと単純なことだ。

unsigned charとuint8_tは同じ型として扱ってくれるのだろうか?という疑問である。
コンパイラの認識がどうなっているのか、だな。


01: #include <stdio.h>
02: #include <stdint.h>
03: 
04: int main(void)
05: {
06:     unsigned char a[10];
07:     uint8_t *b = a;
08: 
09:     for (int lp = 0; lp < 10; lp++) {
10:         a[lp] = lp;
11:     }
12:     printf("b=%d\n", *b);
13: 
14:     return 0;
15: }

うん、gccでもg++でも、-Wや-Wallしても何も出なかった。
unsigned intみたいにすると、gccはwarning、g++はerrorだった。


やっていて気付いたのだが、longって8byte扱いなのね・・・。
てっきり、int=longで、8byteはlong longだけかと思っていた。
64bitのWSLでも、64bitのUbuntu18.04でもそうだった。

strtol()とstrtoll()で悩んだりしたけど、どっちでも同じなのか。
いっそのこと、strtod32()とかstrtod64()とかにしてほしいものだ。

2019/01/30

[c/c++][CERT]配列の初期化子サイズ違いでgcc警告を出したいなら-W

C言語再履修シリーズ。

しばらくサボってしまった。。
後回しにしてしまいがちなのがいかんのだろう。


ARR02-C. 初期化子が暗黙的にサイズを定義する場合であっても、配列のサイズは明示的に指定する


あー、そうよねー。
でもまあ、そんなに「すべし」ってほどのものでもないよね?
ん?


要素指示子を一つだけ使うことで、配列の両端に初期値を与えることができる。

要素指示子?
聞いたことがないし、C言語クイックリファレンスの目次にも出てこなかった。

原文では、

Space can be "allocated" from both ends of an array by using a single designator:

となっているので「using a single designator」が「要素指示子を1つだけ使う」だろう。
designatorは「指名者」とか「指定者」という意味なので、まあ、要素指示子なのだろう。

C言語クイックリファレンスでは「配列指示子(array designator)」となっていた。
が、本文を読んでも見つからない。。。

gccでやってみるべし。


01: #include <stdio.h>
02: 
03: #define MAX (10)
04: 
05: int main(void)
06: {
07:     int a[MAX] = {
08:       1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0
09:     };
10: 
11:     for (size_t lp = 0; lp < sizeof(a) / sizeof(a[0]); lp++) {
12:         printf("[%ld] %d\n", lp, a[lp]);
13:     }
14: 
15:     return 0;
16: }

結果

[0] 1
[1] 3
[2] 5
[3] 7
[4] 9
[5] 8
[6] 6
[7] 4
[8] 2
[9] 0


MAXを10→11に変更


結果

[0] 1
[1] 3
[2] 5
[3] 7
[4] 9
[5] 0
[6] 8
[7] 6
[8] 4
[9] 2
[10] 0


なるほど、配列指示子というのは、要素の初期値を添字のどこに置くかを決めるやり方のようだ。
[MAX-5]だから、最初の例では[5]に8が入ったのだが、次の例では[6]に8が入り、[5]が空きになったので0が代入されたということか。



なお、MAXを10より小さくすると、順番が後のデータによって上書きしていくらしい。

MAX=6

結果

[0] 1
[1] 8
[2] 6
[3] 4
[4] 2
[5] 0

ふーん。


コンパイルを gcc -Wallでやったのだけど、警告も何も出ない。
gcc -Wにすると「initialized field overwritten 」の警告が出た。
"-Wall"って、名前詐欺みたいな気がしてしまうのは私だけか。。。

g++の場合は、オプション無しでコンパイルエラーになった。

array_designator.c: In function ‘int main()’:
array_designator.c:9:5: sorry, unimplemented: non-trivial designated initializers not supported
      };
      ^
array_designator.c:9:5: error: too many initializers for ‘int [6]’

gccでもこれくらいやってくれてよいと思う。

2019/01/20

[c/c++][CERT]ポインタの比較

C言語の再履修シリーズ。。。なのに、間が空きすぎてしまった。
こういうの、気力がいるから、仕事から帰ってからだとなかなか手に付かないのよねぇ。


EXP16-C. 関数ポインタを定数値と比較しない

関数ポインタというと、こういう形の話だろうと思って読んでいた。

typedef void (*func_t)(void);

ここでは、単に関数名だけを比較してるからダメだよ、という話だった。

if (printf != NULL) {
  ...
}

まあ、誤記以外にはやらなさそうな気がする。

ただ、自分で宣言した関数で、別のところで同じ名前のグローバル変数があって、グローバル変数のexternを忘れてて関数の方を・・・
いや、そういうシーンはあり得んな。リンクエラーだ。


ちょっと違うけど、似たような目にあった。
PythonでQRコードを作ろうとして、自分のファイル名をqrcode.pyなんて名前にしたけど、使ったライブラリもqrcodeで、importすると自分をimportしてしまって、qrcodeのメソッドを呼ぼうとしたら「そんなのない!」って言われて悩んだのだった。



EXP34-C. null ポインタを参照しない

もう1つ、似たようなポインタの話を。

まあ、NULLポインタは使ったらいかんだろう、というのはC言語の共通認識としてだいたい広まっているだろう。
ここで困るのは、C言語でスタックを使った変数の場合は値が不定になるということだ。
グローバル変数は、スタンダードなC言語であればmain()前にゼロクリアすることになっているから、まあよかろう(非力なシステムでは、そこも省略するかもしれんが)。


C++では"NULL"というものは定義されていなかったと思う。
やるなら、

const int NULL = 0;

みたいなものを自分で定義するようになっていた気がする。C++第3版くらいの知識なので、古いと思うが。
C++では、0はすべての型の0として使えるようになっているそうだ。
型チェックが厳しいから、変に(void *)などとキャストするとまずいのだ。

まあ、nullptrってのもC++11にはあるそうだし、言語でチェックできるならいいですな。

2019/01/10

[c/c++][CERT]errno_t型がない!

C言語再履修シリーズ。

・・・なのだが、書いた記事の復習もする。


[c/c++][CERT]ernor_tという型があるのか
https://hiro99ma.blogspot.com/2018/12/cccerterrort.html

DCL09-C. errno を返す関数は返り値を errno_t 型として定義する
https://www.jpcert.or.jp/sc-rules/c-dcl09-c.html


記事を書いたときは読んだだけだったのだが、errnoを使う機会が出てきたので使ってみた。
みたら、コンパイルエラー!!
打ち間違えなのかと見直したが、そうではない。
どういうことだ、いったい??



errno_t ‐ 通信用語の基礎知識
https://www.wdic.org/w/TECH/errno_t

errno_tは、Microsoftが提案し、Visual C++に追加された変数型である

えー??
いや、でもUbuntu18.04でtypedefされているのも確認したのだ。

オライリー「Cクイックリファレンス」でも、errno_t型はあるように書かれている。
ただ「セキュア関数の戻り値」について言及している箇所なので、条件があるのか?


しかし、#defineを変えても、/usr/include/errno.hを直接includeしても、全然うまくいかない。
std=c11もstd=gnu11もダメだし、__STDC_LIB_EXT1__や__STDC_WANT_LIB_EXT1__もだめだ。
なぜなんだ・・・。


https://wiki.sei.cmu.edu/confluence/display/c/DCL09-C.+Declare+functions+that+return+errno+with+a+return+type+of+errno_t

原文のCERTは、わかりやすい。
というよりも、2018年に更新されているようだから、その影響か。

#ifndef __STDC_LIB_EXT1__
   typedef int errno_t;
#endif

と書いてあるので、__STDC_LIB_EXT1__が定義されていればerrno_tが使えそうなものなのだが、うまくいかんかった(gcc v7.3.0)。


そこまでやるくらいなら、intでいいんじゃないの、と私は思った。

2019/01/09

[c/c++][CERT]is系の関数だといいと思うんだけどなぁ

C言語再履修シリーズ。


EXP20-C. 成功、真偽、等価を判定するには明示的な検査を行う
https://www.jpcert.or.jp/sc-rules/c-exp20-c.html


レコメンデーションだからスルーすればいいだけなのだが、よく読んでおかないとイメージだけで判断しているかもしれん。


非0の値をif文などで真として判定する場合、値をそのまま書くのではなく、!=0と比較した方がいいよ、ということだ。
まあ、これはわかる。
ライブラリによって、0が真だったり、boolのtrueが真だったりして、訳がわからないからだ。


気にしたのは、最初の違反コードだ。
"is_banned()"という関数は、偽ならば0、真ならば0以外を返すようになっている。
これを!=0でチェックした方がいいよ、としている。

確かにそうなんだけど、標準ライブラリにもisalpha()みたいなのもあるので、is系の名前を付けた関数だったらそのまま評価してやってもいいんじゃないかなぁ、と思ったのだ。


ただ、名前に依存しているだけやん、といわれると返す言葉もないな。

2019/01/07

[c/c++][CERT]パディングの中まで気にするのか

C言語の再履修シリーズ。


DCL39-C. 信頼境界を越えて構造体を渡すとき情報漏えいしない
https://www.jpcert.or.jp/sc-rules/c-dcl39-c.html


何を言っているのかなかなかわからなかったが、たぶんこういうことだろう。

  • 構造体や共用体では、メンバ間にパディングが入ることがある
  • ビットフィールドでも、ビットのパディングが入ることがある
  • パディングの値は不定
  • 不定ゆえに、メンバの書き換えを行う場合にどういう値が入るかも不定
  • そのため、意図していない有用なデータが書き込まれるかもしれない


問題は、最後の部分だ。
いきなり文章が飛躍しているが、タイトルの通り気にするのは「信頼境界」で、例で上がっている「カーネルとユーザスペース間」のような、カーネルからユーザスペースに値を渡すときに、情報を漏らしてはいけないのにカーネル側から漏れてしまうことを懸念している。


memset()の例では、4byteアラインメント(レジスタも4byte)だとして、構造体メンバの真ん中に1byteのメンバがいる。
ここの書き換えを行うときに「どうせ1byteしか見ないからいいや」とコンパイラが判断して、1byteのメンバ分のレジスタしか書き換えず、残りの3byteはそのままで、しかも書込みは4byteで行ったりすると、レジスタの残り3byteはそのときの値を残したままなので、ユーザスペースに渡すデータに書き込まれてしまう、と。


えー、そんなことまで考慮しないといけないのぉ。。。


対策案としてあげてあるコードは、構造体メンバをシリアライズしてユーザスペースに渡して、向こう側で構造体に戻す、というものだ。
つまりまあ、機器間で通信データを作ってやりとりするようなものですな。

ここまでやるんですなぁ。

2019/01/06

[c/c++][gcc]attribute nonnullは静的チェックか

私は、C言語ではあまりNULLチェックをしない。
初期値としてポインタにNULLを代入する、という実装していればやってもよいのだけど、そう作っていないのであれば無意味な値が入っていても非NULLになるので、やらなくてよいかなぁ、と。

ただ、自分で実装しないのでよいならやっても悪くないかな、という気もする。
Javaの@NonNullみたいなイメージだ。


なんとなくgccのオプションを見ていると、attributeにnonnullというものがあることに気付いた。
試しておくか。


01: #include <stdio.h>
02: 
03: static void print_arg(const char *pStr) __attribute__((nonnull (1)));
04: 
05: 
06: static void print_arg(const char *pStr)
07: {
08:     printf("%s\n", pStr);
09: }
10: 
11: int main(void)
12: {
13:     print_arg(NULL);
14:     return 0;
15: }

$ gcc -o tst nonnull.c
nonnull.c: In function ‘main’:
nonnull.c:13:5: warning: null argument where non-null required (argument 1) [-Wnonnull]
      print_arg(NULL);
      ^~~~~~~~~

コンパイルの段階でwarningが出るのか。
ということは、動的なチェックをするのではない・・・?

01: #include <stdio.h>
02: 
03: static void print_arg(const char *pStr) __attribute__((nonnull (1)));
04: 
05: 
06: static void print_arg(const char *pStr)
07: {
08:     printf("%s\n", pStr);
09: }
10: 
11: int main(void)
12: {
13:     const char *p = NULL;
14:     print_arg(p);
15:     return 0;
16: }

$ gcc -o tst nonnull.c
$

何も出なかったし、実行したらSegmentation faultが発生した。
最適化されて変数pなど消されてしまいそうだが、コンパイル時に出なかったので前の方でチェックしているのだろう。


まあ、動的にチェックしたとしても、じゃあNULLだったときにどう振る舞うんだ?という疑問が出てしまうので、このくらいしかできないのかもしれんな。


「変数の初期化くらいしたらいいやん」と言われたらそれまでなのだが、組み込み向けだとROMの消費を気にしてしまうので、私は減らしてしまうのだ。
まあ、言い訳だな、うん。

2019/01/05

[c/c++]calloc()の謎

今回は、CERTではなく、標準ライブラリのcalloc()についての疑問だ。


UnitTestをしたくて、gtestを使ってコードを書いていた。
私が動かしているところではテストが通るのだが、別のところでNGになってしまい、これは変数の初期化がテストコードに入ってないためだろうと推測した。

malloc()で構造体を用意して、必要そうなところに値を入れてテストしていたのだが、malloc()なのでメモリが初期化されず、不定値のためOKだったりNGだったりしてしまうのだろう。
内部で持っている値と比較して、小さかったらエラーにする処理があり、そこが不安定なようだ。


じゃあ、とりあえずmalloc()のあとmemset()でもして、まずは不定値をなくそうと考えた。
実際のコードでは未初期化のまま使わないのだが、UnitTestなので許してもらえるだろう。
malloc()してmemset()となると、同じようなことを2回書くような気持ちになってしまうのだが、そういえばcalloc()というものがあることを思い出した。

malloc()をcalloc()に一括置換したが、コンパイルエラーになった。
そう、malloc()はサイズだけ指定するのだが、calloc()はブロックサイズとブロック数を指定するからだ。


んー、なんでだろう?


calloc()を振り返ろう。

http://linuxjm.osdn.jp/html/LDP_man-pages/man3/calloc.3.html

malloc()、calloc()、realloc()があるが、ブロックサイズとブロック数という指定がいるのはcalloc()だけなのだ。
似ているのは、fwrite()やfread()か。


まあ、単純に考えれば、1バイトを100回処理するよりも、100バイトを1回処理した方が短時間で終わりそうな感触はある。
100バイトというのは単位が悪いので、例えば32bit CPUであれば、4バイト単位で25回だと効率がよさそうだ。
64bit CPUだと、8バイト単位で12回+4バイト単位で1回、というやり方もできるだろう。
1回の処理時間が同じなら、回数が少ない方が短時間で終わる。


あと、デバイスの操作となると、1回でアクセスできる幅がデバイスによって異なることはしばしばだ。
結局はデバイスドライバが受け取って、ドライバがデバイスに合わせたアクセス方法をするだろうから、大きめのブロック数にしておけばうまいこと処理してくれるのかもしれない。


ただ、fwrite()のようにファイルシステムを使うものはまだわかるのだが、calloc()はなんだろうか?
あるとすれば、mmap()などでマッピングしたメモリへのアクセスになるのかもしれんが、それを標準ライブラリで吸収しにいくだろうか・・・。

それに、calloc()でできるのは0で埋めることだけだ。
だったらmemset()も同じようなインターフェースにしてよい気もするのだが、そうはなっていない。


うーむ、何か経緯があるのだろうか?


記事が見つかった。

malloc+memsetとcallocの違いについて
http://mojavy.com/blog/2014/03/05/difference-between-malloc-and-calloc/

速度の違いについては、memset()は実際にメモリ操作を行うけど、calloc()は行わない可能性がある、と。
インターフェースについては、アラインメントが考慮できるかも、というところか?


実装例があるとわかりやすいんじゃなかろうか。

https://github.com/gcc-mirror/gcc/blob/master/libiberty/calloc.c

"gcc calloc"で検索すると出てきたのだが・・・2つの引数にほぼ意味が無い。
やはり「考慮できるかも」の"かも"に過ぎないということか。


しばらく検索すると、CERTの記事が見つかった。

MEM07-C. calloc() の引数は乗算した結果がラップアラウンドしないようにする
https://www.jpcert.or.jp/sc-rules/c-mem07-c.html

ラップアラウンド=wrap around=包み込む、と出てきたが、桁あふれというかバイトあふれというか、8bit変数で255+1すると0に戻る、というような動作の意味もあるらしい。
https://www.weblio.jp/content/wraparound

malloc()はsize_tを取るので、calloc()でも乗算してsize_tを超えないようにしろ、ということだろう。
(関係ないけど、size_tってよく使うけど;移植しにくい型よね・・・。)



ともかく、やはり単純に乗算する実装が多いということだろう。
アラインメント考慮したAPIは標準ライブラリよりもOSなどで提供した方がよいような感じがするのだけど、最初の方で組み込んでしまったので、もはや消すことができなくなってしまった、ということなのかね。

2019/01/01

[c/c++][CERT]同期用プリミティブ

C言語の学び直しシリーズ。


POS03-C. volatile を同期用プリミティブとして使用しない
https://www.jpcert.or.jp/sc-rules/c-pos03-c.html

えっっ??
スレッドでvolatileしとけばなんとかなると思っていたけど、そうでもないってことか?
いや、中身を読まずに雰囲気だけで判断してはいかんな。


アトミック性は、mutexなどで同期を取ればよかろう。

私としては、volatileで別スレッドから書き込まれた変数を参照する、ということでよく行うのだが、これは「可視性」と呼ばれるものだと思う。

逐次性・・・は、よくわからん。
メモリ操作が同じ順番で見える、というのは何だろうか?


私の期待しているvolatileは、該当する変数に対してのアクセスを最適化しない、というものだ。
ループで監視するとき、その実行単位の中で変更する処理がなければ最適化したくなるだろうが、そうせずに直接メモリにアクセスする、とか。

ただ、私がやってきたのは組み込みがほとんどなので、コンパイラも組み込み向けがほとんどだった。
だから、volatileはスレッドというよりも、割り込みハンドラに対してのものだ。
そういう知識を元にしているので、OSとかスレッドとかになると、すったりなのだ。


というわけで、文章を読んでもよくわからんし、コードを見ていこう。


まず、最初の違反コード。
これはflagという変数をvolatileでも何でも無く宣言していて、while()の条件として参照しているだけだ。
だから、コンパイラとしてはwhile()の実行前にflagを評価し、それをメモリか何かに保存するだけだろう。

こういう使い方を「同期用プリミティブ」と呼ぶのだろうか?


そして次の違反コード。
これは、flagをvolatileにしただけ。
こうすると、flagに関しての最適化が行われないので、while()では毎回flagを直接アクセスするし、wakeup()が呼ばれればメモリが更新され、while()を抜けるだろう。


それだけでは満足できないらしい。
最後の適合コードでは、volatileでのフラグ参照ではなく、mutexを使っている。
「使用しない」というのは、そういうことか。。

ここではaccount_balanceという変数を複数からアクセスさせたくない、という前提があるのだろう。
だから、flagでwhile()監視するとか、そういうのがそもそも関係が薄い話なのだな。


書込みする方と読み込む方が必ず一方向しかないなら、クリティカルセクションじゃなくても済むだろう。
フラグが立つのを待ってから処理する、とか。
まあ、そういうのは状況によって変わるから、無難に行くならクリティカルセクションか。


フラグ方式にして不安としてあるのが、アトミックにアクセスできない場合だ。
CPUのバス幅より大きかったり、ビット処理をしたりすると、1回ではアクセスできまい。
そうなると、変更している途中に別のスレッドにディスパッチしたりしまうと、まずかろう。

フラグでビットを1つ立てる方だったらそこまで神経質にならんでいいかもしれんが、値を判定するだったら怖いな。
前にやらかしたのは、時計だ。
時計用の変数を1秒間隔のスレッドで更新し、別のところでは変数をアクセスするようにしていた。
で、1秒スレッドで書き換えるところをmutexしていなかったので、不具合として「希に時計の評価がおかしい」という最悪の現象が出てしまったのだ。

フラグでも、32bit環境で64bit変数の2つビットを立てるとなると、ビット位置によっては2回アクセスしないと終わらないので、同じようなことがあり得るだろう。
ああ、こわいこわい。。。