sizeof演算子

せりか式 - C 言語チュートリアル - sizeof演算子

sizeof演算子について検索してくる人が多いようなので,真面目にsizeof演算子についても書いておくことにします.

sizeof演算子

簡単に言うと,sizeofに渡された型や変数のメモリサイズを調べるものです.

sizeof演算子は2種類の使い方があります.

前者の型を与える方は特に悩む必要は無いと思います. 単に指定した型が利用するメモリサイズを返すものです.
問題は,後者の変数や定数を渡す方です.

sizeofに変数を渡した場合,その変数名で確保されているメモリサイズを返します.

int型の場合
int data[50];
int size = sizeof(data);    // dataは配列全体
の場合,sizeof(data)は配列全体のサイズを指します.当然のことながら,
int型のポインタの場合
int *data;
int size = sizeof(data);    // dataはint型のポインタ
の場合,sizeof(data)int *のサイズを指します. これを踏まえた上で,
int型のポインタの場合2
int *data;
data = (int *)malloc(sizeof(int) * 100);
int size = sizeof(data);    // いったい何を指すのか?
の場合を考えてみましょう.
結構勘違いする人が多いようですが, mallocで確保したサイズではなく,int *のサイズを表しています.
mallocで確保したサイズを知りたい場合には,
ヘッダファイルmalloc.hをインクルードし, mallocで返されたポインタを引数として,malloc_usable_size関数を呼び出します.
mallocで確保されたメモリを知る
#include <malloc.h>

char *data;
data = (char *)malloc(100);
int size = malloc_usable_size(data);    // dataはmallocによって返されたポインタ
この場合,100かもう少し大きな値が返ってくるはずです.
ちょうど100にならないのは,メモリのアライメント(alignment)やoff-by-one バグに対する対策だとは思いますが,私もよく知りません.

いくつかのsizeofに関しての例をあげておきます

文字列に必要なメモリの長さを測る

sizeofに定数を渡すことができることから,文字列に必要なメモリの長さを知ることができます.

文字列に必要なメモリの長さを調べる
#define STR "文字列"
int size = sizeof(STR);    // defineで定義された文字列に必要なメモリの長さ

実際には,終端文字の都合上size+1のメモリが必要です.

長さを調べられない
char *STR = "文字列"
int size = sizeof(STR);    // ここでSTRはchar型のポインタなのでポインタのサイズになる
sizeofにより返される値のサイズを知る

もちろんsizeofの結果をsizeofに渡すこともできます

sizeofの返値のサイズを調べる
int size = sizeof (sizeof(int));    // sizeofの返値を引数としてsizeofを行う
for ループの例

sizeofの使い道の一つとしてforループのループ数の指定にあります.

forの例
double data[50];
double sum = 0;
for(int i = 0; i < sizeof(data) / sizeof(data[0]); ++i){
    sum += data[i];
}

ここで,sizeof(data)は,data[50]全体を指しますので,8 (double型のサイズ) x 50 = 400 となります.
次に,sizeof(data[0])は,一つの要素を指しますので,8 (double型のサイズ) となります.
そのため,400 / 8 = 50となり,dataの持つ要素数となります.
この方法で書いておくと,dataの型を変更した場合や,要素の数を変更した場合でもforの条件を書き換えずに, 要素全てに対して処理をおこなうことができます.

forに使えない例
double *data;
double sum = 0;
data = (double*)malloc(sizeof(double) * N);
for(int i = 0; i < sizeof(data) / sizeof(data[0]); ++i){
    sum += data[i];
}
free(data);

ここで,sizeof(data)は,data全体ではなく,dataの型つまりdouble *のサイズを示してしまいます. そのため,前の例のように,Nの回数分だけforの処理をすることはできません.

sizeof演算子の代用

sizeof演算子は、変数(型)のサイズを知るためによく使われますが、実はsizeof演算子を用いなくても、 サイズを知ることはできます。

プログラム1
    1:int ans;
    2:ans = (int)&(((int*)0)[1]);
この実行結果は、(処理系にもよりますが)4になるはずです。

また、構造体の途中までのサイズを知ることもできます。

プログラム2
    1:struct _data{
    2:   int A;
    3:   double B;
    4:   char C[4];
    5:};
    6:
    7:int ans;
    8:ans = (int)&(((struct _data*)0)->C);
この実行結果は、12になるはずです。

さて、なぜこのような結果になるか考えてみましょう。
さきほどの、(int)&(((struct _data*)0)->C)を見てみます。
これは、次の順に解釈されます。

    1:(struct _data*)0               0をポインタと見なして、struct _data型にキャスティングする。
    2:((struct _data*)0)->C          キャスティングした結果をもとに、変数Cを参照する。
    3:&(((struct _data*)0)->C)       参照したアドレスを読み出す。
    4:                               この時点で、0を基点としたCまでの距離が求められる。
    5:(int)&(((struct _data*)0)->C)  読み出したアドレスをint型にキャスティングする。
同様にして、(int)&(((struct _data*)0)[1])は、配列の次のアドレスまで、 つまり、一つの構造体のサイズを指すことになります。

ただし、これは構造体がきれいである必要があります。
プログラム中で使われる変数の開始位置は、メモリアクセスの関係上、通常は4バイト境界に置かれます。 そのため、構造体中にchar C[5];といったように4の倍数以外のサイズが紛れ込んだとき、 次の4バイト境界が出るまで未使用のメモリブロックとし、空白のメモリができてしまいます。
プログラム2において、char C[5];とすると、(int)&(((struct _data*)0)[1])の値は20となり、 実際の17とは異なる値となってしまいます。

追記
GNU libcは,8バイトになっているようですね.


トップへ