расположение памяти унаследованного класса - PullRequest
13 голосов
/ 29 декабря 2011

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

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

РЕДАКТИРОВАТЬ: - Используя некоторые из ответов ниже: -

#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, хотя из моего (ограниченного) понимания я бы предположил, что у него будет больше.

Ответы [ 5 ]

14 голосов
/ 29 декабря 2011

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

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 .

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

7 голосов
/ 29 декабря 2011

Visual Studio по крайней мере имеет скрытую опцию компилятора /d1reportSingleClassLayout (начиная с ~ 32:00).

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

1 голос
/ 29 декабря 2011

Создайте объект класса, наведите указатель на него на слово вашей машины, используйте 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

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

0 голосов
/ 29 декабря 2011

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

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

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

0 голосов
/ 29 декабря 2011

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

По крайней мере, так я это и выучил.

И если вы находите какой-либо случай особенно сложным, пишите в SO!

...