48

重複の可能性:
メモリ内の C++ オブジェクトの構造と構造体の
メモリ レイアウト c++ オブジェクト

これはおそらく本当にばかげた質問ですが、とにかく尋ねます。オブジェクトがメモリ内でどのように見えるか興味があります。明らかに、すべてのメンバー データが含まれている必要があります。オブジェクトの関数はメモリ内で複製されないと思います (または、間違っているのでしょうか?)。同じ関数が何度も定義された 999 個のオブジェクトをメモリ内に保持するのは無駄に思えます。999 個のオブジェクトすべてに対してメモリ内に関数が 1 つしかない場合、各関数は変更するメンバー データをどのように認識しますか (具体的には低レベルで知りたい)。舞台裏で関数に送信されるオブジェクト ポインターはありますか? おそらく、コンパイラごとに異なりますか?

また、 static キーワードはこれにどのように影響しますか? 静的メンバー データでは、999 個のオブジェクトすべてが静的メンバー データにまったく同じメモリ位置を使用すると思います。これはどこに保存されますか?私が推測する静的関数もメモリ内の1つの場所にすぎず、インスタンス化されたオブジェクトと対話する必要はありません。これは私が理解していると思います。

4

5 に答える 5

26

静的クラス メンバーは、グローバル変数/関数とほぼ同じように扱われます。これらはインスタンスに関連付けられていないため、メモリ レイアウトに関して議論する必要はありません。

ご想像のとおり、クラス メンバー変数はインスタンスごとに複製されます。これは、各インスタンスがすべてのメンバー変数に対して独自の値を持つことができるためです。

クラス メンバー関数は、メモリ内のコード セグメントに 1 回だけ存在します。低レベルでは、これらは通常のグローバル関数と同じですが、へのポインタを受け取りますthis。x86 の Visual Studio では、呼び出し規約ecxを使用して登録します。thiscall

仮想関数、ポリモーフィズムについて話すとき、メモリ レイアウトはより複雑になり、基本的にはクラス インスタンスのトポグラフィを定義する一連の関数ポインタである「 vtable 」が導入されます。

于 2012-09-11T21:43:47.230 に答える
8

ご想像のとおり、データメンバー(フィールド)は順番に配置されます。これには、基本クラスのフィールドも含まれます。

クラス(またはその基本クラスの1つ)に仮想メソッドが含まれている場合、レイアウトは通常vptrで始まります。つまり、そのクラスに関連する関数実装へのポインターのテーブルである仮想テーブル(またはvtable)へのポインターです。これは標準で定義されていないことに注意してください。ただし、現在のすべてのコンパイラはこのアプローチを使用しています。また、多重継承では毛むくじゃらになるので、とりあえず無視しましょう。

+-----------+
|  vptr     |  pointer to vtable which is located elsewhere
+-----------+
|  fieldA   |  first member
|  fieldB   |  ...
|  fieldC   |
|  ...      |
+-----------+

フィールドは、個々のサイズの合計よりも多くのスペースを占める可能性があります。これは、パッキングによって異なります(たとえば、1バイトのパッキングはギャップがないことを保証しますが、パフォーマンスに関しては4バイトまたは8バイトのパッキングよりも効率が低くなります)。

メンバー関数(非静的)はオブジェクトへのポインターを受け取りますが、これが行われる方法は実装とプラットフォーム固有です。たとえば、x86アーキテクチャーでは、ポインターはecxレジスターを介して渡されることがよくあります。これも規格では定義されていません。

静的関数はグローバル関数に似ており、データセグメントにある静的クラスフィールド(クラスのすべてのインスタンスで共有)を操作します。

于 2012-09-11T21:57:00.260 に答える
7

あなたはここでいくつかの質問をしました...

レイアウト

すべての非静的メンバーは、構造体のようにメモリ内で編成されます。コンパイラが何かを挿入することを選択した場合、パディングが発生する可能性があります。オブジェクトの配列がある場合、それは構造体の配列と同じです。

静的メンバー

明らかに別々に保管されています。1 つのコピー。

関数呼び出し

クラスの舞台裏ではちょっとした魔法が起こっています。メンバー関数を呼び出すときは、呼び出し規則が異なることを除いて、他の関数とほとんど同じです。実際には、これにより、オブジェクトのポインター (this) がパラメーター リストに挿入されます。

[編集delete this: 関数自体のコードはオブジェクトに保存されません。これにより、削除したばかりのオブジェクトにアクセスしなくなった場合に、メンバー関数の実行を継続するなどの楽しいことができます]。

オーバーロードされた関数またはポリモーフィックな関数がある場合、物事はもう少し魔法のようになります。 この記事はググって5秒くらいでまとめた解説です。他にもたくさんあると思います。私は、オブジェクト呼び出しの内部についてあまり気にしたことはありませんが、知っていると常にうれしいものです。

これらのさまざまな側面をすべて示すクラスを作成する演習を試し、それぞれの場合に生成されたアセンブリを確認する必要があります。タイムクリティカルなコードをチューニングするときに、以前にそれを行ったことがあります。

于 2012-09-11T21:50:38.570 に答える
3

最初に注意することは、C ++では、「オブジェクト」という用語には整数などが含まれるということです。

次のことは、構造はあなたが期待するのとほぼ同じようにレイアウトされているということです。あるメンバーがメモリ内で次のメンバーに続き、間に未定義の量のパディングがあります。

クラスが別のクラスから継承する場合、クラスはその基本クラスで開始され、その基本クラスは独自の基本クラスで開始される場合があります。したがって、単一継承の場合、Derived*とBase*は同じ値になります。ベースの領域(そのメンバー)に続いて、派生クラスのメンバーが順番に続き、それらの間に未定義の量のパディングがあります。

クラスが複数のベースから継承する場合、状況は少し異なります。ベースエリアはメモリ内に順番に配置されます。Base1の後にBase2などが続きます。その後、派生クラスのメンバーが、未定義の量のパディングを使用して順番に配置されます。

オブジェクトがPODクラスの場合、クラスの最初のメンバーは、オブジェクトが存在するメモリ内の場所にあることが保証されます。これは、Class*とClass->firstMember*が同じ値になることを意味します。これが非PODエンティティに当てはまるとは思わない。

仮想関数を持つポリモーフィッククラスの場合、vtableと呼ばれる追加のシークレットメンバーが作成されます。これは標準の何によっても保証されていませんが、それを実行し、ルールに従う唯一の方法です。各クラスにはこれがあるため、クラスにベースがある場合は、そのテーブルにテーブルがあり、追加の関数を使用できます。

thisすべてのメンバー関数の名前が変更され、最初の引数として受け入れるようにパラメーターが変更されます。これは、コンパイラがものを構築するときに舞台裏で発生します。仮想関数はvtableによってポイントされます。非仮想は単に静的に解決され、直接使用されます。

静的メンバーは、クラスによって作成されたオブジェクトのメンバーではありません。静的メンバーは、スコープが異なる単なるグローバル変数です。

于 2012-09-11T22:39:13.427 に答える
0

メンバー変数が配置され、仮想メソッドが実際に関連付けられている関数へのポインターのリストを含む多態性の場合は、仮想関数テーブルも配置されます。

静的とは、1 つのコピーのみを意味します。

于 2012-09-11T21:38:00.877 に答える