8

このコード サンプルは、配列を正しく出力します。

int b[2] = {1, 2};
int *c = &b;
int  i, j,k = 0;
for (i = 0;i < 2; i++) {
    printf("%d ", *(c+i));
}

これは2つのガベージ値を出力します。

int b[2] = {1, 2};
int  i, j,k = 0;
for (i = 0;i < 2; i++) {
    printf("%d ", *(&b+i));
}

2 つのコード サンプルの動作が異なるのはなぜですか?

4

4 に答える 4

9

宣言:

int b[2] = {1, 2};

int1,を持つ2 つの配列を作成します2
int のシステム サイズが 4 バイトであると仮定すると、配列b[]は次のようにメモリに格納する必要があります。

first ele        +----------+                
     (b + 0) ---►|     1    | 0xbf5c787c  <----- &b ,    (c + 0)
next ele         +----------+ 
     (b + 1) ---►|     2    | 0xbf5c7880  <------------- (c + 1)
                 +----------+              
     (b + 2) ---►|     ?    | 0xbf5c7884  <----- (&b + 1) next array  
                 +----------+                    
             ---►|     ?    | 0xbf5c7888  
                 +----------+ 
             ---►|     ?    | 0xbf5c788c  <----- (&b + 2) next array  
                 +----------+      
             ---►|     ?    | 0xbf5c7890
                 +----------+               

? means garbage value
b[] array in memory from 0xbf5c787c to 0xbf5c7880  
each cell is four bytes 

上の図では、値を持つメモリセル? はガベージ値を意味し、割り当てられていません (メモリからのメモリ0xbf5c7884は配列に割り当てられていません)。値は12メモリ内のアドレス0xbf5c787cおよび0xbf5c7880に格納され、配列 に割り当てられb[]ます。

値を出力する代わりに、(c + i)andを使用してコードでアクセスするメモリのアドレスを出力します。(&b + i)このために、次のプログラムを検討 してください。

#include<stdio.h>
int main(){
  int b[2] = {1, 2}; 
  int  i = 0;
  int *c = &b; //Give warning: "assignment from incompatible pointer type" 
  printf("\n C address: ");  // outputs correct values 
  for (i = 0; i < 2; i++) {
    printf("%p ", (void*)(c + i));
  }
  printf("\n B address: ");  // outputs incorrect values/ and behaving differently 
  for (i = 0; i < 2; i++) {
    printf("%p ", (void*)(&b + i));  // Undefined behavior 
  }
  return 1;
}

出力:

 C address: 0xbf5c787c 0xbf5c7880 
 B address: 0xbf5c787c 0xbf5c7884 

このコードが機能していることを確認してください @ Codepade
通知、(c + i)セルの正しいアドレスを value12出力するため、最初のコードの出力は正しいです。配列に割り当てられていない(配列の外側にある)(&b + i)アドレス値を出力しますが、このメモリにアクセスすると、実行時に未定義の動作(予測不可能)が発生します。b[]b[]

実際には と の間に違いがbあり&bます。

  • bは配列であり、その型はint[2]であり、ほとんどの式で最初の要素のアドレスとしてb崩壊しint*ます (読み取り:配列名が最初の要素へのポインタに崩壊しないいくつかの例外? )。そして、配列内b + 1の次の要素を指します (図に注意してください)。int

  • &bは完全な配列のアドレスであり、その型はint(*)[2]で あり、プログラムで割り当てられていない(&b + 1)型の次の配列を指しint[2]ます (図のどこが指しているかに注意してください(&b + 1))。

とのその他の興味深い違いを知るb&bは: What does sizeof(&array)return?

最初のコード スナイプでは、 を実行すると、配列のアドレスが(この例では )c = &bに割り当てられます。GCC コンパイラを使用すると、このステートメントは「互換性のないポインター型からの代入」という警告を出します。 は へのポインタであるため 、アドレス に格納されている整数を出力します。 値が配列の 2 番目の要素 (この例では)を指しているため 、正しく 出力されます。int*0xbf5c787c
cint*(c + i)(c + i)i = 1(c + 1)0xbf5c7880*(c + 1)2

