当我们创建一个类的对象时,它的内存映射是什么样的。我对对象如何调用非虚拟成员函数更感兴趣。编译器是否创建了一个像vtable一样的表,它在所有对象之间共享?
class A
{
public:
void f0() {}
int int_in_b1;
};
A * a = new A;
?
的记忆图是什么?答案 0 :(得分:13)
您可以想象这段代码:
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:解析器没有被解决(这应该是虚拟的这里,它泄漏内存, __ func __ 值略有不同(它们由编译器为当前函数的名称生成)等等。
答案 1 :(得分:3)
认识到C ++语言没有指定或强制关于对象的内存布局的所有内容。也就是说,大多数编译器都是这样做的。
在您的示例中,类型A的对象只需要足够的内存来容纳int
。由于它没有虚函数,因此不需要vtable。如果f0
成员已被声明为虚拟,那么类型A的对象通常以指向类A vtable(由类型A的所有对象共享)的指针开始,后跟int成员。
反过来,vtable有一个指向每个虚拟函数的指针,已定义,继承或覆盖。为对象调用虚函数包括跟随指向对象的vtable的指针,然后使用固定的偏移量进入vtable(在编译时为每个虚函数确定),以找到要调用的函数的地址。
答案 2 :(得分:1)
函数不会根据它们所在的类存储。
通常编译器会像任何其他函数一样处理任何成员函数,除了为'this'指针添加一个参数。当你根据被调用对象的地址调用它时,它会自动传递给函数。
所有函数,静态,成员甚至虚拟成员都以相同的方式存储在内存中,它们都只是函数。
当编译器构建代码时,它几乎很难编码进入内存的位置,然后链接器遍历您的代码并用“在此硬编码地址调用函数”替换“使用此名称调用函数”命令
答案 3 :(得分:0)
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”来验证(带有错位的命名结果)