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などで提供した方がよいような感じがするのだけど、最初の方で組み込んでしまったので、もはや消すことができなくなってしまった、ということなのかね。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。