4

int A[512]配列がある場合、参照 A が最初の要素を指すことができることを知っています。ポインタ演算では、メモリは として参照されA + indexます。

しかし、私が間違っていなければ、ポインター/参照も機械語のスペースを占有します。int が機械語を占めると仮定すると、上記の配列の 512 個の整数が 513 語のスペースを占めるということですか?

C++ または C# のオブジェクトとそのデータ メンバーの真/偽は同じですか?

更新:うわー、皆さんは速いです。明確にするために、C++ と C#がこれを処理する方法がどのように異なるか、およびキャッシュ ラインに収まるようにオブジェクトのサイズを変更する方法 (可能であれば) に興味があります。

更新: ポインターと配列の違いを認識しました。配列はポインターではなく、上で参照したポインター演算は、配列がポインターに変換された後にのみ有効であることを理解しています。ただし、この区別は全体的な質問には関係ないと思います。C++ と C# の両方で、配列とその他のオブジェクトの両方がメモリに格納される方法に興味があります。

4

7 に答える 7

1

データをキャッシュ ラインに適合させることについて話している場合、参照を含む変数とそれが参照する実際のデータは近くに配置されないことに注意してください。参照は (最終的には) レジスタに格納されますが、元は別のオブジェクトの一部としてメモリ内の別の場所に格納されているか、スタック上のローカル変数として格納されている可能性があります。「オブジェクト」に関連するその他のオーバーヘッドに関係なく、配列の内容自体は操作時にキャッシュ ラインに収まります。これが C# でどのように機能するか興味がある場合、Visual Studio には、コード用に生成された実際の x86 または x64 アセンブリを表示する逆アセンブラー ビューがあります。

配列参照には、IL (中間言語) レベルでの特別な組み込みサポートがあるため、メモリのロード/使用方法は、C++ で配列を使用する場合と基本的に同じであることがわかります。内部的には、配列へのインデックス付けはまったく同じ操作です。違いに気付き始めるのは、「foreach」を使用して配列にインデックスを付ける場合、または配列がオブジェクト型の配列である場合に参照を「ボックス化解除」する必要がある場合です。

メソッドでローカルにオブジェクトをインスタンス化する場合、C++ と C# の間のメモリの局所性に関する 1 つの違いが現れることに注意してください。C++ では、スタック上で配列をインスタンス化できます。これにより、配列メモリが実際に「参照」およびその他のローカル変数のすぐ近くに格納されるという特殊なケースが作成されます。C# では、(マネージド) 配列の内容は常にヒープに割り当てられます。

一方、ヒープ割り当てオブジェクトを参照する場合、特に存続期間の短いオブジェクトの場合、C# は C++ よりもメモリの局所性が優れている場合があります。これは、GC がオブジェクトを「世代」(オブジェクトが存続している時間) ごとに格納する方法と、GC が行うヒープの圧縮によるものです。存続期間の短いオブジェクトは、増大するヒープにすばやく割り当てられます。収集されると、ヒープも圧縮され、圧縮されていないヒープでの後続の割り当てがメモリ内に散在する原因となる「断片化」が防止されます。

C++ では、「オブジェクト プーリング」手法を使用して (または、存続期間の短い小さなオブジェクトを頻繁に使用しないようにすることで) 同様のメモリ局所性の利点を得ることができますが、それには少し余分な作業と設計が必要です。もちろん、これのコストは、スレッドのハイジャック、世代の昇格、参照の圧縮と再割り当てを使用して GC を実行する必要があることであり、予測不可能な時期に測定可能なオーバーヘッドが発生します。実際には、オーバーヘッドが問題になることはめったにありません。特に Gen0 コレクションでは、頻繁に割り当てられる有効期間の短いオブジェクトの使用パターンに対して高度に最適化されています。

于 2013-03-05T17:12:32.780 に答える
1

C++ の配列とポインタについて誤解しているようです。

配列

int A[512];

この宣言は、512intの配列を取得します。他には何もありません。ポインターも何もありません。s の単なる配列ですint。配列のサイズは になります512 * sizeof(int)

名前

名前Aはその配列を指します。ポインタ型ではありません。配列型です。これは名前であり、配列を参照します。名前は、どのオブジェクトについて話しているかをコンパイラに伝えるためのコンパイル時の構成要素にすぎません。名前は実行時には存在しません。

変換

状況によっては、配列からポインターへの変換と呼ばれる変換が発生する場合があります。変換では、配列型の式 (単純な式などA) を受け取り、それを最初の要素へのポインターに変換します。つまり、場合によっては、式(配列を表す) が(配列の最初の要素を指すA) に変換されることがあります。int*

ポインター

配列からポインターへの変換によって作成されたポインターは、それが含まれる式の期間中存在します。これらの特定の状況で表示されるのは、一時的なオブジェクトにすぎません。

状況

配列からポインターへの変換は標準的な変換であり、次のような状況で発生する可能性があります。

  • 配列からポインターにキャストする場合。たとえば、(int*)A.

  • ポインタ型のオブジェクトを初期化するときint* = A;

  • 配列を参照する glvalue が、prvalue を期待する式のオペランドとして現れるときはいつでも。

    のように配列に添字を付けると、このようになりA[20]ます。添字演算子はポインター型の prvalue を想定しているため、A配列からポインターへの変換が行われます。

于 2013-03-05T16:52:27.843 に答える
0

