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
添字によるアクセスに関してaとbは同様に振る舞うが、&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 ** |