web-dev-qa-db-ja.com

メモリレイアウトC ++オブジェクト

基本的に、C++がメモリ内のオブジェクトをどのようにレイアウトするのか疑問に思っています。そのため、動的キャストはメモリ内のオブジェクトのポインタをオフセットで調整するだけだと聞きました。種類を再解釈すると、このポインターで何でもできるようになります。私はこれを本当に理解していません。詳細をいただければ幸いです!

36
rsinha

各クラスは、宣言の順にデータメンバーをレイアウトします。
コンパイラは、アクセスを効率的にするためにメンバー間にパディングを配置できます(ただし、並べ替えはできません)。

どうやって dynamic_cast<> worksはコンパイラ実装の詳細であり、標準では定義されていません。それはすべて、コンパイラが使用するABIに依存します。

reinterpret_cast<>は、オブジェクトのタイプを変更するだけで機能します。確実に機能することを保証できるのは、ポインターをvoid *にキャストしてクラスに戻すと、同じポインターが得られることです。

12
Martin York

メモリレイアウトは、ほとんど実装に任されています。重要な例外は、特定のアクセス指定子のメンバー変数が宣言の順序になることです。

§9.2.14

同じアクセス制御(11章)を持つ(非ユニオン)クラスの非静的データメンバーは、後のメンバーがクラスオブジェクト内でより高いアドレスを持つように割り当てられます。異なるアクセス制御を持つ非静的データメンバーの割り当ての順序は指定されていません(11)。実装のアライメント要件により、2つの隣接するメンバーがすぐに割り当てられない場合があります。仮想機能(10.3)および仮想基本クラス(10.1)を管理するためのスペースの要件もあります。

メンバー変数以外に、クラスまたは構造体は、メンバー変数、基本クラスのサブオブジェクト、仮想関数管理(仮想テーブルなど)、およびこれらのデータのパディングとアライメントのためのスペースを提供する必要があります。これは実装次第ですが、Itanium ABI仕様が一般的な選択肢です。 gccとclangは(少なくともある程度は)それに従います。

http://mentorembedded.github.io/cxx-abi/abi.html#layout

Itanium ABIはもちろんC++標準の一部ではなく、拘束力もありません。さらに詳しく知るには、実装者のドキュメントとツールに目を向ける必要があります。 clangは、クラスのメモリレイアウトを表示するツールを提供します。例として、次の:

class VBase {
    virtual void corge();
    int j;
};

class SBase1 {
    virtual void grault();
    int k;
};

class SBase2 {
    virtual void grault();
    int k;
};

class SBase3 {
    void grault();
    int k;
};

class Class : public SBase1, SBase2, SBase3, virtual VBase {
public:
    void bar();
    virtual void baz();
    // virtual member function templates not allowed, thinking about memory
    // layout and vtables will tell you why
    // template<typename T>
    // virtual void quux();
private:
    int i;
    char c;
public:
    float f;
private:
    double d;
public:
    short s;
};

class Derived : public Class {
    virtual void qux();
};

int main() {
    return sizeof(Derived);
}

クラスのメモリレイアウトを使用するソースファイルを作成した後、clangはメモリレイアウトを表示します。

$ clang -cc1 -fdump-record-layouts layout.cpp

Classのレイアウト:

*** Dumping AST Record Layout
   0 | class Class
   0 |   class SBase1 (primary base)
   0 |     (SBase1 vtable pointer)
   8 |     int k
  16 |   class SBase2 (base)
  16 |     (SBase2 vtable pointer)
  24 |     int k
  28 |   class SBase3 (base)
  28 |     int k
  32 |   int i
  36 |   char c
  40 |   float f
  48 |   double d
  56 |   short s
  64 |   class VBase (virtual base)
  64 |     (VBase vtable pointer)
  72 |     int j
     | [sizeof=80, dsize=76, align=8
     |  nvsize=58, nvalign=8]

このclang機能の詳細については、Eli Benderskyのブログをご覧ください。

