これまで授業準備の参考にしてきた本の中に,「関数の仮引数に配列を指定できる」と書かれていて,驚きました.
関数の仮引数は,配列の形で宣言できること,そして要素数は何であっても関係ないし,書かなくてもいいことは,授業でも少し説明しています.配列サイズを書かないのは,mainの仮引数のargvに使っています.
しかし,だからといって,配列の値をコピーするわけではないのですが….
関数の仮引数が配列かそうでないか,確認するプログラムを書いてみました.
#include <stdio.h> int sizeof1(int array[10]) { return sizeof(array); } int sizeof2(int array[]) { return sizeof(array); } int sizeof3(int *array) { return sizeof(array); } int main(void) { int array[10]; printf("sizeof(array) : %d\n", sizeof(array)); printf("sizeof1 : %d\n", sizeof1(array)); printf("sizeof2 : %d\n", sizeof2(array)); printf("sizeof3 : %d\n", sizeof3(array)); return 0; }
結果は以下のとおり.CygwinのGCC 3.4.4を使用しましたが,たぶん他でも,「sizeof1〜sizeof3の数値(バイト数)が同じ」なのは,変わらないでしょう.
sizeof(array) : 40
sizeof1 : 4
sizeof2 : 4
sizeof3 : 4
なぜ「他でも変わらない」と言えるか…「printf("sizeof1 : %d\n", sizeof1(array));」の中のarrayは配列変数ではなく,その配列の先頭アドレス(いわゆるポインタ値)であって,そのポインタ値が関数に渡るからです.
ここで別の考え方をとってみます.もし「関数の仮引数を配列で書けば,それが配列変数となり,実引数(の配列)の値がコピーされる」のが規格で定められているのなら,どうなるかを考えてみます(背理法proof by contradictionですね).そうすると,上記のsizeof1関数やsizeof2関数の中で,「sizeof(array)/sizeof(array[0])」によって配列の要素数が求められます.これは画期的なことです.プログラミングテクニックとして,書籍やインターネット上で広く紹介されているはずです.もちろん現実はそうではありません.配列を関数に渡すときには,ここにも書いたとおり,その要素数の情報も合わせて渡すことになります.これは結局のところ,「関数の仮引数を配列で書けば,それが配列変数となり,実引数(の配列)の値がコピーされる」が正しくないことの傍証となるでしょう.
これまで授業で「関数の仮引数に配列形式で書いても,それはポインタ変数である」と説明してきまして,本を読んでこれは根本的に間違っていたかと不安になりました.宣言時には,上述のsizeof1の中の変数arrayは配列型,sizeof2の変数arrayは不完全型であっても,これらの変数をメモリ上で確保して*1実引数の値をコピーする時点で,ポインタ変数になる,と解釈すればよさそうです.「それは実質的にポインタ変数である」と修正して,今後も説明に使うことにします.
*1:仮引数はauto変数なので,関数呼び出し時に確保される点に注意.