转载:C++对象内存模型2 (虚函数,虚指针,虚函数表)

C分为四个区:堆,栈,静态全局变量区,常量区

C++内存分为5个区域(堆栈全常代 ):

  1. 堆 heap :
    由new分配的内存块,其释放编译器不去管,由我们程序自己控制(一个new对应一个delete)。如果程序员没有释放掉,在程序结束时OS会自动回收。涉及的问题:“缓冲区溢出”、“内存泄露”

  2. 栈 stack :
    是那些编译器在需要时分配,在不需要时自动清除的存储区。存放局部变量、函数参数。
    存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也就自动释放了。

  3. 全局/静态存储区 (.bss段和.data段) :
    全局和静态变量被分配到同一块内存中。在C语言中,未初始化的放在.bss段中,初始化的放在.data段中;在C++里则不区分了。

  4. 常量存储区 (.rodata段) :
    存放常量,不允许修改(通过非正当手段也可以修改)

  5. 代码区 (.text段) :
    存放代码(如函数),不允许修改(类似常量存储区),但可以执行(不同于常量存储区)

根据c/c++对象生命周期不同,c/c++的内存模型有三种不同的内存区域,即 自由存储区,动态区、静态区。

  1. 自由存储区:局部非静态变量的存储区域,即平常所说的栈
  2. 动态区: 用operator new ,malloc分配的内存,即平常所说的堆
  3. 静态区:全局变量 静态变量 字符串常量存在位置

而代码虽然占内存,但不属于c/c++内存模型的一部分

 

从例子入手,考察如下带有虚函数的类的对象内存模型:

class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
virtual ~A();
private:
int m_data1, m_data2;
};

class B : A {
public:
virtual void vfunc1();;
void func2();
virtual ~B();
private:
int m_data3;
};

class C : B {
public:
virtual void vfunc1();
void func();
private:
int m_data1, m_data4;
};

注:在子类中出现与父类相同名称的变量和非虚函数不是最佳实践,这里是为了说明其内存结构。

其对象内存结构见下图。

                                               *图片来源于侯捷老师

对其分析如下:

1. 每个含有虚函数的类在内存中多一根指针(vptr),见图中a,b,c对象中第一个位置,存储的是虚函数表(vtbl)所在的位置。

2. 虚函数表(vtbl)存储着所有虚函数的位置,由于其动态绑定特性,在覆写(override)后在子类中存储的虚函数位置与父类中不相同。

3. 分析上述代码, B继承A,所以A中的数据部分也被B继承下来,同时B添加上了自己的数据部分m_data3,加之vptr,组成了B左侧的内存布局。

  A中的虚函数vfunc1(),vfunc2()可以被覆写和动态绑定。

  所以在B中,vfunc1()被覆写,其vtbl中对应项指向了新的函数的位置(亮蓝色)。vfunc2()未被覆写,仍然指向原先位置(深蓝色)。

  C与B同理,vfunc1()被覆写,其vtbl中对应项指向了新的函数的位置(橘黄色)。vfunc2()未被覆写,仍然指向原先位置(深蓝色)。

非虚函数静态绑定,存储在单独的内存空间(code memory section,灰色函数部分),调用时把对象的this指针,传给一个invisible参数,以便确定谁在调用函数。

4. 调用虚函数的语句的C语言形式如图中下部分所示,其中n表示对应的函数在第几个位置(编译器在建立虚函数表的时候已知),从而实现动态绑定。

 

文章来源:http://www.cnblogs.com/Stultz-Lee/p/6751522.html & https://www.cnblogs.com/wangxiaobao/p/5850949.html

posted @ 2019-02-16 11:26  Daniel.L  阅读(120)  评论(0)    收藏  举报