なぜ配列のアドレスがCの値と等しいのですか?


189

次のコードビットでは、ポインタ値とポインタアドレスが予想どおりに異なります。

しかし、配列の値とアドレスはそうではありません!

どうすればいいの?

出力

my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>

int main()
{
  char my_array[100] = "some cool string";
  printf("my_array = %p\n", my_array);
  printf("&my_array = %p\n", &my_array);

  char *pointer_to_array = my_array;
  printf("pointer_to_array = %p\n", pointer_to_array);
  printf("&pointer_to_array = %p\n", &pointer_to_array);

  printf("Press ENTER to continue...\n");
  getchar();
  return 0;
}

comp.lang.c FAQから:-[Cの「ポインタと配列の同等性」とはどういう意味ですか?](c-faq.com/aryptr/aryptrequiv.html)-[配列はポインタへの減衰を参照するため、arrが配列の場合、arrと&arrの違いは何ですか?](c-faq.com/aryptr/aryvsadr.html)または、配列とポインタのセクション全体を読んでください。
jamesdlin

3
私はこの質問に2年前にこの質問に図表付きの回答を追加しました何がsizeof(&array)戻りますか?
Grijesh Chauhan

回答:


212

配列の名前は、通常、配列の最初の要素のアドレスを評価し、これarray&array同じ値(ただし、異なるタイプを持っているので、array+1そして&array+1意志はありません配列が1つの以上の要素が長い場合に等しくなります)。

これには2つの例外があります。配列名がオペランドsizeofまたは単項&(address-of)の場合、名前は配列オブジェクト自体を参照します。したがってsizeof array、ポインタのサイズではなく、配列全体のバイト単位のサイズがわかります。

として定義されている配列の場合、T array[size]タイプはT *ます。インクリメントすると、配列の次の要素に移動します。

&array同じアドレスに評価されますが、同じ定義が与えられると、型のポインタを作成します。T(*)[size]つまり、単一の要素ではなく配列へのポインタです。このポインタをインクリメントすると、単一の要素のサイズではなく、配列全体のサイズが追加されます。たとえば、次のようなコードでは:

char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));

2番目のポインターは最初のポインターよりも16大きいことが期待できます(これは16文字の配列であるため)。%pは通常、ポインタを16進数に変換するため、次のようになります。

0x12341000    0x12341010

3
@Alexandre:&arrayは配列の最初の要素へのポインタであり、as arrayは配列全体を指します。基本的な違いはsizeof(array)、とを比較することでも確認できsizeof(&array)ます。ただしarray、関数に引数として渡す場合&arrayは、実際には渡されるだけであることに注意してください。でカプセル化されない限り、配列を値で渡すことはできませんstruct
クリフォード

14
@Clifford:配列を関数に渡すと、最初の要素へのポインターに減衰するため、配列へのポインターでは&array[0]なく、効果的に渡され&arrayます。それは簡単なことかもしれませんが、明確にすることが重要だと思います。関数は、渡されたポインタの型と一致したプロトタイプを持っている場合、コンパイラは警告が表示されます。
CBベイリー

2
@Jerry Coffinたとえば、int * p =&a、intポインタpのメモリアドレスが必要な場合、&pを実行できます。&arrayは配列全体(最初の要素のアドレスから開始)のアドレスに変換されるため。次に、配列ポインタの最初の要素のアドレスを格納する配列ポインタのメモリアドレスを見つけるにはどうすればよいですか?それは記憶のどこかにあるに違いない?
ジョン・リー・

2
@JohnLee:いいえ、メモリ内のどこかに配列へのポインタがある必要はありません。ポインタを作成すると、そのアドレスを取得できますint *p = array; int **pp = &p;
Jerry Coffin

3
@Cliffordの最初のコメントは間違っていますが、なぜそれを維持するのですか?次の(@Charles)の返答を読まない人には誤解を招くかもしれないと思います。
リック

30

これは、配列名my_array)が配列へのポインターと異なるためです。これは配列のアドレスのエイリアスであり、そのアドレスは配列自体のアドレスとして定義されます。

ただし、ポインタはスタック上の通常のC変数です。したがって、そのアドレスを取得して、内部に保持しているアドレスとは異なる値を取得できます。

私はこのトピックについてここに書きました -見てください。


&my_arrayは無効な操作であってはなりません。my_arrayの値はスタック上にないため、my_array [0 ... length]だけがそうですか?その後、それはすべて理にかなっています...
Alexandre

@Alexandre:なぜ許可されているのか、よくわかりません。
Eli Bendersky

register静的、動的、自動のいずれの保存期間でも、変数のアドレスを取得できます(マークされていない場合)。
CBベイリー

my_arrayので、それ自体は、スタック上でmy_array ある配列全体。
ca

