わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

仮引数に配列を指定できるが,それは配列ではない

これまで授業準備の参考にしてきた本の中に,「関数の仮引数に配列を指定できる」と書かれていて,驚きました.
関数の仮引数は,配列の形で宣言できること,そして要素数は何であっても関係ないし,書かなくてもいいことは,授業でも少し説明しています.配列サイズを書かないのは,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;
}

結果は以下のとおり.CygwinGCC 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変数なので,関数呼び出し時に確保される点に注意.