配列
アラインメントを明示的に指定しない限り、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++と同じだと思いますが、確証がありません。ガベージコレクションが提供されている言語では違ったりするのかも。今度、調べてみようかな。