いいえ、CLRのオブジェクトは、参照している(想像してみてください)の「単純な」メモリマッピングにもマップされませC++CLRリフレクションを使用してオブジェクトを操作できることを忘れないでください。つまり、すべてのオブジェクトには、その中に追加情報(マニフェスト)が必要です。これにより、オブジェクトの単なるコンテンツよりも多くのメモリがすでに追加され、これにマルチスレッド環境での管理用のポインタも追加され、オブジェクトへの予想されるメモリ割り当てのlocking点ではるかに遠くなります。CLR

また、ポインタサイズはビットマシン間32で異なることを忘れないでください。64

于 2013-03-05T16:33:05.357 に答える
0

配列int A[512]は512*sizeof(int)を使用します(+コンパイラが追加することを決定したパディング-この特定の例では、パディングがない可能性が非常に高いです)。

配列Aをintへのポインターに変換してAA + index使用できるという事実は、実装A[index]ではほとんどの場合、とまったく同じ命令であるという事実を使用していA + indexます。ポインタへの変換はどちらの場合も発生します。これは、に到達するためにA[index]、配列Aの最初のアドレスを取得し、index時間を追加するsizeof(int)必要があるためです。どちらの場合も、は配列の最初のアドレスとその中の要素の数を参照しています。A[index]A + indexAindex

ここで使用される余分なスペースはありません。

上記はCおよびC++に適用されます。

「マネージドメモリ」を使用するC#およびその他の言語では、各変数を追跡するための余分なオーバーヘッドがあります。これは変数自体のサイズには影響しませんAが、もちろんどこかに格納する必要があります。したがって、単一の整数であろうと非常に大きな配列であろうと、すべての変数には、変数とある種の「参照カウント」(変数が使用される場所の数、および変数を削除できるかどうか)。

于 2013-03-05T16:33:38.207 に答える
0

C++の配列とポインタを混同していると思います。

の配列intはまさにそれであり、メモリ内の場所の配列であり、それぞれがsizeof(int)N-1を格納できる場所を占めていますint

ポインタはメモリ位置を指すことができるタイプであり、メモリ内のCPUレジスタサイズを使用するため、32ビットマシンでsizeof(int*)は32ビットになります。

配列へのポインタが必要な場合は、次のようにします。int * ptr = &A[0]; これは配列の最初の要素を指します。これで、ポインタがメモリ(CPUワードサイズ)を占有し、intsの配列ができました。

配列をCまたはC++の関数に渡すと、配列の最初の要素へのポインターに減衰します。これは、ポインターが配列であるという意味ではなく、配列からポインターへの減衰があることを示しています。

C#では、配列は参照型であり、ポインターがないため、心配する必要はありません。配列のサイズを占めるだけです。

于 2013-03-05T16:33:44.467 に答える
0

議論する言語がいくつかあることを考えると、ここには複数の異なる例があります。

簡単な例、C++ の単純な配列から始めましょう。

int array[512];

ここでのメモリ割り当てに関してはどうなりますか? 512 ワードのメモリが配列のスタックに割り当てられます。ヒープ メモリは割り当てられません。いかなる種類のオーバーヘッドもありません。配列へのポインターはなく、何もありません。メモリの 512 ワードだけです。

C++ で配列を作成する別の方法を次に示します。

int * array = new int[512];

ここでは、ヒープ上に配列を作成しています。ヒープに追加のメモリを割り当てずに、512 ワードのメモリを割り当てます。次に、それが完了すると、その配列の先頭へのアドレスがスタック上の変数に配置され、追加のメモリ ワードが使用されます。アプリケーション全体の合計メモリ フットプリントを見ると、確かに 513 になりますが、1 つがスタック上にあり、残りがヒープ上にあることに注意してください (スタック メモリの割り当てははるかに安価であり、断片化しますが、使いすぎたり誤用したりすると、より簡単に不足する可能性があります。

今C#に。C# には 2 つの異なる構文はありません。

int[] array = new int[512];

これにより、ヒープ上に新しい配列オブジェクトが作成されます。これには、配列内のデータ用の 512 ワードのメモリと、配列オブジェクトのオーバーヘッド用の追加メモリが少し含まれます。配列のカウント、同期オブジェクト、および実際には考慮する必要のないその他のオーバーヘッドを保持するために 4 バイトが必要です。そのオーバーヘッドは小さく、配列のサイズには依存しません。

また、スタックに配置されるその配列へのポインター (C# で使用する方が適切な「参照」) もあり、1 ワードのメモリを占有します。C++ と同様に、スタック メモリは、メモリを断片化することなく、非常に迅速に割り当て/割り当て解除できるため、プログラムのメモリ フットプリントを考慮すると、多くの場合、スタック メモリを分離することが理にかなっています。

于 2013-03-05T17:30:47.350 に答える
0

ネイティブ C++について:

しかし、私が間違っていなければ、ポインター/参照も機械語のスペースを占有します

参照は必ずしもメモリ内のスペースを必要としません。C++11 標準のパラグラフ 8.3.2/4 によると:

参照がストレージを必要とするかどうかは指定されていません (3.7)。

この場合、ポインタのように使用 Aできます。実際、必要に応じて (たとえば、関数への引数として渡すとき) ポインタに減衰Aしますが、の型はis int[512], not int*: したがって、ポインタAではありません。たとえば、次のことはできません。

int A[512];
int B;
A = &B;

格納に使用されるメモリ位置A(つまり、配列が始まるメモリ アドレスを格納するために使用される) は必要ないため、ほとんどの場合、コンパイラはA.

于 2013-03-05T16:38:33.940 に答える