2011-12-29 13 views
11

Chciałbym dokładnie wiedzieć, w jaki sposób zajęcia będą układane w pamięci esp. z dziedziczeniem i funkcjami wirtualnymi.układ pamięci odziedziczonej klasy

Wiem, że nie jest to zdefiniowane przez standard języka C++. Czy istnieje jednak prosty sposób, aby dowiedzieć się, w jaki sposób twój konkretny kompilator wdroży te słowa, pisząc jakiś kod testowy?

EDIT: - Korzystanie niektóre z poniższych odpowiedzi: -

#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 wyjście jest: -

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

Więc wszystkie klasy posiadają V-ptr w Loc 0 wielkości 8 D ma inny v-ptr w miejscu 16. Podobnie jak w przypadku E. G wydaje się mieć również v-ptr w wieku 16 lat, chociaż z mojego (ograniczonego) zrozumienia domyśliłbym się, że ma więcej.

Odpowiedz

9

Jednym sposobem jest wydrukowanie przesunięcia wszystkich członków:

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"); 
} 

wyjściowa:

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 

Więc można zobaczyć wszystkie offsety tutaj. Zauważysz, że nie ma nic w offsecie 0, ponieważ prawdopodobnie jest tam wskaźnik do vtable.

Zauważ również, że odziedziczone elementy mają takie same przesunięcia zarówno w systemie Child, jak i Parent.

+1

+1 Ten przykładowy kod jest najbliższy temu, co wymyśliłem. Po prostu staraj się unikać polegania na układzie pamięci. Nie ma gwarancji, że pozostanie taki sam w przyszłych wersjach kompilatora (lub nawet w innych kontekstach z tą samą wersją kompilatora, np. Optymalizacją). Założę się, że prawie zawsze istnieje lepszy sposób na rozwiązanie problemu. – Andre

+0

Dzięki. To trochę pomaga. Łącząc twoją odpowiedź z Azzą ... Byłem również zainteresowany tym, co by się stało, gdybyśmy mieli klasę A: ; klasa B; klasa C: publiczna A, publiczna B; Wydaje się, że daje to wynik, gdy istnieje wiele wskazówek do vtable. Członkowie danych A wydają się być pierwsi przed B. – owagh

+0

Nigdy nie zajmowałem się dziedziczeniem wielokrotnym. Ale nadal możesz spróbować zobaczyć, co pokazują przesunięcia. Naprawdę nie mam pojęcia, jak wiele dziedziczenia działa pod spodem. – Mysticial

7

Visual Studio cone ma hidden compiler option/d1reportSingleClassLayout (od około 32: 00).

Użycie: /d1reportSingleClassLayoutCLASSNAME gdzie nie będzie żadnych spacji między przełącznikiem kompilatora a CLASSNAME (oczywiście należy zastąpić to nazwą klasy, która Cię interesuje).

+0

Hej, to jest wspaniałe i dokładnie to, czego chciałem. Czy są podobne funkcje dla innych kompilatorów, takich jak gcc, icc itd.? – owagh

+0

@owagh: Przepraszam, że nie wiem. :/ – Xeo

+0

@owagh Dla gcc, nie wydaje się to być idealnym odpowiednikiem, ale może nadal może być użyteczny: http://stackoverflow.com/questions/15951597/what-is-the-linux-aquivalent-to- msvcs-option-d1reportsingleclasslayout (short: skompiluj w trybie debugowania za pomocą opcji '-g' lub' -ggdb', a następnie użyj narzędzia 'pahole' w pliku obiektowym). –

0

Najlepszym sposobem jest najprawdopodobniej napisanie kilku prostych przypadków testowych, a następnie skompilowanie i debugowanie ich w asembler (wyłączenie wszystkich optymalizacji): uruchamianie jednej instrukcji w tym samym czasie zobaczysz, gdzie wszystko pasuje.

Przynajmniej tak się tego nauczyłem.

A jeśli uznasz, że jakaś sprawa jest szczególnie trudna, wyślij ją w SO!

0

Dopóki trzymasz się pojedynczego dziedziczenia, podobiekty są zwykle układane w kolejności, w jakiej są zadeklarowane. Wskaźnik jest poprzedzony informacją o typie z przodu, np. używany do dynamicznej wysyłki. Po zakwalifikowaniu wielu dziedzin rzeczy stają się bardziej złożone, szczególnie gdy chodzi o dziedziczenie wirtualne.

Aby znaleźć dokładne informacje przynajmniej dla jednego aromatu ABI, możesz poszukać Itanium ABI. Dokumentuje wszystkie te szczegóły. Jest używany jako C++ ABI przynajmniej na niektórych platformach Linux (tzn. Wiele kompilatorów może tworzyć pliki obiektowe połączone w jeden plik wykonywalny).

Aby określić układ, wystarczy wydrukować adresy podobiektów obiektu podanego. To powiedziawszy, chyba że zaimplementujesz kompilator, zwykle nie ma to znaczenia. Jedynym realnym zastosowaniem układu obiektu, który mam podwojeniem, jest aranżacja elementów w celu zminimalizowania dopełnienia.

+0

Na pewno nie dodano _vptr_, jeśli nie ma funkcji "wirtualnych"? Próbuję uzyskać rozstrzygającą odpowiedź na pytanie, czy pojedyncze dziedziczenie POD, bez niczego "wirtualnego", spowoduje dokładną kolejność członków, tj. "Base.m1, base.m2, pochodna.m1, pochodna.m2". Wiem, że mogę po prostu wypróbować to i zobaczyć, czy działa na mojej implementacji, ale naprawdę próbuję znaleźć gwarantowany międzyplatformowy sposób, by to zrobić. (Tło polega na tym, że mapuję do formatów binarnych, w których kolejność jest krytyczna). –

1

Utwórz obiekt klasy, przesyłając go do słowa maszyny, użyj sizeof, aby znaleźć rozmiar obiektu i sprawdź pamięć w miejscu. Coś takiego:

#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 

Na marginesie, Stanley B. Lippman ma doskonałą książkę "Inside the C++ Object Model".

Powiązane problemy