C

sizeof演算子について

More than 1 year has passed since last update.

C言語のsizeof演算子について、まとめました。

sizeof(int)==4であり、ポインタ型のサイズが4である処理系を前提とします。

オペランド
コード例
戻り値について

(型名)
sizeof(int);
型のサイズを返す。オペランドに型名を指定する場合、必ずカッコで囲む。

char
sizeof(char);
規格の定義により、型char、signed char、unsigned char、およびそれらの修飾版をオペランドとするsizeof式の結果は、1でなければならない。

単項式
sizeof n; sizeof(n+1);
式が返す値の型のサイズを返す。

文字定数
sizeof('A');
sizeof(int)に等しい。C++の場合、sizeof(char)に等しい。

文字列リテラル
sizeof("abc");
末尾のナル文字を含むサイズを返す。コード例では3+1=4を返す。

空文字列
sizeof("");
末尾のナル文字のサイズ=1を返す。

ポインタ型
sizeof(char *);
ポインタサイズを返す。

空ポインタの間接参照
sizeof(*(int *)NULL);
sizeof演算子のオペランドが評価されるのは、C99においてオペランドが可変長配列型の場合のみである。それ以外の場合、オペランドは評価せず、結果は整数定数となる。コード例の場合、NULLポインタの間接参照は発生せず、結果は4となる。

配列
sizeof(buf);
配列のサイズを返す。原則的に、式中の配列オブジェクトはその配列の先頭要素へのポインタ値を返すが、配列がsizeofのオペランドである場合は例外である。

配列型の仮引数
void f(int p[10]){sizeof(p);}
配列型の仮引数は、その配列の先頭要素へのポインタに型調整される。したがって、仮引数をオペランドとするsizeof式は、配列のサイズではなくポインタサイズを返す。ただし、仮引数が多次元配列であり、その部分配列がsizeofのオペランドである場合、その部分配列のサイズを返す。

