9

次のCプログラムがあります。

#include <stdio.h>

int main(){
    int a[2][2] = {1, 2, 3, 4};
    printf("a:%p, &a:%p, *a:%p \n", a, &a, *a);
    printf("a[0]:%p, &a[0]:%p \n", a[0], &a[0]);
    printf("&a[0][0]:%p \n", &a[0][0]);
    return 0;
}

次の出力が得られます。

a:0028FEAC, &a:0028FEAC, *a:0028FEAC
a[0]:0028FEAC, &a[0]:0028FEAC
&a[0][0]:0028FEAC

&aa*a- がすべて同一である理由を理解できません。a[0]、 、&a[0]についても同様です&a[0][0]

編集:

回答のおかげで、これらの値が等しくなる理由がわかりました。Kernighan & Ritchieの本の次の行が、私の質問の鍵であることが判明しました。

 the name of an array is a synonym for the location of the initial element.

したがって、これにより、

a= &a[0]、および

a[0]= &a[0][0] (a配列の配列と考える)

直感的に、出力の背後にある理由は明らかです。aしかし、ポインタが C でどのように実装されているかを考えると、とがどのよう&aに等しいか理解できません。メモリ内に配列を指す変数があると仮定していますa(この配列メモリ ブロックの開始アドレスは、この変数の値になりますa)。

しかし、それは、変数が格納され&aたメモリ位置のアドレスを取得することを意味しませんか? aなぜこれらの値は等しいのでしょうか?

4

8 に答える 8

17

それらは同一のポインターではありません。これらは、すべて同じメモリ位置を指す個別の型のポインターです。同じ値 (並べ替え)、異なる型。

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]およびintobjectの先頭でもあります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
  • のオペランドの場合は、ポインターのサイズではなく、配列オブジェクトのバイト単位のサイズが得られますsizeofsizeof arr
  • 配列 (サブ) オブジェクトを初期化するために使用される初期化子の文字列リテラルの場合、無意味に配列オブジェクトをポインター値で初期化するのではなくchar s[6] = "hello";、配列値をコピーします。sこの最後の例外は、質問しているコードには適用されません。

(2011 ISO C 標準のN1570_Alignofドラフトでは、4 番目の例外であると誤って述べられています。_Alignof式ではなく、括弧で囲まれた型名にのみ適用できるため、これは正しくありません。エラーは、最終的な C11 標準で修正されています。)

推奨される読み物: comp.lang.c FAQのセクション 6 。

于 2013-08-21T18:04:11.543 に答える
6

すべての式が配列の先頭を指しているため:

a = {{a00},{a01},{a10},{a11}}

a配列であるという理由だけで配列を指しているので、a == &a[0]

&a[0][0]2D 配列の最初のセルに配置されます。

于 2013-08-21T15:11:52.260 に答える
5
 +------------------------------+
 | a[0][0]   <--   a[0] <--   a | // <--&a, a,*a, &a[0],&a[0][0] 
 |_a[0][1]_                     |
 | a[1][0]   <--   a[1]         |
 | a[1][1]                      |
 +------------------------------+
于 2013-08-21T15:15:36.763 に答える
3

C の 2D 配列は、要素が 1D 配列 (行) である 1D 配列として扱われます。
たとえば、 の 4x3 配列T(「T」は何らかのデータ型) は次のように宣言でき T a[4][3]、次のスキームで記述できます。

                       +-----+-----+-----+
  a ==     a[0]   ---> | a00 | a01 | a02 |
                       +-----+-----+-----+
                       +-----+-----+-----+
           a[1]   ---> | a10 | a11 | a12 |
                       +-----+-----+-----+
                       +-----+-----+-----+
           a[2]   ---> | a20 | a21 | a22 |
                       +-----+-----+-----+
                       +-----+-----+-----+
           a[3]   ---> | a30 | a31 | a32 |
                       +-----+-----+-----+

また、配列要素は行ごとにメモリに格納されます。
を先頭にT追加し[3]てを追加すると、タイプ の要素のa配列が得られます。ただし、名前自体は配列であり、それぞれが要素の配列である要素があることを示しています。したがって、それぞれ要素の配列の配列があります。が の最初の要素 ( )を指して いることは明らかです。一方、の最初の要素 ( ) のアドレスを指定し、行(の 配列のアドレス) を指定します。は 2D 配列 のアドレスを指定します。3Ta[4]4343
aa[0]a[4]&a[0]a[0]a[4]&a[0][0]0tha00 | a01 | a02)a[4][3]&aa[3][4]*aへのポインタに減衰しますa[0][0]。へのポインタではないことに
注意してください。へのポインタです。 したがって aa[0][0]a[0]

  • G1:a&a[0]は同等です。
  • G2: *aa[0]&a[0][0]は同等です。
  • G3: &a(2D 配列のアドレスを与えるa[3][4])。
    しかし、グループG1G2、およびG3は、同じ結果が得られますが、同一ではありません(なぜ同じ結果が得られるのかを上で説明しました)。
于 2013-08-21T15:33:00.703 に答える
3

aは配列の最初の要素のアドレスであり、C 標準によれば、a[X]と等しいことがわかっています*(a + X)

そう:

&a[0] == aは= =&a[0]と同じだからです。&(*(a + 0))&(*a)a

&a[0][0] == aは= =&a[0][0]と同じだから&(*(*(a + 0) + 0)))&(*a)a

于 2013-08-21T15:14:42.330 に答える
2

これは、C では配列にオーバーヘッドがないことも意味します。他のいくつかの言語では、配列の構造は次のとおりです。

&a     -->  overhead
            more overhead
&a[0]  -->  element 0
            element 1
            element 2
            ...

&a != &a[0]

于 2013-08-21T15:18:12.777 に答える
2

直感的に、出力の背後にある理由は明らかです。しかし、ポインタが C でどのように実装されているかを考えると、a と &a がどのように等しいのか理解できません。メモリ内に配列を指す変数 a があると仮定しています (この配列メモリ ブロックの開始アドレスは、この変数 a の値になります)。

うーん、ダメ。メモリのどこかに格納されているアドレスのようなものはありません。生データに割り当てられるメモリのみがあり、それだけです。ネイキッドを使用すると、aすぐに最初の要素へのポインターに崩壊aし、の「値」がアドレスであるという印象を与えますが、の唯一の値aは生の配列ストレージです。

実際のところ、a&aは異なりますが、値ではなく型のみです。この点を明確にするために、1D 配列を使用して少し簡単にしましょう。

bool foo(int (*a)[2]) {    //a function expecting a pointer to an array of two elements
    return (*a)[0] == (*a)[1];    //a pointer to an array needs to be dereferenced to access its elements
}
bool bar(int (*a)[3]);    //a function expecting a pointer to an array of three elements
bool baz(int *a) {    //a function expecting a pointer to an integer, which is typically used to access arrays.
    return a[0] == a[1];    //this uses pointer arithmetic to access the elements
}

int z[2];
assert((size_t)z == (size_t)&z);    //the value of both is the address of the first element.
foo(&z);     //This works, we pass a pointer to an array of two elements.
//bar(&z);   //Error, bar expects a pointer to an array of three elements.
//baz(&z);   //Error, baz expects a pointer to an int

//foo(z);    //Error, foo expects a pointer to an array
//bar(z);    //Error, bar expects a pointer to an array
baz(z);      //Ok, the name of an array easily decays into a pointer to its first element.

ご覧のとおり、同じ値を共有していても、動作が大きく異なりますa&a

于 2013-09-14T15:40:24.573 に答える