したがって、C / C ++で渡される配列「オブジェクト」には、配列の最初のオブジェクトのアドレスが含まれていることを常に知っていました。
配列「オブジェクト」へのポインタとそれに含まれる値を同じにするにはどうすればよいですか?
誰かが私に、おそらくアセンブリですべてがどのように機能するかについてのより多くの情報を教えてもらえますか?
簡単な答え: 配列へのポインターは、配列の最初の要素へのポインターと同じ値を持つように定義されています。これが、C および C++ の配列のしくみです。
衒学的な答え:
C および C++ には、右辺値式と左辺値式があります。左辺値は、&
演算子を適用できるものです。また、暗黙的な変換もあります。オブジェクトは、使用する前に別の型に変換できます。(たとえば、sqrt( 9 )
thenを呼び出すと、 が定義されていないため、9
に変換されます。)double
sqrt( int )
配列型の左辺値は暗黙的にポインターに変換されます。暗黙の変換は に変わりarray
ます&array[0]
。static_cast< int * >( array )
これは、C++ では ,として明示的に書き出すこともできます。
そうすればOKです。へのキャストvoid*
は別の話です。void*
少し醜いです。また、括弧を as でキャストするの(void*)array
も醜いです。したがって(void*) a
、実際のコードでは避けてください。
あなたは2つの無関係な(そして実際には相互に排他的な)ものを混ぜ合わせているので、さらに混乱が生じます。
まず、「 C / C ++で渡される配列オブジェクトには、配列の最初のオブジェクトのアドレスが含まれている」と正しく述べています。ここでのキーワードは「回る」です。実際には、配列を配列オブジェクトとして渡すことはできません。配列はコピーできません。関数パラメータリストで配列スタイルの宣言を使用している場合は常に、実際にはポインタ宣言として解釈されます。つまり、配列ではなく、「受け渡し」しているポインタです。しかし、そのような状況ではあなたの平等は成り立たない
void foo(int a[]) {
assert((void *) &a == (void *) a); // FAIL!!!
}
上記の主張は失敗することが保証されています-平等は成り立ちません。したがって、この質問のコンテキスト内では、「渡す」配列について忘れる必要があります(少なくとも上記の例で使用されている構文では)。ポインタオブジェクトに置き換えられた配列については、同等性は成り立ちません。
第二に、実際の配列オブジェクトはポインタではありません。また、オブジェクトという用語を引用符で囲む必要はありません。配列は、いくつかの固有のプロパティがありますが、本格的なオブジェクトです。問題の同等性は、「配列性」を失っていない実際の配列、つまりポインタオブジェクトに置き換えられていない配列オブジェクトにも当てはまります。例えば
int a[10];
assert((void *) &a == (void *) a); // Never fails
つまり、配列全体のアドレスは、数値的には最初の要素のアドレスと同じです。ここでは何も珍しいことはありません。実際、C / C ++の構造体タイプでも、まったく同じ(本質的に)同等性を観察できます。
struct S { int x; } a;
assert((void *) &a == (void *) &a.x); // Never fails
つまり、構造体オブジェクト全体のアドレスは、その最初のフィールドのアドレスと同じです。
配列「オブジェクト」へのポインターとそれに含まれる値が同じになるにはどうすればよいですか?
配列は、いくつかの要素を格納する連続したメモリ ブロックです。
明らかに、配列の最初の要素はあるアドレスにあります。
最初の要素と実際の配列の先頭の間のデータはありません。
したがって、最初の要素は配列と同じアドレスを持ちます。
次のスレッドを読んでください
http://www.cplusplus.com/forum/beginner/29595/
どちらも同じアドレスを指しているにもかかわらず、基本的(&a != a)
には型の違いにより(配列と最初の要素へ&a
のポインターを返すため)説明しています。a
それらの両方を(void*)
アドレス値のみにキャストしているため、比較して等しいことがわかります。つまり((void*) a == (void*)&a)
、あなたが述べたように。配列のアドレスは最初の要素と同じでなければならないため、これは理にかなっています。
これら 2 つの宣言を見てみましょう。
int a[4];
int * b;
と は両方ともa
とb
互換性のある型を持ちint *
、たとえば、期待する関数に引数として渡すことができますint *
:
void f(int * p);
f(a); // OK
f(b); // OK
の場合a
、コンパイラは 4 つの int 値にスペースを割り当てます。a
を呼び出すときなどに name を使用するとf(a)
、コンパイラは知っているので、それらの int 値の最初に割り当てた場所のアドレスを置き換えるだけです。
の場合b
、コンパイラは 1 つのポインターにスペースを割り当てます。b
を呼び出すときなどに name を使用するとf(b)
、コンパイラは、割り当てられたストレージからポインター値を取得するためのコードを生成します。
になると、と&
の違いが明らかになります。always は、コンパイラが変数に割り当てたストレージのアドレスを意味します。はこれらの 4 つの int 値のアドレス (したがって、ちょうど と一致します) であり、一方はポインター値のアドレスです。それらにもさまざまな種類があります。a
b
&
&a
a
&b
&a
は とまったく同じではありませんがa
、比較すると等しいとは言えません。それらは異なる型を持ちます:&a
はポインターでa
あり、配列です。たとえば、sizeof
これらの式に演算子を適用すると、違いがわかります。sizeof(a)
は 4 つの int 値のサイズに評価され、sizeof(&a)
はポインターのサイズに評価されます。
さて、私が考えたのは、配列を作成したときに、配列用のスペースをどこかに割り当て、最初のオブジェクトへのポインターを別の場所に作成し、コードで渡したのはポインターだったということです。
これは実際に、C++ の new または C/C++ の malloc を使用して配列を作成したときに発生する動作です。そのような、
int * a = new a[SIZE];
assert((void*)&a==(void*)a); // Always fails
私が学んだことは、 のスタイルで宣言された配列のint a[SIZE];
場合、配列を関数に渡そうとすると、最初の要素へのポインターが作成されるということです (これは、配列ポインターの減衰と呼ばれます)。実際、AndreyT が書いているように、
void foo(int a[]) {
assert((void *) &a == (void *) a); // Always fails
}
これは、配列を渡そうとしたときにのみ、 のスタイルで配列用のポインターが作成されることを示していますint a[SIZE];
。