二次元配列をダンプする

頭の体操として、二次元配列の各要素を参照するコードを、いろんな書き方で書いてみようと思います。

いまさら二次元配列?と思うなかれ。こういう当たり前のコードを、1ステップずつ意味を理解した上で、高速でコーディングできるのも、ひとつの技術力だと思うのです。相対的な話ですけど、息を吸うようにシェルスクリプトを操れる技術とか、自分により距離の近い技術の方が、熟達すべき技術としての優先度は高いと思います。

さておき、前回使ったint型二次元配列の全要素についてをダンプするコードを、添字指定で書いてみました。

  1 #include <stdio.h>
  2
  3 int main(int argc, char **argv){
  4
  5     int matrix[][5] = {{100,101,102,103,104},
  6                        {200,201,202,203,204},
  7                        {300,301,302,303,304},};
  8     int i,j;
  9
 10     /* 添字指定あり */
 11     for(i=0; i<sizeof(matrix)/sizeof(matrix[0]); i++){
 12         for(j=0; j<sizeof(matrix[0])/sizeof(matrix[0][0]); j++){
 13             printf("%d ", matrix[i][j]);
 14         }
 15         printf("\n");
 16     }
 17
 18     return 0;
 19 }

Cプログラマの間では、配列の各要素へアクセスするのに、あえて添字指定を使わずに間接演算子で間接参照する方が、なんかハッカーっぽくてかっこいいという暗黙の風潮があるような気がしますが、僕は添字指定の方が直観的に分かりやすいと感じるので、極力、添字指定を使っています。確かに間接演算子の方がよりマシン語実装に近い表現ではあるのですが、コンパイルすりゃどうせ同じですしね。

ここから頭の体操です。上のダンプ処理の部分を、間接演算子を使って書いてみます。

    /* 添字指定なし */
    for(i=0; i<sizeof(matrix)/sizeof(*matrix); i++){
        for(j=0; j<sizeof(*matrix)/sizeof(**matrix); j++){
            printf("%d ", *(*(matrix+i)+j));
        }
        printf("\n");
    }

1ステップずつ追って行きます。

    for(i=0; i<sizeof(matrix)/sizeof(*matrix); i++){

sizeof(matrix)は、二次元配列matrixの全体サイズ(sizeof(int)*5*3=60バイト)を返します。

sizeof(*matrix)は、

  1. matrixはT型配列(T=配列)オブジェクトである。
  2. 式中の配列オブジェクトは、その先頭要素へのポインタ値に展開される。
  3. 先頭要素へのポインタ値とは、先頭部分配列{100,101,102,103,104}へのポインタ値である。
  4. このポインタ値に間接演算子(*)をつけると、配列オブジェクト{100,101,102,103,104}に展開される。

通常、式中における配列オブジェクトは、その先頭要素へのポインタ値に展開されますが、(*matrix)はsizeof演算子オペランドであるため、その展開は行われません。したがって、sizeof(*matrix)は、配列{100,101,102,103,104}のサイズ(int*5=20バイト)を返します。

以上より、式sizeof(matrix)/sizeof(*matrix)は、60/20=3、つまりmatrixのエントリ数(レコード数)を返します。このループは、テーブルmatrixの各エントリについて、iを参照カーソルとして1エントリずつ舐めるループとなります。

        for(j=0; j<sizeof(*matrix)/sizeof(**matrix); j++){

sizeof(*matrix)は、先ほどの通り、matrixの1エントリ数のサイズ(20)を返します。
sizeof(**matrix)は、

  1. *matrixは、配列{100,101,102,103,104}を返します。
  2. 式中の配列型オブジェクトは、その先頭要素へのポインタを返します。この場合、先頭要素{100}へのポインタ値を返します。
  3. **matrixは、先頭要素{100}へのポインタ値を間接参照するため、値100を返します。
  4. sizeof(**matrix)は、すなわちsizeof(100)という意味になるので、もちろん4(int型サイズ)を返します。

以上より、sizeof(*matrix)/sizeof(**matrix)は、20/4=5、1エントリのフィールド数を返します。

            printf("%d ", *(*(matrix+i)+j));
  1. matrixは、T型配列(T=配列)の先頭要素へのポインタ値を返します。つまり、配列{100,101,102,103,104}へのポインタを返します。
  2. matrix+iは、matrixが先頭部分配列へのポインタ値を返すことから、これにiを加算すると、matrixの先頭アドレスに「i個分のエントリのサイズ」を加算したポインタ値を返します。例えばi=1の場合、そのポインタ値(matrix+1)は、配列{100,101,102,103,104}の領域末尾の次の領域を指すことになります。
  3. 演算子の優先順位は「*>+」ですから、*(matrix+i)+jにおいて、*(matrix+i)が先に計算されます。
  4. (matrix+i)は、現在参照している配列オブジェクト{x00,x01,x02,x03,x04}へのポインタ値を返しますから、*(matrix+i)は配列{x00,x01,x02,x03,x04}に展開されます。
  5. 式中の配列オブジェクトは、その先頭要素へのポインタ値に展開されるので、*(matrix+i)は値x00へのポインタ値に展開されます。
  6. (*(matrix+i)+j)は、値x00へのポインタ値にjを加算したものに展開されます。x00はint型であるため、そのポインタ値にj加算すると、j*4バイト先を参照することになります。つまり、参照カーソルはフィールド単位で移動します。
  7. *(*(matrix+i)+j)は上記を間接参照しますから、現在参照カーソルのある値(x00〜x04のいずれか)に展開されます。

変数iはエントリ単位での参照カーソル、変数jはフィールド単位での参照カーソルとしてふるまうため、以上よりコード例の二重ループは、エントリ単位で参照カーソルiを回して、現在のエントリについて、参照カーソルjで各フィールドの値をダンプする。という動きになります。