本物のC

C の詳細:配列とポインタ


「C の配列はポインタと同等」と言われる事があるが、

という辺りが正確な認識だろう。前者については「C の基本:配列」で説明されているので、ここでは後者について述べる。

コード例

#include <stdio.h>
int main(void)
{
int a[3] = { 11, 22, 33 };
int *b;
b = a;
printf("a[0] : %d\n", a[0]);
printf("b[0] : %d\n", b[0]);
printf("a : 0x%x, &a : 0x%x\n", a, &a);
printf("b : 0x%x, &b : 0x%x\n", b, &b);
return 0;
}

このコードを実行すると以下の様な出力が得られる。

a[0] : 11 b[0] : 11 a : 0x28ff24, &a : 0x28ff24 b : 0x28ff24, &b : 0x28ff20

添字によるアクセスに関してabは同様に振る舞うが、&a&bは違う値を与える。この様な挙動を理解する為には、規格の内容を正しく把握する必要がある。

配列の関わる型変換

まず上のコードを書いた時点で、メモリの一部に次の様な名前が付けられる。

メモリ
:
aのアドレス) b
:
int×3) a
:

b[0]という式は(*((b)+(0)))として定義される6.5.2.1#2+はこの場合ポインタ演算となり、(b)+(0)はやはりintへのポインタ型でbと同じ値を取る6.5.6#8

a[0]という式も(*((a)+(0)))として定義されるが、ここでaはまず配列そのものint[3]から配列の先頭要素へのポインタint *に変換され6.3.2.1#3、それから上と同じポインタ演算を受ける。

この変換の例外となるのがsizeofやアドレス演算子&のオペランドと、配列の初期化に用いられる文字列リテラルである6.3.2.1#3。この規定により&a配列へのポインタint (*)[3]となる。(int *[3]は「ポインタの配列」であって「配列へのポインタ」とは異なる。)

コード例で表示している式の型をまとめれば、次の様になる。

変数x xの型 &xの型
a int[3]int * int (*)[3]
b int * int **