7

以下の2行で、

char a[5]={1,2,3,4,5};
char *ptr=(char *)(&a+1);
printf("%d",*(ptr-1));

これにより、画面に 5 が出力されます。一方、&a の代わりに a を使用すると、

char a[5]={1,2,3,4,5};
char *ptr=(char *)(a+1);
printf("%d",*(ptr-1));

これは1を出力します

a も &a も配列の先頭アドレスです。では、なぜこの違いがあるのでしょうか?

char *ptr=&a+1; も

警告を示します。

4

5 に答える 5

12

配列はポインタではありません! 詳細については、comp.lang.c FAQ のセクション 6 を参照してください。

最初に 2 番目のケースを見てみましょう。これは、より「通常の」慣用的な外観であるためです。1行ずつ:

  1. a5 つcharの要素を含む配列を宣言します。
  2. 配列の名前 ( a) は、このコンテキストの最初の要素へのポインターに分解されます。それに追加1し、結果を に代入しptrます。 ptrを指し2ます。キャストは必要ありませんが、キャストは必要です。
  3. から減算1ptr、逆参照して出力します。したがって、1.

ここで、最初のケースに対処しましょう。もう一度行ごとに説明します。

  1. a5 つcharの要素を含む配列を宣言します。
  2. のアドレスを取得し、型ポインタaを生成します。char (*)[5]次に、このポインターに追加1します。ポインター演算のために、この新しいポインターはメモリ内の直後のバイトに渡さ5れます。次に、型キャスト (今回は必須) し、この値を に割り当てますptr
  3. from から減算1してからptr、dreference して出力します。 ptrは であるchar *ため、この減算は単にポインタを「の末尾の 1 つ後」から 1 つ戻してa、 の最後の要素を指すようにしaます。したがって、 が得られます5

最後に、char *ptr=&a+1;警告が表示される理由は、C ではポインター型間の変換を明示的にキャストする必要があるためです。上記のように、はではなく&a型であるため、その値を変数に代入するには、明示的なキャストが必要です。char (*)[5] char *char *

于 2013-05-01T03:28:43.383 に答える
10

まったく初めてのようですので、厳密な説明ではなく、簡単な言葉で説明させてください。

ご覧のとおり、上記のプログラムでは、a&aは同じ数値を持ちます。混乱の原因はそこにあると思います。それらが同じである場合、次のアドレスは両方の場合の後に次のアドレスを与える必要があるのではないかと思うかもしれません。aポインター演算:

(&a+1) and (a+1)

でもそうじゃない!!配列のベースアドレス(aここ) と配列のアドレスは同じではありません! a数値的&aには同じかもしれませんが、同じタイプではありません。aは型char*&aあり、型は ですchar (*)[5]。つまり、&aは (のアドレス) へのポインタであり、サイズ 5 の配列です。しかしa、ご存知のように、 は配列の最初の要素のアドレスです。数値的には、下の^を使ったイラスト。

しかし、これら 2 つのポインタ/アドレスをインクリメントすると、つまり(a+1)とのよう(&a+1)に、算術演算がまったく異なります。最初のケースでは、配列内の次の要素のアドレスに「ジャンプ」しますが、後者の場合は、次のように 5 要素ずつジャンプします。それは、5 つの要素の配列のサイズです! .わかった?

  1 2 3 4 5  
  ^               // ^ stands at &a

  1 2 3 4 5
           ^     // ^ stands at (&a+1)

  1 2 3 4 5
  ^              //^ stands at a

  1 2 3 4 5
    ^            // ^ stands at (a+1)

以下は、以下のようにサイズを明示的に指定しないと、(&a+1) のようなものが発生したときに、プログラムが「ジャンプ」する要素の数がわからないことを意味するため、配列の未指定の境界に関するエラーが発生します。

char a[]={1,2,3,4,5};
char *ptr=(char *)(&a+1);  //(&a+1) gives error as array size not specified.

次に、ポインタ/アドレスを(ptr-1)次のようにデクリメントする部分に進みます。最初のケースでは、デクリメント部分に到達する前に、型にキャストされる上記のステートメントで何が起こるかを知っておく必要がありますchar*

char *ptr=(char *)(&a+1);

ここで何が起こるかというと、元type(&a+1)型を「剥ぎ取り」、それを配列のベースアドレスと同じchar (*)[5]型にキャストすることです。(配列のベースアドレスの違いに注意してください。と配列のアドレス .したがって、上記のステートメントのキャストと代入の後、のデクリメントが続き、配列の最後の要素の直後のメモリ位置が得られます。char*aprintf()ptr5

  1 2 3 4 5
          ^     // ^ stands at location of 5, so *ptr gives 5

したがって、ポインターptrをデクリメントした後にポインターを逆参照すると、期待どおり*(ptr-1)の値が出力5されます。

最後に、 が印刷されている 2 番目のケースと比較してください。記号^1を使用して示した図を見てください。としてインクリメントした場合、配列の 2 番目の要素を指します。つまり、このアドレスを に割り当てました 。したがって、2番目のケースで逆参照すると、 が得られます。aa+12ptrptr(ptr-1)1ptr1

  1 2 3 4 5
  ^            // ^ stands at address of 1, so *ptr gives 1

これですべてが明確になったことを願っています。

于 2013-05-01T08:19:15.863 に答える
2

違いは、取得するポインターの型にあります。

  • 配列名a自体は、配列の最初の要素へのポインターを表します。式などでそのように解釈された場合a+1、ポインターは単一の文字を指していると見なされます。
  • &a一方、を取得すると、ポインターは 5 文字の配列を指します。

整数をポインターに追加する場合、ポインターが移動するバイト数は、ポインターが指すオブジェクト ポインターの型によって決まります。ポインタが を指している場合char、追加するとポインタがバイト単位Nで進みます。Nポインターが 5 つの の配列を指している場合char、追加するとポインターがバイト単位Nで進みます。5*N

それがまさにあなたが得ている違いです。最初の例では、ポインターを配列の末尾を1つ過ぎた要素に進め(これは正当です)、最後の要素に戻します。一方、2 番目の例では、ポインターを 2 番目の要素に進めてから、配列の最初の要素を指すように戻します。

于 2013-05-01T03:36:03.650 に答える
0

あなたが遭遇しているのは、微妙なポインター演算です。

コンパイラは、"a" を char (サイズが 1 バイトのエンティティ) へのポインタとして扱います。これに 1 を追加すると、エンティティのサイズ (つまり 1) だけインクリメントされるポインターが生成されます。

コンパイラは、「&a」を char の配列 (サイズが 5 バイトのエンティティー) へのポインターとして扱います。これに 1 を追加すると、エンティティのサイズ (つまり 5) だけインクリメントされるポインターが生成されます。

これがポインタ演算の仕組みです。ポインターに 1 を追加すると、ポインターである型のサイズだけインクリメントされます。

もちろん、面白いことに、「a」または「&a」の値を評価する場合、逆参照すると、両方とも同じアドレスに評価されます。それが、あなたが行う価値を見る理由です。

于 2013-05-01T03:56:04.503 に答える
0

配列は、最初の要素へのポインターに「崩壊」します。したがって、a のアドレスを取得すると、5 文字の配列へのポインタが得られます。これは、a を宣言するようなものchar[][5]です。そして、このポインターをインクリメントすると、配列の次の要素に進みますchar[][5]。つまり、一度に 5 文字です。char[5]これは、配列から減衰するポインターをインクリメントすること (つまり、一度に 1 文字ずつ) とは異なります。

于 2013-05-01T03:32:19.643 に答える