11
int main()
{
    char arr[5][7][6];
    char (*p)[5][7][6] = &arr;
    printf("%d\n", (&arr + 1) - &arr);
    printf("%d\n", (char *)(&arr + 1) - (char *)&arr);
    printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr);
    printf("%d\n", (unsigned)(p + 1) - (unsigned)p);

    return 0;
}

上記のコードを実行すると、次の出力が得られます。

 1
 210 
 42
 210

出力1がすべての場合ではないのはなぜですか?

4

3 に答える 3

6

髪を分割したい場合: まず、コードはprintf()ステートメント全体で未定義の動作を呼び出します。2 つのポインターの違いは typeptrdiff_tであり、そのための正しい変換指定子は%td, not%dです。

残りは単なる憶測です。システムが合理的であり、数値的には、ポインターの値&arrは、変換される型に関係なく常に同じであると仮定しましょう。

(&arr + 1) - &arrもちろん、ポインター演算の規則に従って、は 1 です。(2 つのポインターの実際の違いは210 * sizeof(int)バイトですが、これは学校の数学ではなくポインター演算です。そのため、結果は size の単位で与えられます。sizeof(T)ここTで、 はポインターの基本型です。)

次に(char *)(&arr + 1) - (char *)&arr、ポインタを にキャストします。char *のサイズcharは 1 であるため、これは差をバイト単位で出力します。ここでは、ポインター演算を効果的にだまし/悪用しています。

さらに:printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr)タイプの 2 つのポインターを減算していますint (*)[7][6]。それがarr崩壊するものです。もちろん、7 * 6 = 42 なので、 と のサイズの違いarr + 1arr42 要素です。

pただし、は配列の最初の要素へのポインターではなく、配列自体へのポインターです。その型は として正しく示されint (*)[5][6][7]ます。ここで、その型を使用して違いを出力するが、ポインターがちょうど であることをだましてコンパイラーに除算をさせない場合、210 である がunsigned得られます。5 * 6 * 7

于 2013-07-16T14:02:50.253 に答える
5

2 次元の char 配列である最初の要素を指して&arrいるのに対し、完全な 3 次元の char 配列のアドレスであることに注意してください。arr以下の図のようなもの:

 0xbf8ce2c6
+------------------+     ◄-- arr  =  0xbf8ce2c6  
|    0xbf8ce2f0    |  
|   +------------------+     ◄-- arr + 1 = 0xbf8ce2f0
|   |   0xbf8ce31a |   |
|   |   +------------------+      ◄-- arr + 2 = 0xbf8ce31a 
|   |   0xbf8ce344 |   |   |
|   |   |   +------------------+      ◄-- arr + 3 = 0xbf8ce344
|   |   0xbf8ce36e |   |   |   |
|   |   |   |  +------------------+      ◄-- arr + 4 = 0xbf8ce36e
|   |   |   |  |   |   |   |   |  |
+---|---|---|--|---+   |   |   |  |  Each are 7*6, 2-Dimensional 
    |   |   |  |       |   |   |  |  Consists Of 42 bytes 
    +---|---|--|-------+   |   |  |  
        |   |  |           |   |  |
        +---|--|-----------+   |  |
            |  |               |  |
            +--|---------------+  |
               |                  |
               +------------------+

 The diagram show: 
 1. How a 3-dimensional can be interpreted as series of 2-dimensional arrays
 2. Here (arr + i) points to a 2-D array 
 3. Notice difference between: (arr + i + 1) - (arr + i) = 0x2a = 42, where i = [0, 4]

の型は&arrchar(*)[5][7][6]次元 の char 3D 配列のアドレスです[5][7][6]&arrとの値の違い&arr + 15 * 7 * 6 * sizeof(char)=210です。
のサイズですchar[5][7][6]ので5 * 7 * 6 * sizeof(char)
コードでは、3 次元配列と次の 3 次元配列 (コードには存在しません) を&arr指しています。&arry + 1

codepadeでこの作業コードを確認してください:

