类成员和成员函数的内存位置

时间:2014-03-01 19:39:37

标签: c++ pointers memory memory-management

这是一个简单的C ++类:

class A
{
public:
    explicit A() : m_a(0) { }
    explicit A(int a) m_a(a) { }
    int getA() const { return m_a; }
    void setA(int a) { m_a = a; }

private:
    int m_a;
}

这是我目前所知道的:

  • 当您声明类实例的对象时,将为该对象分配内存。分配的内存相当于其成员的内存总结。所以在我的案例中sizeof(A) = sizeof(int) = sizeof(m_a)
  • A类的所有成员函数都存储在内存中,而A类的所有实例都使用相同的成员函数。

这是我知道:

成员函数存储在哪里以及它们实际存储的方式?让我们说例如int存储在4个字节上;我可以想象RAM内存布局有4个连续的单元格,每个单元格存储一部分int。我怎么能想象一个函数的这个布局?(这听起来很傻,但我想函数必须在内存中占有一席之地,因为你可以指向它们的指针)。还存储了功能指令的方式和位置?我的第一个看法是函数和函数指令存储在程序可执行文件(及其动态或静态库)中,但如果这是真的,那么在创建函数指针时会发生什么? AFAIK函数指针指向RAM内存中的位置,它们是否可以指向程序二进制文件中的位置?如果是的话,这是如何工作的?

任何人都可以向我解释这是如何运作的,并指出我所知道的是对还是错?

3 个答案:

答案 0 :(得分:11)

首先,您需要了解linker的作用以及executables的内容(通常在virtual memory中执行)和address spaces& processes。在Linux上,阅读ELFexecve(2)系统调用。另请阅读Levine的Linkers & Loaders书和Operating Systems: Three Easy Pieces

成员函数可以是虚函数或普通函数。

  • 普通(非virtual)成员函数就像一个C函数(除了它有this作为隐式的,通常是第一个参数)。例如,您的getA方法实现类似于以下C函数(在对象之外,例如在二进制可执行文件的code segment中):

    int C$getA(A*thisptr) const { return thisptr->m_a; }
    

    然后想象编译器正在将p->getA()翻译成C$getA(p)

  • virtual member function通常通过 vtable virtual method table)实现。具有一些虚拟成员函数(包括析构函数)的对象通常作为其第一个(隐式)成员字段指向这样的表(由编译器在别处生成)。您的class A没有任何虚拟方法,但想象一下,如果它有一个额外的virtual void print(std::ostream&);方法,那么您的class A将具有与

    相同的布局
    struct A$ {
       struct A$virtualmethodtable* _vptr;
       int m_a;
    };
    

    ,虚拟表可能是

    struct A$virtualmethodtable {
      void (*print$fun) (struct A$*, std::ostream*);
    };
    

    (所以添加其他虚拟功能意味着只需在 vtable 中添加插槽); 然后像p->print(std::cout);这样的电话会被翻译成几乎像 p->_vptr.print$fun(p,&std::cout); ...此外,编译器会生成各种虚方法表(每个类一个)作为常量表。

注意:对于多重或虚拟继承,事情会更复杂。

在这两种情况下,成员函数都不会占用对象中的任何额外空间。如果它是非虚拟的,它只是一个普通函数(在代码段中)。如果是虚拟的,则它在虚拟方法表中共享一个插槽。

NB。如果您使用最近的GCC(即使用g++)进行编译,则可以通过它,例如-fdump-tree-all标志:它将生成数百个转储文件,部分显示转储的文本形式 - 编译器的一些内部表示,您可以使用寻呼机(例如less)或文本编辑器进行检查。您也可以使用MELT或查看使用g++ -S -fverbose-asm -O1 ....

生成的汇编代码

答案 1 :(得分:0)

所有本地非静态变量和非虚函数都保存在代码/文本段中。

所有静态变量和全局变量都保存在静态数据段中。

具有虚拟功能的类或从具有虚拟功能的类继承的类将由编译器插入vptr指针。 Vptr指向具有功能插槽数量的虚拟功能表。每个插槽包含存储在代码段中的功能地址。

答案 2 :(得分:-2)

要理解这一点,您需要了解程序的内存布局。代码将由对象共享。并且所有对象都有自己的数据副本。