當我們創建一個類的對象時,它的內存映射是什麼樣的。我更關心對象如何調用非虛擬成員函數。編譯器是否創建了像所有對象之間共享的vtable這樣的表?C++類對象內存映射
class A
{
public:
void f0() {}
int int_in_b1;
};
A * a = new A;
什麼是內存映射?
當我們創建一個類的對象時,它的內存映射是什麼樣的。我更關心對象如何調用非虛擬成員函數。編譯器是否創建了像所有對象之間共享的vtable這樣的表?C++類對象內存映射
class A
{
public:
void f0() {}
int int_in_b1;
};
A * a = new A;
什麼是內存映射?
你能想象這樣的代碼:
struct A {
void f() {}
int int_in_b1;
};
int main() {
A a;
a.f();
return 0;
}
被改造成類似:
struct A {
int int_in_b1;
};
void A__f(A* const this) {}
int main() {
A a;
A__f(&a);
return 0;
}
調用f是非常簡單的,因爲它是非虛擬的。 (有時對於虛擬調用,如果對象的動態類型是已知的,則虛擬調度可以避免,因爲它在這裏。)
較長的例子,要麼給你的功能如何虛功或可怕的想法迷惑你:
struct B {
virtual void foo() { puts(__func__); }
};
struct D : B {
virtual void foo() { puts(__func__); }
};
int main() {
B* a[] = { new B(), new D() };
a[0]->foo();
a[1]->foo();
return 0;
}
變爲類似:
void B_foo(void) { puts(__func__); }
void D_foo(void) { puts(__func__); }
struct B_VT {
void (*foo)(void);
}
B_vtable = { B_foo },
D_vtable = { D_foo };
typedef struct B {
struct B_VT* vt;
} B;
B* new_B(void) {
B* p = malloc(sizeof(B));
p->vt = &B_vtable;
return p;
}
typedef struct D {
struct B_VT* vt;
} D;
D* new_D(void) {
D* p = malloc(sizeof(D));
p->vt = &D_vtable;
return p;
}
int main() {
B* a[] = {new_B(), new_D()};
a[0]->vt->foo();
a[1]->vt->foo();
return 0;
}
每個對象只一個vtable指針,並且您可以將多個虛擬方法添加到該類中,而不會影響對象大小。 (vtable增長,但是每個類存儲一次,並且不佔用大量開銷。)請注意,在此示例中,我簡化了許多細節,但does work:析構函數沒有解決(它應該另外虛擬於此)泄漏內存,並且值將會略有不同(它們由編譯器爲當前函數的名稱生成)等等。
第二個例子是我寫過幾個星期的舊書,現在我發現我忘了添加* this *指針,即使它們沒有被使用。如果你看不到如何添加它們,只需告訴我,我可以編輯;否則我會保持它與編碼板鏈接中編譯的代碼相同。 – 2010-03-11 07:10:29
認識到C++語言沒有指定或強制對象的所有內存佈局。也就是說,大多數編譯器都做得差不多。
在您的示例中,類型A的對象只需要足夠的內存來存放int
。由於它沒有虛函數,所以不需要vtable。如果f0
成員已被聲明爲虛擬的,則類型A的對象通常以指向類A vtable的指針(由所有類型爲A的對象共享)開始,後跟int成員。
反過來,vtable有一個指向每個虛函數的指針,定義,繼承或覆蓋。調用一個對象的虛擬函數包括跟蹤從對象到vtable的指針,然後在vtable中使用一個固定偏移量(在編譯時爲每個虛函數確定)來查找要調用的函數的地址。
class A
{
public:
void f0() {}
void f1(int x) {int_in_b1 = x; }
int int_in_b1;
};
A *a = new A();
在內部實現(表示)是這樣的:(功能名實際上錯位)
struct A
{
int int_in_b1;
};
void Class_A__constructor(struct a*) {} // default constructor
void Class_A__f0(struct a*) {}
void Class_A__f1(struct a*, int x) {a->int_in_b1 = x;}
// new is translated like this: (inline)
void* new() {
void* addr = malloc(sizeof(struc a));
Class_A__constructor(addr);
return addr;
}
它可以通過執行在對象文件中的命令「nm」是被驗證(有錯位的命名導致)
您從問題中複製了'A a = new A();'錯誤。 – 2010-03-11 06:59:17
@Roger:謝謝,我沒有注意到 – Phong 2010-03-11 07:30:01
功能不存儲基於他們在什麼課。
通常編譯器將只把任何成員函數類似,只是任何其他功能爲「this」指針添加一個參數。當您根據調用的對象的地址調用該函數時,該函數將自動傳遞給該函數。
所有函數,靜態,成員甚至虛擬成員都以相同的方式存儲在內存中,它們都只是函數。
當編譯器構建代碼時,它會將代碼放到內存中,然後鏈接器會遍歷代碼並用「在此硬編碼地址中調用函數」替換「用此名稱調用函數」命令「
如果您想要如何建模C++對象(我說可以,因爲有多種方法來實現C++內部),我推薦Stanley Lippman的'Inside the C++ Object Model'。 – 2010-03-11 06:59:40
如果你更正了你的代碼,爲什麼不用編譯器輸出運行你的編譯器,並看看它產生了什麼? – 2010-07-04 12:23:29