C ++类对象内存映射

时间:2010-03-11 06:14:55

标签: c++

当我们创建一个类的对象时,它的内存映射是什么样的。我对对象如何调用非虚拟成员函数更感兴趣。编译器是否创建了一个像vtable一样的表,它在所有对象之间共享?

class A
{
public:
  void f0() {}
  int int_in_b1;
};

A * a = new A;

的记忆图是什么?

4 个答案:

答案 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”来验证(带有错位的命名结果)