Макет памяти унаследованного класса

Я очень хочу знать, как будут организованы классы в памяти esp. с наследованием и виртуальными функциями.

Я знаю, что это не определено стандартом языка С++. Однако есть ли простой способ узнать, как ваш конкретный компилятор будет реализовывать их, скажем, написав тестовый код?

EDIT: - Используя некоторые из ответов ниже: -

#include <iostream>

using namespace std;

class A {
  public:
    int a;
    virtual void func() {}
};

class B : public A {
  public:
    int b;
    virtual void func() {}
};

class C {
  public:
    int c;
    virtual void func() {}
};

class D : public A, public C {
  public:
    int d;
    virtual void func() {}
};

class E : public C, public A {
  public:
    int e;
    virtual void func() {}
};

class F : public A {
  public:
    int f;
    virtual void func() {}
};

class G : public B, public F {
  public:
    int g;
    virtual void func() {}
};

int main() {
  A a; B b; C c; D d; E e; F f; G g;
  cout<<"A: "<<(size_t)&a.a-(size_t)&a<<"\n";
  cout<<"B: "<<(size_t)&b.a-(size_t)&b<<" "<<(size_t)&b.b-(size_t)&b<<"\n";
  cout<<"C: "<<(size_t)&c.c-(size_t)&c<<"\n";
  cout<<"D: "<<(size_t)&d.a-(size_t)&d<<" "<<(size_t)&d.c-(size_t)&d<<" "<<(size_t)&d.d-    (size_t)&d<<"\n";
  cout<<"E: "<<(size_t)&e.a-(size_t)&e<<" "<<(size_t)&e.c-(size_t)&e<<" "<<(size_t)&e.e-    (size_t)&e<<"\n";
  cout<<"F: "<<(size_t)&f.a-(size_t)&f<<" "<<(size_t)&f.f-(size_t)&f<<"\n";
  cout<<"G: "<<(size_t)&g.B::a-(size_t)&g<<" "<<(size_t)&g.F::a-(size_t)&g<<" "    <<(size_t)&g.b-(size_t)&g<<" "<<(size_t)&g.f-(size_t)&g<<" "<<(size_t)&g.g-(size_t)&g<<"\n";
}

И результат: -

A: 8
B: 8 12
C: 8
D: 8 24 28
E: 24 8 28
F: 8 12
G: 8 24 12 28 32

Таким образом, все классы имеют v-ptr при loc 0 размера 8. D имеет другой v-ptr в местоположении 16. Аналогично для E. G также, похоже, имеет v-ptr в 16, хотя из моего (ограниченного) понимания я бы догадался, что у него больше.

Ответ 1

Один из способов - распечатать смещения всех членов:

class Parent{
public:
    int a;
    int b;

    virtual void foo(){
        cout << "parent" << endl;
    }
};

class Child : public Parent{
public:
    int c;
    int d;

    virtual void foo(){
        cout << "child" << endl;
    }
};

int main(){

    Parent p;
    Child c;

    p.foo();
    c.foo();

    cout << "Parent Offset a = " << (size_t)&p.a - (size_t)&p << endl;
    cout << "Parent Offset b = " << (size_t)&p.b - (size_t)&p << endl;

    cout << "Child Offset a = " << (size_t)&c.a - (size_t)&c << endl;
    cout << "Child Offset b = " << (size_t)&c.b - (size_t)&c << endl;
    cout << "Child Offset c = " << (size_t)&c.c - (size_t)&c << endl;
    cout << "Child Offset d = " << (size_t)&c.d - (size_t)&c << endl;

    system("pause");
}

Вывод:

parent
child
Parent Offset a = 8
Parent Offset b = 12
Child Offset a = 8
Child Offset b = 12
Child Offset c = 16
Child Offset d = 20

Итак, вы можете увидеть все смещения здесь. Вы заметите, что ничего нет при смещении 0, поскольку это, по-видимому, есть указатель на vtable.

Также обратите внимание, что унаследованные члены имеют одинаковые смещения как в Child, так и в Parent.

Ответ 2

У Visual Studio atleast есть опция скрытого компилятора /d1reportSingleClassLayout (начиная с ~ 32: 00).

Использование: /d1reportSingleClassLayoutCLASSNAME, где между переключателем компилятора и CLASSNAME не должно быть пробелов (очевидно, замените это на имя интересующего вас класса).

Ответ 3

Создайте объект класса, указав указатель на него на машинное слово, используйте sizeof, чтобы найти размер объекта и изучите память в этом месте. Что-то вроде этого:

#include <iostream>

class A
{
 public:
  unsigned long long int mData;
  A() :
   mData( 1 )
  {
  }      
  virtual ~A()
  {
  }
};
class B : public A
{
 public:
  unsigned long long int mData1;
  B() :
   A(), mData1( 2 )
  {
  }
};

int main( void )
{
 B lB;

 unsigned long long int * pB = ( unsigned long long int * )( &lB );

 for( int i = 0; i < sizeof(B) / 8; i++ )
 {
  std::cout << *( pB + i ) << std::endl;
 }

 return ( 0 );
}


Program output (MSVC++ x86-64):

5358814688 // vptr
1          // A::mData
2          // B::mData1

С одной стороны, Стэнли Б. Липпман имеет отличную книгу "Внутри объектной модели С++" .

Ответ 4

Лучший способ - это, вероятно, написать несколько простых тестовых примеров, а затем скомпилировать и отладить их в ассемблере (вся оптимизация выключена): запустив одну команду за раз, вы увидите, где все подходит.

По крайней мере, так, как я это узнал.

И если вы найдете какой-либо случай, особенно сложный, отправьте сообщение в SO!

Ответ 5

Пока вы придерживаетесь одиночного наследования, подобъекты обычно выкладываются в том порядке, в котором они объявлены. Указатель добавляется к тому, что они печатают информацию спереди, например. используется для динамической отправки. Как только множественное наследование включено, все становится более сложным, особенно когда задействовано виртуальное наследование.

Чтобы найти точную информацию, по крайней мере, для одного аромата ABI, вы можете найти Itanium ABI. Это документирует все эти детали. Он используется как С++ ABI, по крайней мере, на некоторых платформах Linux (т.е. Несколько компиляторов могут создавать объектные файлы, связанные с одним исполняемым файлом).

Чтобы определить макет, просто распечатайте адреса подобъектов объекта-объекта. Тем не менее, если вам не удалось реализовать компилятор, это, как правило, не имеет значения. Единственное реальное использование макета объекта, которое я сомневаюсь, заключается в том, чтобы упорядочить элементы, чтобы минимизировать отступ.