int main()
{
    char arr[5][7][6];
    printf(" &arr  : %p", &arr);
    printf(" &arr+1: %p", &arr + 1);

    return 0;
}

出力:

 &arr  : 0xbf5dd7de
 &arr+1: 0xbf5dd8b0

(&arr + 1) - (&arr)= 0xbf5dd8b0 - 0xbf5dd7de= 0xd2=の違い210

2 番目の printf で:

printf("%d\n", (char *)(&arr + 1) - (char *)&arr);

type のアドレスchar(*)[5][7][6]を plain(char*)に型キャストします。sizeofchar[5][7][6]210両方とも 210 far であるためです。(覚えておいてsizeof(char) == 1ください)。これが出力の理由です。210

最初のステートメントで述べたようにarr、文字の 2 次元配列である最初の要素のアドレスです。のタイプはarrですchar(*)[7][6]。これで 1 つの要素 (サイズの 2 次元配列は6 * 7 * sizeof(char) = 42) になります。
(注: 3 次元配列は、各要素が 2 次元配列である 1 次元配列と考えることができます)。

3 番目の printf で:

printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr);

符号なしの値に型キャストします (ただし、アドレス/ポインター型には型キャストしません)。arr + 1との違いarr42 * sizeof(char)= 42(つまり のサイズに等しいchar[7][6]) です。したがって、printf ステートメントの出力は次のとおり42です。

注: sizeof (int) == sizeof (void*)? を読む必要があります。、アドレスを値に型キャストしているためです。この変換は完全には定義されていません。(私の説明は、あなたの出力と私が与えた出力についてです)。

さらに明確にするために、コードパッドの作業コードの下を確認してください。

int main()
{
    char arr[5][7][6];
    printf(" arr  : %p\n", arr);
    printf(" arr+1: %p", arr + 1);

    return 0;
}

出力は次のとおりです。

 arr  : 0xbf48367e
 arr+1: 0xbf4836a8

(arr + 1) - (arr)= 0xbf4836a8- 0xbf48367e= 0x2a=の差を取る42

最後の印刷:

 printf("%d\n", (unsigned)(p + 1) - (unsigned)p);

&arr+1&arr=の差を取るだけです ( 2102 番目の printf と同様)。そして、それを値型(ポインター型ではなく)に型キャストしています。p&arr

さらに、(目的を理解するために追加するだけで、読者が役立つと思います)、

概念をより深く理解するのに役立つ sizeof 演算子の使用arrとの違いをもう 1 つ学びましょう。&arrこの最初の読み取り:sizeofオペレーター

演算子を配列識別子に適用するsizeofと、結果は、配列識別子によって表されるポインターのサイズではなく、配列全体のサイズになります。

codepadeでこの作業コードを確認してください:

int main()
{
    char arr[5][7][6];
    printf(" Sizeof(&arr)  : %lu and value &arr: %p\n", sizeof(&arr), &arr);
    printf(" Sizeof(arr)   : %lu and value arr : %p\n", sizeof(arr), arr);
    printf(" Sizeof(arr[0]): %lu and value a[0]: %p\n",sizeof(arr[0]), arr[0]);
    return 0;
}

その出力:

Sizeof(&arr)  : 4 and value &arr: 0xbf4d9eda
Sizeof(arr)   : 210 and value arr : 0xbf4d9eda
Sizeof(arr[0]): 42 and value a[0]: 0xbf4d9eda
  • これ&arrは単なるアドレスで、システムではアドレスは 4 バイトで、これは完全な 3 次元の char 配列のアドレスです。

  • arrは 3 次元配列の名前であり、sizeof演算子は である配列の合計サイズを示します210 = 5 * 7 * 6 * sizeof(char)

私の図に示したようにarr、2 次元配列である最初の要素を指しています。だからarr= (arr + 0)。現在、*逆参照演算子 atを使用(arr + 0)すると、アドレス so で値が得られます*(arr + 0) = arr[0]

  • 注意sizeof(arr[0])42=を与え7 * 6 * sizeof(char)ます。そしてこれは、3 次元配列が 2 次元配列の配列であることを概念的に証明しています。

