配列

アラインメントを明示的に指定しない限り、C/C++言語における配列はメモリ上に密に配置されます。特に二次元以上の配列や構造体の配列になると、たまに混乱の種になるので、配列のメモリ上の配置規則を覚えておくと、役立ちます。例えば、

int array[4] = {
    1, 2, 3, 4,
};

のように宣言された一次元配列は以下のようにメモリ上に配置されます。ただし、int型の配列arrayの先頭アドレスを0xBFF4AA20と仮定します。

アドレス(変数名) アドレス 値(変数名)
&array[0] = array 0xBFF4AA20 array[0] 1
&array[1] 0xBFF4AA24 array[1] 2
&array[2] 0xBFF4AA28 array[2] 3
&array[3] 0xBFF4AA2C array[3] 4

この配列はsizeof(int) = 4バイトごとに値が配置されていることがわかります。一般に、type型の配列arrayはsizeof(type)バイトごとに値が配置されます。したがって、sizeof(array)はsizeof(type)の要素数倍に等しくなります。次に、二次元配列について同様に見ていきます。

int array[3][4] = {
    {  1,  2,  3,  4, },
    {  5,  6,  7,  8, },
    {  9, 10, 11, 12, },
};

のように宣言された二次元配列は以下のようにメモリ上に配置されます。ただし、配列arrayの先頭アドレスを0xBFF4AA20と仮定します。

アドレス(変数名) アドレス 値(変数名)
&array[0][0] = array[0] = array 0xBFF4AA20 array[0][0] 1
&array[0][1] 0xBFF4AA24 array[0][1] 2
&array[0][2] 0xBFF4AA28 array[0][2] 3
&array[0][3] 0xBFF4AA2C array[0][3] 4
&array[1][0] = array[1] 0xBFF4AA30 array[1][0] 5
&array[1][1] 0xBFF4AA34 array[1][1] 6
&array[1][2] 0xBFF4AA38 array[1][2] 7
&array[1][3] 0xBFF4AA3C array[1][3] 8
&array[2][0] = array[2] 0xBFF4AA40 array[2][0] 9
&array[2][1] 0xBFF4AA44 array[2][1] 10
&array[2][2] 0xBFF4AA48 array[2][2] 11
&array[2][3] 0xBFF4AA4C array[2][3] 12

ここでC/C++における重要な規則が露呈してきます。二次元配列とよく似た挙動をする型にポインタの配列がありますが、array[0] = arrayとなるのはarrayが二次元配列の場合のみです。ポインタの配列だとこの規則は成立しません。一方、&array[0][0] = array[0]は二次元配列とポインタの配列の両者で成立します。これがわかっているかどうかがポインタと配列の違いをわかっているかどうかにつながり、ひいてはC/C++におけるポインタの本質的理解につながります。では、さらにしつこく三次元配列について同様に見ていきましょう。

int array[2][3][4] = {
    {
        {  1,  2,  3,  4, },
        {  5,  6,  7,  8, },
        {  9, 10, 11, 12, },
    },
    {
        { 13, 14, 15, 16, },
        { 17, 18, 19, 20, },
        { 21, 22, 23, 24, },
    },
};

のように宣言された三次元配列は以下のようにメモリ上に配置されます。ただし、配列arrayの先頭アドレスを0xBFF4AA20と仮定します。

アドレス(変数名) アドレス 値(変数名)
&array[0][0][0] = array[0][0] = array[0] = array 0xBFF4AA20 array[0][0][0] 1
&array[0][0][1] 0xBFF4AA24 array[0][0][1] 2
&array[0][0][2] 0xBFF4AA28 array[0][0][2] 3
&array[0][0][3] 0xBFF4AA2C array[0][0][3] 4
&array[0][1][0] = array[0][1] 0xBFF4AA30 array[0][1][0] 5
&array[0][1][1] 0xBFF4AA34 array[0][1][1] 6
&array[0][1][2] 0xBFF4AA38 array[0][1][2] 7
&array[0][1][3] 0xBFF4AA3C array[0][1][3] 8
&array[0][2][0] = array[0][2] 0xBFF4AA40 array[0][2][0] 9
&array[0][2][1] 0xBFF4AA44 array[0][2][1] 10
&array[0][2][2] 0xBFF4AA48 array[0][2][2] 11
&array[0][2][3] 0xBFF4AA4C array[0][2][3] 12
&array[1][0][0] = array[1][0] = array[1] 0xBFF4AA20 array[1][0][0] 13
&array[1][0][1] 0xBFF4AA24 array[1][0][1] 14
&array[1][0][2] 0xBFF4AA28 array[1][0][2] 15
&array[1][0][3] 0xBFF4AA2C array[1][0][3] 16
&array[1][1][0] = array[1][1] 0xBFF4AA30 array[1][1][0] 17
&array[1][1][1] 0xBFF4AA34 array[1][1][1] 18
&array[1][1][2] 0xBFF4AA38 array[1][1][2] 19
&array[1][1][3] 0xBFF4AA3C array[1][1][3] 20
&array[1][2][0] = array[1][2] 0xBFF4AA40 array[1][2][0] 21
&array[1][2][1] 0xBFF4AA44 array[1][2][1] 22
&array[1][2][2] 0xBFF4AA48 array[1][2][2] 23
&array[1][2][3] 0xBFF4AA4C array[1][2][3] 24

先と同様に、array[0][0] = array[0] = arrayという等式が成立しています。さらに言うと、私の環境では、array[0][0] = array[0] = array = &array[0][0] = &array[0] = &arrayさえも成立しています。この疑問を解消するためには、ポインタを真に理解する必要があります。重要な点は、配列として確保したメモリ上にアドレスを示す値はひとつも存在しないことです。これに関してはまた後日。
残念ながらC/C++以外のプログラミング言語における配列のメモリ上に配置規則については知らないです。たぶんC/C++と同じだと思いますが、確証がありません。ガベージコレクションが提供されている言語では違ったりするのかも。今度、調べてみようかな。