配列の要素数を得るために以下のようなコードが紹介されていることが多い。これを実行した結果、配列の要素数5を得るにはsizeof a / sizeof a[0]とするのが良いように見える。
#include <stdio.h> int main() { double a[5] = {0}; printf("%p\t", a); printf("%d\t", sizeof a); printf("%d\t", sizeof a[0]); printf("%d\n", sizeof a / sizeof a[0]); return 0; }
0xbfd54498 40 8 5
このルールは静的配列には有効だが動的配列には無効である。以下のようなコードを考える。動的配列に対して静的配列と同じようにsizeof演算子を使って得られた結果は期待とは異なっていることが判る。これはsizeof演算子に対してint型配列へのポインタが与えられ、sizeof演算子はこのポインタのサイズを返したからである。
#include <stdio.h> #include <stdlib.h> int main() { double a_static[5] = {0}; double *p_a_dynamic = (double *)calloc(5, sizeof(double)); printf("%p\t", a_static); printf("%d\t", sizeof a_static); printf("%d\t", sizeof a_static[0]); printf("%d\n", sizeof a_static / sizeof a_static[0]); printf("%p\t", p_a_dynamic); printf("%d\t", sizeof p_a_dynamic); printf("%d\t", sizeof p_a_dynamic[0]); printf("%d\n", sizeof p_a_dynamic / sizeof p_a_dynamic[0]); return 0; }
0xbfd54498 40 8 5 0x95e79b0 4 8 0
静的配列に対してもポインタをとることが出来るので以下のようなコードを書くことが出来る。int型静的配列のポインタをsizeof演算子に与えたときの結果は動的配列のポインタのそれと同じである。
#include <stdio.h> #include <stdlib.h> int main() { double a_static[5] = {0}; double *p_a_static = a_static; double *p_a_dynamic = (double *)calloc(5, sizeof(double)); printf("%p\t", a_static); printf("%d\t", sizeof a_static); printf("%d\t", sizeof a_static[0]); printf("%d\n", sizeof a_static / sizeof a_static[0]); printf("%p\t", p_a_static); printf("%d\t", sizeof p_a_static); printf("%d\t", sizeof p_a_static[0]); printf("%d\n", sizeof p_a_static / sizeof p_a_static[0]); printf("%p\t", p_a_dynamic); printf("%d\t", sizeof p_a_dynamic); printf("%d\t", sizeof p_a_dynamic[0]); printf("%d\n", sizeof p_a_dynamic / sizeof p_a_dynamic[0]); return 0; }
0xbfd54498 40 8 5 0xbfd54498 4 8 0 0x95e79b0 4 8 0
ここで、動的配列に対してもsizeof演算子を作用させる方法があれば問題は解決するように思える。しかし、そのような方法は「ない」。そのため、静的配列、動的配列の要素数を得る一般的な方法は無い。したがってsizeof演算子を使うべきでは無い。
次善の策は、別の変数に配列の要素数を記憶させて、配列を使うときは必ずこの変数と一緒に使うということだ。静的配列に関してはsizeofによる解決策が使えるし、動的配列に関してはmallocやcallocに与える引数をあらかじめ変数に記憶させればよい。このルールに従えば、静的配列、動的配列は共通のコードですむ。
さらに配列を関数に渡し、この中で配列の内容を参照することを考えると、sizeof演算子による解決策が全く効かないことが判る。配列を関数に渡すと、配列の先頭アドレスが渡るが、これはつまりポインタが渡ったということと同じである。配列へのポインタに対するsizeof演算子では配列の要素数をもとめることは出来なかったことを思い出すと、配列を関数に渡す場合は「必ず配列の要素数を与えなければいけない」ことがわかる。
#include <stdio.h> #include <stdlib.h> void array_print (int *a, int N) { printf("%p\t", a); printf("%d\t", sizeof a); printf("%d\t", sizeof a[0]); printf("%d\t", sizeof a / sizeof a[0]); printf("%d\t", N); int i = 0; for (i = 0; i < N; i++) { printf("a[%d]=%d ", i, a[i]); } printf("\n"); return; } int main() { int a_static[5] = {0}; int a_static_N = sizeof a_static / sizeof a_static[0]; int *p_a_static = a_static; int a_dynamic_N = 5; int *p_a_dynamic = (int *)calloc(a_dynamic_N, sizeof(int)); array_print(a_static, a_static_N); array_print(p_a_static, a_static_N); array_print(p_a_dynamic, a_dynamic_N); return 0; }
0xbf9a561c 4 4 1 5 a[0]=0 a[1]=0 a[2]=0 a[3]=0 a[4]=0 0xbf9a561c 4 4 1 5 a[0]=0 a[1]=0 a[2]=0 a[3]=0 a[4]=0 0x87278c0 4 4 1 5 a[0]=0 a[1]=0 a[2]=0 a[3]=0 a[4]=0