配列と関数呼び出し


さて、関数呼び出しをすると配列がどのように扱われるのかをすこし見てみましょう。 今回は、例として文字列を使います。C においては、文字列というのは char (1byte=8bit)データの配列で、最後が '\0'文字で終わるものです。 ただの char の配列(charへのpointer)ということだと、 例えば printf 君はどこまで印字すればいいのか分からないので、 最後にかならず'\0'を入れておく約束になっているわけです。

さて、char buffer[100] とかを局所変数として作ると、どこにできるのか? 実は、配列も普通の局所変数と同じで stack frame 上に作られます。 たとえば、以下のプログラム(ソース)の中では、

つまり、各 frame 上に配列領域が作成されているのです。

一方で、当然ですが、ポインタ型の局所変数についても ポインタ用の領域が frame 上に確保されます。 ですから、このプログラムの実行はだいたいこんな感じになります。

メソッドの引数のところを char * a と書いても char a[] と書いても同じ意味にとられます(C の統一間のないところ)。 引数のところに配列の型を書いても、ポインタの意味で解釈されちゃうよって思っておくとすっきりするでしょう。


さて、じゃあ、今度はヤバイ事をやってみましょう(ソース)。 先ほどのものに、以下の関数を加えました。意図としては、buffer に org の文字列をコピーして、 できあがった buffer を返そうとしています。
char * copyString(char * org) { /* org から buffer に値をコピーして、buffer を返す(イケナイ事) */
  char buffer[1000];
  strcpy(buffer, org);
  return buffer;
}

int main(int argc, char** argv) {
  char * first_one;
....
  char sampleA[] = "AAAAAA";
....
  first_one = copyString(sampleA);
さて、なにが問題かというと、 buffer のポインタを呼び出し側に向けて漏らしています。 つまり、局所変数は関数呼び出しが終わるとなくなり、そのうち、別の関数呼び出しで同じ領域が使われることになります。 ですから、first_oneは、指している「相手が(確実には)いない宙ぶらりんなポインタ」になってしまいます。 実際、プログラムを実行すると、first_one の中身は、別の関数呼び出しが実行される度に変わってしまうことがあります。 こんな感じ。 実際に、debugger などでも挙動を確かめてみましょう。

実は、コンパイルの段階から、コンパイラは「ヤバイ、ヤバイ」って騒いでくれます。 皆さんは、コンパイラの助言に耳を傾けてください。

kamada@shusaku%gcc readlineB.c
readlineB.c: In function `copyString':
readlineB.c:6: warning: function returns address of local variable

実は、もうひとつヤバイ事があるんですが、それは余談で。


2002.10.14/ Tomio KAMADA: kamada@cs.kobe-u.ac.jp