2 配列

同じ型のデータが大量にある場合、配列を使うと便利である。配列名と自然数の添え字に よりデータが指定できるので、大量のデータでも容易にアクセスできる。この配列とメモ リー及びポインターの関係を述べる。

ここでの話で特に重要なことは、配列の添え字から、データが格納されているアドレスを 割り出す方法である。そして、それを理解したならば、関数への配列の受け渡し方につい て考える。最終的には、配列を関数へ渡す方法を理解して欲しい。

2.1 一次元配列

C言語では、配列名はデータの先頭を表すポインターのように動作する。以前学習したよ うに、ポインターの値に1加算すると、次のデータを示すようになる。したがって、配列 名に1加算すると、次の配列の値を示すポインターになる。これについては、具体例を示 した方が分かりやすいだろう。リスト1を使って説明する。
4行
整数型のポインターpと要素数3の整数型の配列a、整数型の変数 iの宣言
10行
配列aの先頭アドレスをポインターpへ代入
12行
ポインターpのアドレスと、そこに格納されているアドレスを表示
15行
配列のアドレスと値、aをポインターと考えた場合のアドレスと値、ポ インターpのアドレスと値の表示

   1 #include <stdio.h>
   2 
   3 int main(void){
   4   int *p, a[3], i;
   5   
   6   a[0]=11;
   7   a[1]=22;
   8   a[2]=33;
   9 
  10   p=a;
  11 
  12   printf("%p %p\n", &p, p);
  13 
  14   for(i=0;i<3; i++){
  15     printf("%p %d %p %d %p %d\n", &(a[i]), a[i], a+i, *(a+i), p+i, *(p+i));
  16   }
  17 
  18   return 0;
  19 }
\fbox{実行結果}
0xbffff69c 0xbffff680
0xbffff680 11 0xbffff680 11 0xbffff680 11
0xbffff684 22 0xbffff684 22 0xbffff684 22
0xbffff688 33 0xbffff688 33 0xbffff688 33

このプログラムの実行直後のメモリーの様子を図1に示す。このメ モリーの様子と実行結果から、次に示す2つのことが分かるだろう。

まずは、配列名は配列の先頭アドレスを示すポインターのように動作する。したがって、 リスト1の10行目のように、左辺値として配列名を指定して、それを ポインターに代入することができる。

次に分かることは、配列への要素のアクセスは、ポインターを使って表現できる。すなわ ち、a[i]は、*(a+i)と同じであることがわかる。事実、コンパイラーはこのよ うにしてメモりーにアクセスするように機械語に変換するのである。

図 1: プログラム実行後のメモリーの様子
\includegraphics[keepaspectratio, scale=1.0]{figure/array_1D.eps}

2.2 多次元配列

C言語の多次元配列について正確に述べようとすると、ここでの説明よりも、さらにもっ と込み入った話がある。ますます混乱する者が多くなりそうなので、ここではコンパイラー の動作を考慮した細かい説明は避ける。興味のある者は、自分で学習せよ。

これまでの話で、一元のポインターは分かった。2次元以上はどうなっているのだろうか?。 同じようにプログラムを作成して調べてみるのが良いだろう。ここでは、配列の添え字か らどのようにしてメモりーのアドレスの導出方法に興味がある。そのために、リスト 2の2次元の配列を使ったプログラムを考える2

このプログラムの実行結果と図2のメモリーの様子から、a[i][j] は配列名の先頭アドレスa3*i+j加算したアドレスになることが分かる。

さらに、ここでも配列はポインターを用いて、

a[i][j] $ \quad\rightarrow\quad$*(a+i)[j] $ \quad\rightarrow\quad$*(*(a+i)+j)
となっていることが分かる。ここのところは分からなくても良い。
   1 #include <stdio.h>
   2 
   3 int main(void){
   4   int a[2][3], i,j;
   5   int *p;
   6 
   7   p=a;
   8 
   9   printf("pointer p address %p  value %p\n", &p, p);
  10   
  11   a[0][0]=0;   a[0][1]=1;   a[0][2]=2;
  12   a[1][0]=10;  a[1][1]=11;  a[1][2]=12;
  13 
  14   for(i=0; i<2; i++){
  15     for(j=0; j<3; j++){
  16       printf("%p %d %p %d %p %d\n",
  17 	     &(a[i][j]), a[i][j], p+3*i+j, *(p+3*i+j), *(a+i)+j, *(*(a+i)+j));
  18 
  19     }
  20   }
  21 
  22   return 0;
  23 }
\fbox{実行結果}
pointer p address 0xbffff674  value 0xbffff680
0xbffff680 0 0xbffff680 0
0xbffff684 1 0xbffff684 1
0xbffff688 2 0xbffff688 2
0xbffff68c 10 0xbffff68c 10
0xbffff690 11 0xbffff690 11
0xbffff694 12 0xbffff694 12
図 2: 2次元配列を使ったプログラムの実行後のメモリーの様子
\includegraphics[keepaspectratio, scale=1.0]{figure/array_2D.eps}

2.3 関数への配列の受け渡し

ここでしつこく配列の添え字からメモリーのアドレスの出し方を示したのは、関数へ配列 を渡す場合について説明したかったからである。

多次元配列の例でも分かるように、配列のデータへアクセスする場合、配列名と添え字の 値と、配列のサイズが必要である。例えば、int hoge[10][20][30]と配列を定義した場 合を考える。このとき、hoge[i][j][k]のデータにアクセスするためには、

が必要である。配列のサイズの最初の値10は不要である。

このことから、関数に配列の全てを渡す場合には、配列名とサイズを渡す必要があること が分かる。ただし、配列名直後のサイズは不要である。配列の要素にアクセスする時には 不要である。配列を引数にした関数は、リスト3のように書くこと ができるのである。

実行結果を見て分かるように配列の場合は、アドレス渡しとなっている。通常の変数は値 渡しであるが、配列は特別な扱いを受けている。これは、通常の配列は非常に大きいので、 値渡しでいちいちメモりーにコピーしていては時間がかかりすぎるからである。

   1 #include <stdio.h>
   2 void double_array(int fuga[][20][30]);
   3 
   4 /* ======================================================== */
   5 /*    main function                                         */
   6 /* ======================================================== */
   7 int main(void){
   8 
   9   int hoge[10][20][30];
  10   int i, j, k;
  11   
  12   for(i=0; i<10; i++){
  13     for(j=0; j<20; j++){
  14       for(k=0; k<30; k++){
  15 	hoge[i][j][k]=i+j+k;
  16       }
  17     }
  18   }
  19 
  20   double_array(hoge);
  21 
  22   for(i=7; i<10; i++){
  23     printf("hoge[%d][%d][%d]=%d\n", i, i, i, hoge[i][i][i]);
  24   }
  25 
  26   return 0;
  27 }
  28 
  29 /* ======================================================== */
  30 /*    function   double_array                               */
  31 /* ======================================================== */
  32 void double_array(int fuga[][20][30]){
  33 
  34   int i, j, k;
  35 
  36   for(i=0; i<10; i++){
  37     for(j=0; j<20; j++){
  38       for(k=0; k<30; k++){
  39 	fuga[i][j][k]=2*fuga[i][j][k];
  40       }
  41     }
  42   }
  43 
  44 }
\fbox{実行結果}
hoge[7][7][7]=42
hoge[8][8][8]=48
hoge[9][9][9]=54

ホームページ: Yamamoto's laboratory
著者: 山本昌志
Yamamoto Masashi
平成19年6月24日


no counter