列挙型
sizeof(enum COLOR);
sizeof(enum {RED,GREEN,BLUE};
列挙型は汎整数型に分類されるが、型の選択は処理系定義。

構造体
sizeof(struct node_t);
構造体のサイズを返す。バイトアラインメントにより構造体オブジェクトがパディングを含む場合、パディングを含めたサイズを返す。

共用体
sizeof(union union_t);
共用体のメンバのうち、最大のメンバのサイズを返す。バイトアラインメントにより共用体オブジェクトがパディングを含む場合、パディングを含めたサイズを返す


sizeof(n++);
sizeof演算子のオペランドが評価されるのは、C99においてオペランドが可変長配列型の場合のみである。それ以外の場合、オペランドは評価せず、結果は整数定数となる。したがってnはインクリメントされない。

(C99)可変長配列
n=2; sizeof(int [n++]);
オペランドの型が可変長配列型である場合、オペランドは評価される。後置インクリメント演算子は、そのオペランドの値を返すため、コード例のsizeof式は、2*4=8を返す。nは3へインクリメントされる。

関数呼出し1
sizeof(printf("abc"));
関数の返却値の型のサイズを返す。printf関数の返却値の型はint型であるため、コード例ではsizeof(int)に等しい値を返す。オペランドがC99での可変長配列型でないため、コード例のprintf関数は実行されない。

(C99)関数呼出し2
sizeof(int [printf("abc")]);
オペランドが可変長配列型であるため、オペランドに含まれるprintf関数を含む式は評価される。規格C99では、printf関数の戻り値は「書き出された文字数」と定義されているが、問題はそれが「ライブラリの内部バッファに書き込みした時点での文字数」なのか、「最終的に出力ストリームへフラッシュした文字数」なのかという点である。「書き出す」という表現について、C99規格票の他の用例をあたってみたところ、abort関数の説明文に「書き出されていないバッファリングされたデータ」という表現を見つけた。この表現からして、「書き出し」=「出力ストリームへのフラッシュ」という意味と判断される。とすると、コード例でのprintf関数呼出し時点では(行バッファリングが有効の状態では)バッファはフラッシュされていないのだから、コード例のsizeof式は0を返すはず…と思いきや、行バッファリング有効の状態でコード例を実行したところ、printf関数復帰のタイミングでは標準出力されないものの、sizeof式は3*4=12を返した。C99規格の「書き出された文字数」という過去形表現は、「printf関数の副作用完了点(ライブラリ関数が復帰する直前)を起点とした過去」を意味するものではなく、単純に「printf関数の呼出しによって書き出された文字数」ととらえるのが正解なのだろうか。

(C99)関数呼出し3
sizeof(int [printf("ab\nc")]);
行バッファリング有効の場合、printf関数復帰のタイミングでは改行までの文字列("ab\n")を出力する。行バッファリング有効の状態でコード例を実行したところ、printf関数復帰のタイミングでは"ab\n"が出力されたが、sizeof式は4*4=16を返した。

関数型ポインタ
sizeof(&main);
関数型ポインタのサイズを返す。

関数指示子
sizeof(main);
規格では許されない。gccで試したところ、無意味な定数1を返した。

関数型の仮引数
void f(void(*fp)(void)){sizeof(fp);}
関数の仮引数に関数指示子を指定すると、その仮引数は関数へのポインタ型に型調整される。コード例では関数型ポインタのバイトサイズを返す。

ビットフィールド
(省略)
規格では許されない。gccではコンパイルエラー(‘sizeof’ applied to a bit-field)。

不完全配列型
extern int n[]; sizeof(n);
規格では許されない。gccではコンパイルエラー(invalid application of ‘sizeof’ to incomplete type ‘int[]’)。

任意の記憶域クラス指定子で修飾された型名
sizeof(auto int);
規格C90では特に禁止されていないが、gccではコンパイルエラー(expected expression before ‘auto’)。

任意の記憶域クラス指定子で修飾された型を持つ変数
register int n; sizeof(n)
こちらはコンパイルエラーは出ない。

void
sizeof(void);
規格では許されない。gccで試したところ、1を返した。


sizeof式の結果の型

sizeof式の結果の型は、size_t型という非負整数である(<stddef.h>に定義)。sizeof式をオペランドに含む演算では、その結果が非負整数となる可能性があることに注意する。

以下のコード例は、char型のサイズとint型のサイズを比較することを意図したものであり、直観的には"int is greater than char."が表示されるように思える。しかし、意図した通りには動作しない。「>」の左辺式の結果はunsignedとして評価されるからである。

すなわち、(数学的式としては)-3>0ではなく、((2^32)-3)>0という式として評価される(負数の内部表現を2の補数とし、size_t型が32ビットであると仮定)。よって、不等式は真と評価される。


sizeof_test.c

if(sizeof(char) - sizeof(int) > 0)

printf("char is greater than int.\n");
else
printf("int is greater than char.\n");


実行結果

$ ./sizeof_test

char is greater than int.


sizeof演算子の定義(C89/90)

「プログラム言語C JIS X 3010-1993 (ISO/IEC 9899:1990)」より引用。


6.3.3.4 sizeof演算子

制約 sizeof演算子は, 関数型若しくは不完全型をもつ式, それらの型の名前を括弧で囲んだもの, 又はビットフィールドオブジェクトを指し示す左辺値に対して適用してはならない。

意味規則 sizeof演算子の結果は, そのオペランドの(バイト数での)大きさとする。オペランドは, 式, または括弧で囲まれた型の名前のいずれかとする。その大きさは, オペランドの型によって決定する。オペランド自身は, 評価しない。その結果は, 整数定数とする。

 型char, unsigned char又はsigned char(又はそれらの修飾版)をもつオペランドに適用した場合の結果は、1とする。配列型をもつオペランドに適用した場合の結果は, その配列中の総バイト数とする(43)。構造体型又は共用体型をもつオペランドに適用した場合の結果は、内部及び末尾の詰め物部分を含めたオブジェクトの総バイト数とする。

 結果の型は, 処理系定義とし, その型(符号無し整数型)は, ヘッダ<stddef.h>に定義されているsize_tとする。


注(43) 配列型又は関数型をもつと宣言された仮引数に適用する場合, sizeof演算子は, 6.2.2.1の型変換によって得られるポインタの大きさを与える(6.7.1参照)。