当我们创建一个类的对象时,它的内存映射是什么样子的?我更感兴趣的是对象如何调用非虚拟成员函数。编译器是否会创建一个类似于vtable的表格,该表格在所有对象之间共享?
class A
{
public:
void f0() {}
int int_in_b1;
};
A * a = new A;
a的内存映射将是什么?
当我们创建一个类的对象时,它的内存映射是什么样子的?我更感兴趣的是对象如何调用非虚拟成员函数。编译器是否会创建一个类似于vtable的表格,该表格在所有对象之间共享?
class A
{
public:
void f0() {}
int int_in_b1;
};
A * a = new A;
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会增长,但这是存储在每个类中一次,不会占用重要的空间开销。)请注意,在此示例中,我已经简化了许多细节,但它确实有效:析构函数未被处理(此处应该另外是虚拟的),它会泄漏内存,并且 __func__ 值将略有不同(它们由编译器生成以当前函数名称为基础)。还有其他问题。
函数不是按照它们所在的类进行存储的。
通常编译器会将任何成员函数视为其他函数一样处理,只是会添加一个“this”指针参数。当您调用该函数时,该指针会自动传递到基于所调用对象的地址的函数中。
所有函数(静态函数、成员函数或虚拟成员函数)都以相同的方式存储在内存中,它们只是函数而已。
当编译器构建代码时,它几乎会硬编码它在内存中的位置,然后链接器会通过您的代码并将“调用该名称的函数”命令替换为“调用该硬编码地址处的函数”。
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;
}