C言語のsizeofにおける配列、ポインタ、構造体などの動きのまとめ

C言語のsizeofについて色々調べたのでまとめてみました。

sizeofとは何か?

sizeofとは、型や変数などのメモリサイズを返す関数のこと。例えば、intやfloat等の型を指定した場合は「型が利用するメモリサイズ」を返し、変数やポインタ、構造体を指定した場合は「変数等で確保されているメモリサイズ」を返す。

sizeofの戻り値の型はsize_tであり、32bitか64bitなのか等の環境の違いによってsize_tの型も変わる。(例えば、32bitであれunsigned intが使用されて、64bitであればunsigned long intが使用される。また、OSによる違いもある)

#include <stdio.h>

void main() {

  printf("char size is %d\n", sizeof(char));
  printf("char* size is %d\n", sizeof(char *));
  printf("int size is %d\n", sizeof(int));
  printf("int* size is %d\n", sizeof(int *));
  printf("float size is %d\n", sizeof(float));
  printf("float* size is %d\n", sizeof(float *));
  printf("double size is %d\n", sizeof(double));
  printf("double* size is %d\n", sizeof(double *));
}

実行すると、以下の様な結果になった。

char size is 1
char* size is 4
int size is 4
int* size is 4
float size is 4
float* size is 4
double size is 8
double* size is 4

参考記事:sizeof operator in C - GeeksforGeeks

参考記事:sizeof演算子

あと、最近叩かれている某プログラミングスクールの記事では以下の様なコードを書いていたが、sizeofの使い方としては全くダメな例だ。

#include <stdio.h>

void main() {

  printf("char size is %d\n", sizeof(32));
}

上のコードでは、sizeof(32)で32byteが返ってくることを想定して書いたコードだろう。しかし、実際には32は単にint型の整数だとみなされて、sizeof(int)とした時と全く同じ結果が返ってくる。

むしろ、可読性が悪くなっているので、sizeof(int)よりもダメなコードと言える。

参考記事:侍エンジニア塾のC言語のサンプルがヤバすぎる。 - Qiita

sizeofのポインタについて

sizeofでポインタを指定する場合は、どの型や変数に関わらず4byteを返す。これはポインタが「アドレスを格納する」と言う役割を考えれば当然だと言える。

参考記事:ポインタ変数のサイズ

実際に、以下のコードを試してみる。

#include <stdio.h>

void main() {
  printf("char* size is %d\n", sizeof(char *));
  printf("int* size is %d\n", sizeof(int *));
  printf("double* size is %d\n", sizeof(double *));
}

結果は以下の通り、全てのポインタが4byteになる。

char* size is 4
int* size is 4
double* size is 4

配列のsizeofについて

sizeofに配列を指定すると、配列名を指定した場合はその配列全体のメモリサイズ、配列の1つの要素に対してsizeofを指定するとその要素のメモリサイズが表示される。

#include <stdio.h>

void main() {
  double array_d[50];

  printf("array_d is %d byte.\n", sizeof(array_d));
  printf("array_d[10] is %d byte.\n", sizeof(array_d[10]));
}

結果は以下の通り。

array_d is 400 byte.
array_d[10] is 8 byte.

先ほどの例では配列の要素名を指定した上でsizeofでメモリサイズを測定したが、配列の要素数が不確定の場合の方がsizeofを使うメリットが上がると思う。

#include <stdio.h>

void main() {
  double array_d[] = {4, 5, 55466, 43.55, 5552};

  printf("array_d is %d byte.\n", sizeof(array_d));
  printf("array_d[10] is %d byte.\n", sizeof(array_d[10]));
}

結果は以下の通りになった。

array_d is 40 byte.
array_d[10] is 8 byte.

構造体のsizeofについて

構造体のsizeofはその構造体の合計メモリサイズ以上の値を返す。必ずしも、その構造体の合計メモリサイズぴったりの値が返るわけではない。

例えば、以下のコードを書いたとする。

#include <stdio.h>

typedef struct {
  int n;
  int array_n[10];
  double dn;
} TestStruct;

void main() {
  TestStruct test;

  printf("test.n: %d, test.array_n: %d, test.dn: %d, test: %d", 
         sizeof(test.n), sizeof(test.array_n), sizeof(test.dn), sizeof(test));
}

上のコードの構造体はint型が1つ、int型の要素10つの配列が1つ、double型が1つで構成されているため合計52byteが返ってくると期待するだろう。 しかし、期待と異なる結果を返す。

test.n: 4, test.array_n: 40, test.dn: 8, test: 56

構造体の合計は本来52byteであるはずなのに、結果は56byteと返ってきた。

これはコンパイラが構造体を作る時に、各メンバをびっちり詰めて構造体を作るのではなく、各メンバと構造体の終わりに1byteの隙間を入れて構造体を作成するからであり、コンパイラの仕様とも言える動きだ。

参考記事:size of struct in C - Stack Overflow

ただ、絶対に隙間を作るわけではなく、例えば構造体のメンバがintとcharだけ等の場合は隙間を作らない場合もある。