假设我们有一个班级:
class Foo
{
private:
int a;
public:
void func()
{
a = 0;
printf("In Func\n");
}
}
int main()
{
Foo *foo = new Foo();
foo->func();
return 0;
}
当创建并初始化类Foo的对象时,我理解整数a将占用4个字节的内存。该功能如何存储?调用foo-> func()时,内存/堆栈/寄存器/程序计数器会发生什么?
答案 0 :(得分:15)
简答:无论创建的类的实例数是多少,它都只存储在二进制文本或代码部分中一次。
对于每个类的实例,函数不会单独存储在任何位置。它们的处理方式与任何其他非成员函数的处理方式相同。唯一的区别是编译器实际上向函数添加了一个额外的参数,这是类类型的指针。 例如,编译器将生成如下函数原型:
void func(Foo* this);
(请注意,这可能不是最终签名。根据包括编译器在内的各种因素,最终签名可能更加神秘)
对成员变量的任何引用都将替换为
this-><member> //for your ex. a=0 translates to this->a = 0;
所以行foo-&gt; func();大致翻译为:
答案 1 :(得分:10)
您的函数不是虚函数,因此静态调用:编译器将跳转插入与您的函数对应的代码段。每个实例不使用额外的内存。
如果你的函数是虚拟的,你的实例将携带一个vpointer,它将被取消引用以找到它的类'vtable,然后将其编入索引以找到要调用的函数指针,最后跳转到那里。因此额外的成本是每个类一个vtable(可能是一个函数指针的大小,乘以你的类的虚函数的数量),每个实例有一个指针。
请注意,这是虚拟调用的常见实现,但标准并没有强制执行,因此它实际上根本不能实现,但您的机会非常好。如果编译时知道了实例的静态类型,编译器通常也可以完全绕过虚拟调用系统。
答案 2 :(得分:2)
成员函数就像常规函数一样,它们存储在“代码”或“文本”部分中。 (非静态)成员函数有一个特殊之处,那就是传递给函数的“隐藏”this
参数。因此,在您的情况下,foo
中的地址将传递给func
。
该参数究竟是如何传递的,寄存器和堆栈的变化由ABI(应用程序二进制接口)定义,并且因处理器而异。除非您告诉我们编译器,操作系统和处理器的组合正在使用(并假设信息随后公开可用 - 并非所有编译器/操作系统供应商都会非常清楚地说明这一点),因此没有严格的定义。例如,x86-64将在WIndows上使用RCX
表示this
,在Linux上使用RDI
,并且调用指令会自动将返回地址压入堆栈。在ARM处理器上[在Linux中,但我认为在Windows中同样适用,我从来没有看过],R0用于this
指针,以及用于调用的BX指令,作为一部分它本身存储lr
并返回指令的pc
。然后必须lr
中func
保存[{1}},因为它会调用printf
。