3
my_array&or sizeof演算子の主語ではない場合、最初の要素へのポインタ(つまり&my_array[0])として評価されますが、my_arrayそれ自体はそのポインタではありませんmy_arrayまだ配列です)。そのポインターは、一時的な右辺値です(たとえばint a;、が与えられ、それはのようですa + 1)-概念的には、少なくとも「必要に応じて計算」されます。の実際の「値」my_arrayは、配列全体の内容です。Cでこの値を固定することは、jarファイルでフォグをキャッチしようとするようなものです。
ca

27

もし(関数に渡すを含む)の発現の配列の名称を使用する場合、それはアドレスの(のオペランドでない限りCにおいて、&)オペレータまたはsizeofオペレータは、減衰その最初の要素へのポインタです。

つまり、ほとんどのコンテキストでarray&array[0]、タイプと値の両方が同等です。

あなたの例でmy_arrayは、printfにそれを渡すときにchar[100]減衰するタイプを持っていますchar*

&my_arrayタイプchar (*)[100](100の配列へのポインターchar)があります。これはのオペランドであるため&my_array最初の要素へのポインタまですぐに減衰しない場合の1つです。

配列へのポインタは、配列オブジェクトがその要素の連続したシーケンスであるため、配列の最初の要素へのポインタと同じアドレス値を持っていますが、配列へのポインタは、要素へのポインタへの異なるタイプを持っていますその配列。これは、2種類のポインターに対してポインター演算を行う場合に重要です。

pointer_to_arrayタイプchar *-初期my_array化式で減衰するため、配列の最初の要素を指すように初期化され、&pointer_to_array タイプchar **(を指すポインターへのポインター)char

これらの:my_array(減衰が後にchar*)、&my_array及びpointer_to_array配列または配列の最初の要素のいずれかで直接すべての点ので、同じアドレス値を有します。


3

理由my_array&my_arrayアレイのメモリレイアウトを見ると、同じアドレスになる結果が簡単に理解できます。

10文字の配列があるとしましょう(コードでは100文字ではありません)。

char my_array[10];

のメモリはmy_array次のようになります。

+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.

C / C ++では、配列は次のような式の最初の要素へのポインタに減衰します。

printf("my_array = %p\n", my_array);

配列の最初の要素がある場所を調べると、そのアドレスが配列のアドレスと同じであることがわかります。

my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].

3

Cの直前のプログラミング言語であるBプログラミング言語では、ポインターと整数は自由に交換可能でした。システムは、すべてのメモリが巨大な配列であるかのように動作します。各変数名には、グローバルまたはスタック相対アドレスが関連付けられています。各変数名について、コンパイラーが追跡しなければならないのは、それがグローバル変数であるかローカル変数であるか、および最初のグローバルまたはローカルを基準としたアドレスです。変数。

i;[すべてが整数/ポインタであったため、型を指定する必要がなかった]のようなグローバル宣言が与えられた場合、コンパイラーは次のaddress_of_i = next_global++; memory[address_of_i] = 0;ようi++に処理し、ステートメントは次のように処理されますmemory[address_of_i] = memory[address_of_i]+1;

のような宣言arr[10];はとして処理されaddress_of_arr = next_global; memory[next_global] = next_global; next_global += 10;ます。その宣言が処理されるとすぐarr、コンパイラーは配列であることをすぐに忘れる可能性があることに注意してください。のようなステートメントarr[i]=6;はとして処理されmemory[memory[address_of_a] + memory[address_of_i]] = 6;ます。コンパイラーはarr、配列とi整数のどちらを表すか、またはその逆かを気にしません。実際、両方が配列であるか、両方が整数であるかは問題ではありません。結果として生じる振る舞いが有用であるかどうかに関係なく、記述されているように完全に幸福にコードを生成します。

Cプログラミング言語の目標の1つは、Bとほぼ互換性を持つことでした。Bでは、配列の名前(Bの用語では「ベクトル」と呼ばれます)は、最初にポイントするために割り当てられたポインターを保持する変数を識別しました指定されたサイズの割り当ての最初の要素に追加されるため、その名前が関数の引数リストにある場合、関数はベクトルへのポインタを受け取ります。Cが「実際の」配列型を追加しましたが、その名前は、最初に割り当てを指すポインター変数ではなく、割り当てのアドレスに厳密に関連付けられていましたが、配列をポインターに分解すると、Cタイプの配列を宣言するコードは同じように動作しますベクトルを宣言し、そのアドレスを保持する変数を決して変更しないBコードに。


1

実際には&myarraymyarray両方ともベースアドレスです。

使用する代わりに違いを確認したい場合

printf("my_array = %p\n", my_array);
printf("my_array = %p\n", &my_array);

使用する

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.