最初のコードでの割り当てについては、以下int *c = &b;の @AndreyT の回答を読むことを強くお勧めします。ポインターを使用して配列要素にアクセスする正しく簡単な方法は次のとおりです。

int b[2] = {1, 2};
int *c = b;   // removed &, `c` is pointer to int  
int i;
for (i = 0; i < 2; i++){
    printf("%d ", *(c + i)); 
 // printf("%d ", c[i]); // is also correct statement 
}

2番目のコードでは、割り当てられたメモリの外側を指すように追加iし、printfステートメントでは逆参照演算子を使用してメモリにアクセスすると、無効なメモリアクセスが発生し、実行時のこのコードの動作はUndefinedです。これが、2 番目のコードが異なる実行で異なる動作をする理由です。 &b*

コードは構文的に正しいためコンパイルされますが、実行時に未割り当てメモリへのアクセスが OS カーネルによって検出される可能性があります。これにより、OS カーネルが、例外の原因となったプロセスにシグナル コア ダンプを送信する可能性があります (興味深い点: OS がプロセスによるメモリ権限違反を検出すると、有効なメモリへの無効なアクセスは SIGSEGV を返し、無効なアドレスへのアクセスは次を返します)。 : SIGBUS)。場合によっては、プログラムが失敗せずに実行され、ガベージ結果が生成される場合があります。

2番目のコードに関して、「配列へのポインター」を使用して配列を印刷する正しい方法は次のとおりです。

#include<stdio.h>
int main(){
  int b[2] = {1, 2}; 
  int  i;
  int (*c)[2] = &b;   // `c` is pointer to int array of size 2
  for(i = 0; i < 2; i++){
     printf(" b[%d] = (*c)[%d] = %d\n", i, i, (*c)[i]); // notice (*c)[i]
  }
  return 1;
}

出力:

b[0] = (*c)[0] = 1
b[1] = (*c)[1] = 2  

@codepadeをチェックしてください。*c演算子の優先順位が逆参照演算子[]よりも高いため、括弧で囲む必要があることに注意して* ください (一方、int へのポインターを使用する場合は、上記のコードのように括弧は必要ありません)。

于 2013-07-15T20:16:26.603 に答える
5

これは、ポインター算術演算ptr+iが適用されるポインター型によるものです。

  • i最初のケースでは、へのポインターを追加しますint。これは、配列のインデックス付けと同じです。配列へのポインターは最初の要素へのポインターと同じであるため、コードは機能します。
  • i2 番目のケースでは、2 つの の配列へのポインタを追加しますint。したがって、追加により、割り当てられたメモリを超えてしまい、未定義の動作が発生します。

この点を簡単に説明すると、次のようになります。

int b[2] = {1,2};
printf("%p\n%p\n%p\n", (void*)&b, (void*)(&b+1), (void*)(&b+2));

32 ビットints のシステムでは、これは 8 バイトで区切られたアドレスを出力します - サイズint[2]:

0xbfbd2e58
0xbfbd2e60
0xbfbd2e68
于 2013-07-15T18:23:23.260 に答える
1
int *c = &b;     

これは実際には有効ではありません。キャストが必要です。

int *c = (int *) &b; 

2 つの式:

 *(c+i)

 and 

 *(&b+i)

同じではありません。最初の式iでは a に追加さint *れ、2 番目の式iでは a に追加されint (*)[2]ます。をint *c = (int *) &b;に変換しint (*)[2]ますint *c + ii- 番目のint要素を指しますcが、ポインター値を実際の配列オブジェクトの外に移動する要素を指します。&b+iint [2]b

于 2013-07-15T18:22:14.683 に答える