それらは同一のポインターではありません。これらは、すべて同じメモリ位置を指す個別の型のポインターです。同じ値 (並べ替え)、異なる型。
C の 2 次元配列は、配列の配列に他なりません。
オブジェクトa
のタイプint[2][2]
は 、または の 2 要素配列の 2 要素配列ですint
。
配列型の式は、すべてではありませんがほとんどのコンテキストで、配列オブジェクトの最初の要素へのポインターに暗黙的に変換 (「減衰」) されます。したがって、式 a
は、単項&
orのオペランドでない限りsizeof
、タイプであり、 (またはそれがより明確な場合)int(*)[2]
と同等です。2次元配列の0行目へのポインタになります。これはポインターオブジェクトではなく、ポインター値(または同等のアドレス) であることを覚えておくことが重要です。明示的に作成しない限り、ここにはポインター オブジェクトはありません。&a[0]
&(a[0])
それで、あなたが尋ねたいくつかの表現を見てください:
&a
配列オブジェクト全体のアドレスです。type のポインター式ですint(*)[2][2]
。
a
配列の名前です。上で説明したように、配列オブジェクトの最初の要素 (行) へのポインターに "減衰" します。型のポインタ式ですint(*)[2]
。
*a
ポインター式を逆参照a
します。a
(減衰後) は 2 の配列へのポインターであるため、 は 2int
の配列*a
ですint
。これは配列型であるため、配列オブジェクトの最初の要素へのポインターに (すべてではなくほとんどのコンテキストで) 減衰します。だからそれはタイプint*
です。*a
と同等&a[0][0]
です。
&a[0]
配列オブジェクトの最初 (0 番目) の行のアドレスです。タイプint(*)[2]
です。a[0]
配列オブジェクトです。unary の直接オペランドであるため、ポインターに減衰しません&
。
&a[0][0]
配列オブジェクトの行 0 の要素 0 のアドレスです。タイプint*
です。
これらのポインタ式はすべて、メモリ内の同じ場所を参照します。その位置は、配列オブジェクトの先頭a
です。また、配列 objecta[0]
およびint
objectの先頭でもありますa[0][0]
。
ポインター値を出力する正しい方法は、"%p"
形式をvoid*
使用してポインター値を次のように変換することです。
printf("&a = %p\n", (void*)&a);
printf("a = %p\n", (void*)a);
printf("*a = %p\n", (void*)*a);
/* and so forth */
この への変換はvoid*
、メモリ内の場所のみを指定する「生の」アドレスを生成し、その場所にあるオブジェクトのタイプは指定しません。したがって、同じメモリ位置から始まるオブジェクトを指す異なる型の複数のポインターがある場合、それらをすべて変換するvoid*
と同じ値が得られます。
(私は[]
インデックス演算子の内部の仕組みについて詳しく説明しました。式x[y]
は、定義により と同等です。*(x+y)
ここで、x
はポインター (おそらく配列の暗黙的な変換の結果) でy
あり、整数です。またはその逆ですが、それは醜いです;arr[0]
と0[arr]
は同等ですが、意図的に難読化されたコードを書いている場合にのみ役立ちます. その同等性を考慮すると、a[0][0]
意味を説明するのに1段落ほどかかります.この答えはおそらくすでに長すぎます.)
完全を期すために、配列型の式が配列の最初の要素へのポインターに暗黙的に変換されない3 つのコンテキストを次に示します。
- unary のオペランドの場合、配列オブジェクト全体のアドレスが得られます
&
。&arr
- のオペランドの場合は、ポインターのサイズではなく、配列オブジェクトのバイト単位のサイズが得られます
sizeof
。sizeof arr
と
- 配列 (サブ) オブジェクトを初期化するために使用される初期化子の文字列リテラルの場合、無意味に配列オブジェクトをポインター値で初期化するのではなく
char s[6] = "hello";
、配列値をコピーします。s
この最後の例外は、質問しているコードには適用されません。
(2011 ISO C 標準のN1570_Alignof
ドラフトでは、4 番目の例外であると誤って述べられています。_Alignof
式ではなく、括弧で囲まれた型名にのみ適用できるため、これは正しくありません。エラーは、最終的な C11 標準で修正されています。)
推奨される読み物: comp.lang.c FAQのセクション 6 。