存储在内存中的对象的函数在哪里

Where are functions of an object stored in memory?

本文关键字:对象 在哪里 函数 内存 存储      更新时间:2023-10-16

假设我们有一个类:

class Foo
{
private:
    int a;
public:
    void func()
    {
       a = 0;
       printf("In Funcn");
    }
}
int main()
{
    Foo *foo = new Foo();
    foo->func();
    return 0;
}

当Foo类的对象被创建和初始化时,我知道整数a将占用4个字节的内存。函数是如何存储的?调用foo->func((时,内存/堆栈/寄存器/程序计数器中会发生什么?

简短的答案是:无论创建的类的实例数量如何,它都将只存储在二进制文件的文本或代码部分一次。

对于类的每个实例,函数不会单独存储在任何位置。它们的处理方式与其他任何非成员函数相同。唯一的区别是编译器实际上向函数添加了一个额外的参数,即类类型的指针。例如,编译器将生成如下的函数原型:

void func(Foo* this);

(请注意,这可能不是最终签名。最终签名可能更神秘,这取决于包括编译器在内的各种因素(

对成员变量的任何引用都将被取代

this-><member> //for your ex. a=0 translates to this->a = 0;

因此,行foo->func((;大致翻译为:

  1. 将Foo*的值推送到堆栈中#编译器相关
  2. 调用func,这将导致指令指针跳转到可执行文件中func的偏移量#依赖于体系结构读取this和this
  3. Func将从堆栈中弹出该值。对成员变量的任何进一步引用之前都将取消对该值的引用

您的函数不是虚拟的,因此它是静态调用的:编译器插入到与您的函数对应的代码段的跳转。每个实例不使用额外的内存。

如果你的函数是虚拟的,你的实例会携带一个vpointer,它会被取消引用以找到它的类"vtable",然后它会被索引以找到要调用的函数指针,最后跳到那里。因此,额外的成本是每个类一个vtable(可能是一个函数指针的大小,乘以类的虚拟函数的数量(,每个实例一个指针。

请注意,这是虚拟调用的常见实现,但标准并没有强制执行,因此它实际上根本不可能像那样实现,但您的机会非常大。如果编译器在编译时知道实例的静态类型,那么它通常也可以完全绕过虚拟调用系统。

成员函数就像常规函数一样,它们存储在"代码"或"文本"部分。(非静态(成员函数有一点特别,那就是传递给函数的"隐藏"this参数。因此,在您的情况下,foo中的地址将传递给func

该参数的确切传递方式,以及寄存器和堆栈的情况由ABI(应用程序二进制接口(定义,并且因处理器而异。对此没有严格的定义,除非你告诉我们正在使用编译器、操作系统和处理器的组合(并假设信息是公开的——并不是所有的编译器/操作系统供应商都会非常清楚地告诉我们(。例如,x86-64将在WIndows上使用RCX作为this,在Linux上使用RDI,调用指令将自动将返回地址推送到堆栈上。在ARM处理器上[在Linux中,但我认为Windows中也是如此,我只是从未看过],R0用于this指针,BX指令用于调用,作为其本身的一部分,它将lr与要返回的指令的pc存储在一起。然后,lr必须保存在func中[可能在堆栈上],因为它调用printf