はじめに
今回はsizeofオペレータをとりあげます。sizeofは、引数に与えたオブジェクトや型名から、その型のデータがメモリ上に占めるバイト数を求めるオペレータです。皆さんも、メモリ領域を動的に確保したいときや文字列操作などに関連して使ったことがあるでしょう。まずはsizeofオペレータの使用上の注意について説明し、その後でsizeofオペレータの誤用例を2つ紹介します。
sizeofオペレータ使用上の注意
「sizeofオペレータの使用上の注意 その1」は、プログラマが意図する正しい引数を渡すこと、です。メモリ上のオブジェクトのコピーや移動などの操作は、オブジェクトのメモリ上のサイズにもとづいて行います。オブジェクトのサイズを間違っていたら、アクセス違反やオーバフローといった脆弱性につながることは容易に想像できるでしょう。
「sizeofオペレータの使用上の注意 その2」は、引数に副作用を持つような式を渡さないこと、です。C言語仕様では、sizeofオペレータが引数を評価するのは引数の型が可変長配列型であるとき、それ以外の場合は引数を評価しない、と明記しています(C言語仕様セクション6.5.3.4)。
例えば、次のようなコードではaの値はインクリメントされません。
int a = 14; int b = sizeof(a++); printf("%d\n", a);
3行めのprintfが出力するaの値は14のままです。sizeofは「オペレータ」(演算子)なのでかっこの中の式は、関数のように評価されるわけではありません。かっこの中はa++という式として認識されますが、sizeofオペレータにとってその計算をすることが仕事ではなく、指定された引数の型(サイズ)が分かればよいのです。
sizeofオペレータは引数の型を知りたいだけなので、不要な計算をしないのだ、と理解するとよいでしょう。コード上には a++ と書いてあるのにそれが評価されないというのは、プログラマの誤解を招くので避けるべきです。
ちなみに引数に可変長配列型の式が渡される例として、C言語仕様には以下のコード例が掲載されています(C言語仕様セクション6.5.3.4 EXAMPLE 3)。
#include <stddef.h> size_t fsize3(int n) { char b[n+3]; // variable length array return sizeof b; // execution time sizeof }
可変長配列bのサイズは関数 fsize3の実引数nに依存しており、実行時にbを評価してはじめてそのサイズが分かるというわけです。
「sizeof オペレータの使用上の注意 その3」は、間違ってポインタ自身のサイズを求めないように注意、です。これは注意その1とかぶりますが、あえて書きました。例えば、以下のコード例はdouble型データの配列を動的に確保しようとしています。
double *allocate_array(size_t num_elems){ double *d_array; if (num_elems > SIZE_MAX/(sizeof d_array)){ /* エラー処理 */ } d_array = (double *)malloc((sizeof d_array) * num_elems); if (d_array == NULL){ /* エラー処理 */ } return d_array; }
2か所使われている sizeofオペレータの引数がどちらもポインタなので、返り値はポインタ自身のサイズになります。実際に求めたいのはポインタが指している先のメモリ領域のサイズなので、sizeof(d_array)ではなくsizeof(*d_array)と書くべきです。あるいは型名を使ってsizeof(double)としてもいいでしょう。
「その1」と「その3」で挙げた問題が実際のライブラリで見つかっています。標準的な画像データを扱うライブラリとIMAPサーバのデーモンで見つかった例を以下に紹介します。