上記の私の回答では、「サイズchar[5][7][6]5 * 7 * 6 * sizeof(char)です」のように何度も書いたからです。だから私は @ codepadeの下に興味深いコードを追加しています:

int main(){
 printf(" Char         : %lu \n", sizeof(char));
 printf(" Char[5]      : %lu \n", sizeof(char[6]));
 printf(" Char[5][7]   : %lu \n", sizeof(char[7][6]));
 printf(" Char[5][7][6]: %lu \n", sizeof(char[5][7][6]));

 return 1;
}

出力:

 Char         : 1 
 Char[5]      : 6 
 Char[5][7]   : 42 
 Char[5][7][6]: 210 
于 2013-07-16T14:24:25.913 に答える
5

(&arr + 1) - &arr

&arr6 の 7 つの配列の 5 つの配列の配列のアドレスですchar。1つを追加すると、1つではなくこれらのオブジェクトの配列があった場合、6つの配列の7つの配列の5つの配列の次の配列がどこにあるかのアドレスが生成されます。char元の住所 を引くと&arr、2 つの住所の差が生じます。C 標準では、この違いは 2 つのアドレス間の要素の数として表されます。要素の型は、指しているオブジェクトの型です。その型は 5 配列の 7 配列の 6 配列であるcharため、2 つのアドレス間の距離は 1 要素です。つまり、 から&arrまでの距離(&arr + 1)は、 5 配列 7 配列 6 の1char配列です。

(char *)(&arr + 1) - (char *)&arr

&arr + 16 の 7 つの配列の 5 つの配列の次の配列がどこにあるかへのポインタcharです。に変換されるchar *と、結果は次の配列の最初のバイトへのポインタになります。同様に(char *)&arr、最初の配列の最初のバイトへのポインタです。次に、2 つのポインターを減算すると、要素の違いが得られます。これらのポインタは へのポインタであるためchar、差は の数として生成されcharます。したがって、違いは、5 つの配列の 7 つの配列の 6 の配列のバイト数、charつまり 5•7•6charまたは 210charです。

(unsigned)(arr + 1) - (unsigned)arr

は(またはその他の例外的なケースで)arr使用されないため、6 の配列 7 の配列 5 の配列から最初の要素へのポインターに自動的に変換されます。したがって、これは 6 の 7 つの配列の配列へのポインターです。次に、6 の 7 つの配列の次の配列へのポインターです。このアドレスが に変換されると、使用している C 実装では、結果は実際にはオブジェクトのメモリ アドレスになります。(これは一般的ですが、C 標準では保証されておらず、アドレスが 64 ビットであるのに32 ビットの場合、確実に壊れます。) 同様に、&sizeofcharchararr + 1charunsignedunsigned(unsigned)arr最初のオブジェクトのアドレスです。アドレスが減算されると、結果はアドレス間の距離 (バイト単位) になります。したがって、違いは 6 の 7 つの配列の配列のバイト数でありchar、これは 7•6 バイト、つまり 42 バイトです。この場合の重要な違いに注意&arrしてcharください。arrchar

(unsigned)(p + 1) - (unsigned)p

p6 の 7 つの配列の 5 つの配列の配列へのポインタcharです。次にp+1、次の配列がある場所へのポインターです。に変換するunsignedと、上記のように動作します。減算すると、バイトの差が得られるため、6 の 7 つの配列の 5 つの配列の配列のサイズであるcharため、再び 210 バイトになります。

余談として:

のタイプは で(&arr + 1) - &arrありptrdiff_t、 で出力する必要があり%tdます。

のタイプは で(char *)(&arr + 1) - (char *)&arrありptrdiff_t、 で出力する必要があり%tdます。

のタイプは で(unsigned)(arr + 1) - (unsigned)arrありunsigned int、 で出力する必要があり%uます。

のタイプは で(unsigned)(p + 1) - (unsigned)pありunsigned int、 で出力する必要があり%uます。

于 2013-07-16T14:19:08.067 に答える