http://eli.thegreenplace.net/2012/12/17/dumping-a-c-objects-memory-layout-with-clang/

gccは同様のツール、 `-fdump-class-hierarchy 'を提供します。上記のクラスの場合、(とりわけ)出力されます:

Class Class
   size=80 align=8
   base size=58 base align=8
Class (0x0x141f81280) 0
    vptridx=0u vptr=((& Class::_ZTV5Class) + 24u)
  SBase1 (0x0x141f78840) 0
      primary-for Class (0x0x141f81280)
  SBase2 (0x0x141f788a0) 16
      vptr=((& Class::_ZTV5Class) + 56u)
  SBase3 (0x0x141f78900) 28
  VBase (0x0x141f78960) 64 virtual
      vptridx=8u vbaseoffset=-24 vptr=((& Class::_ZTV5Class) + 88u)

メンバー変数を項目化することはありません(または少なくとも取得方法がわかりません)が、clangレイアウトの場合と同様に、オフセット28から64の間でなければならないことがわかります。

1つの基本クラスがprimaryとして選択されていることがわかります。これにより、thisSBase1としてアクセスされるときに、Classポインターを調整する必要がなくなります。

Gccに相当するものは次のとおりです。

$ g++ -fdump-class-hierarchy -c layout.cpp

Visual C++に相当するものは次のとおりです。

cl main.cpp /c /d1reportSingleClassLayoutTest_A

参照: https://blogs.msdn.Microsoft.com/vcblog/2007/05/17/diagnosing-hidden-odr-violations-in-visual-c-and-fixing-lnk2022/

16
Praxeolitic

前に述べたように、詳細は複雑で読みにくく、実際にはコンパイラ開発者にのみ有用であり、コンパイラによって異なります。基本的に、各オブジェクトには次のものが含まれます(通常、この順序で配置されます)。

  1. ランタイムタイプ情報
  2. 非仮想ベースオブジェクトとそのデータ(おそらく宣言の順に)。
  3. メンバー変数
  4. 仮想ベースオブジェクトとそのデータ(おそらくDFSツリーの検索順序で)。

これらのデータは、メモリのアライメントを容易にするためにパディングされる場合とされない場合があります。ランタイムタイプ情報には、タイプ、仮想親クラスのvテーブルなど、コンパイラ固有の情報が隠されています。

キャストに関しては、reinterpret_castは、ポインタのC++データ型を変更するだけで、他には何もしません。そのため、使用時に何をしているのかを確実に把握しておく必要があります。 dynamic_castは、静的型キャストとほぼ同じことを行います(ポインターの変更)。ただし、ランタイム型情報を使用して、指定された型にキャストできるかどうか、およびその方法を判断します。繰り返しますが、これらはすべてコンパイラ固有のものです。 dynamic_cast a void*すべてのすばらしいランタイムチェックを実行できるように、ランタイムタイプ情報の場所を知る必要があるためです。

4
Jason E

答えは、「複雑です」です。動的なキャストでは、ポインタをオフセットで調整するだけではありません。作業を行うために、オブジェクト内の内部ポインターを実際に取得する場合があります。 GCCはItanium用に設計されたABIに従いますが、より広く実装されています。詳細な情報は Itanium C++ ABI にあります。

4
Hyman Rosen

この質問はすでに http://dieharddeveloper.blogspot.in/2013/07/c-memory-layout-and-process-image.html から回答されています。プロセスのアドレス空間のうち、共有オブジェクト用に予約されている領域があります。新しいプロセスが作成されると、プロセスマネージャはまず2つのセグメントを実行可能ファイルからメモリにマップします。次に、プログラムのELFヘッダーをデコードします。実行可能ファイルが共有ライブラリに対してリンクされていることをプログラムヘッダーが示す場合、プロセスマネージャー(PM)はプログラムヘッダーから動的インタープリターの名前を抽出します。動的インタープリターは、実行時リンカーコードを含む共有ライブラリを指します。

1
cppminds