C++类的内存结构
⽬录
代码与可执⾏⽂件
代码段,数据段,BSS段,堆、栈
代码段:简单说就是存储函数与常量的地⽅。C/C++写的成员函数,⾮成员函数都是在这⾥。
数据段:初始化的全局变量,初始化的静态变量被编译器放在这⾥
BSS 段: 这⾥存放未初始化的全局变量,未初始化的静态变量。BSS 部分并不占据存储空间,编译器只是把这些为初始化的全局/静态变量记录在这⾥。内存空间要等到执⾏阶段由系统分配,并完成初始化!这也是为什么内存结构上BSS 部分在栈空间下⾯的原因。
堆、栈:这个⽐较好理解,不做介绍了
⼀个⼩c 程序demo
#include <stdio.h>
int global_init = 1;
int global_uinit;
static int global_static_init = 1;
static int global_static_uinit;
int main()
{
int local_init = 1;
int local_uinit;
static int local_static_init = 1;
static int local_static_uinit;
return 0;
}
⽤ nm 查看结果:其中 D、d 都是表⽰ data 段; B、b 表⽰BSS 段。详细介绍参考man nm
C++ 对象与存储
虚指针理解为⼀个 int64_t* 的数组,每个数组成员都是函数指针
先说明⼏点:
空类⼤⼩不为 0
静态类成员函数与类绑定,并不会被注⼊ this 指针
因此静态成员函数可以通过类名直接调⽤,不需要创建类对象。同样因为没有 this 指针,所以静态成员函数也不能调⽤普通成员函数,只能访问静态成员变量。
普通成员函数为类全局共享,不与类实例绑定
但是普通成员函数的调⽤需要绑定实例,这是因为普通成员函数 this 指针的存在; 也因为普通成员函数绑定的不是类实例,所以普通继承关系不具有多态,⽽是由指针决定。
虚表为类全局共享,不与类实例绑定; 虚指针与类实例绑定
虚表是全局存在的,相当于⼀个全局变量,⽽不是每个类实例都创建⼀个虚表,虚表只能通过虚指针来访问。
虚指针在Linux G++/Clang++ 实现是放在类的最前⾯。须指针指向虚表的操作由构造函数初始化。
成员函数不占⽤类的内存空间
即 new ⼀个对象只是创建了对象的数据部分,并不包含函数部分
类的实际内存结构如下:
虚表与虚指针
说明:
1. 虚表指针总是存在在类的头部,并按类的继承顺序排放。⼀个⼦类可以有多个虚表指针,且虚指针个数和具有虚函数的基类个数相
同。
2. 虚成员函数总是按照声明顺序存在于虚表中。
3. 如果存在同名函数,⼦类虚函数会覆盖每⼀个⽗类的每⼀个同名虚函数。
4. ⼦类独有的虚函数填⼊第⼀个虚函数表中,且⽤⽗类指针是不能调⽤。
5. ⽗类独有的虚函数不会被覆盖覆盖。仅⼦类和该⽗类指针能调⽤。
如下图类的内存结构图
⽆虚函数
class Drive
{
public:
void f() {}
};
int main()
{
Drive d;
cout << sizeof(d) << endl;
return 0;
}
如下,类中没有虚函数,只有⼀个成员函数,以及其他默认构造析构函数。类似于空类,类的⼤⼩为1(注意空类⼤⼩不为0,因为为0的话,实例化后没法区分)。因此可以的出结论类的⾮虚成员函数信息不存在于对象实例中!
⽆继承
代码:
class Drive
{
public:
virtual void vf() {}
void f() {}
};
int main()
{
Drive d;
return 0;
}
如下,⼦类经过强制类型转换,得到虚表指针,并提取虚表指针的内容,经过转换可以得到第⼀个虚函数。虚表中只有⼀个虚函数。
单继承
class Base1
{
public:
virtual void vb1f() {}
virtual void vf() {}
};
class Drive : public Base1
{
public:
virtual void vdf() {}
virtual void vf() {}
void f() {}
};
int main()
{
Drive d;
return 0;
}
虚表中只有多个虚函数。顺序是⽗类,⼦类的顺序。其中注意到双⽅共有的虚函数 “vf”,在虚表中⼦类的虚函数覆盖了⽗类的需函数。
指针调用成员函数多继承
class Base1
{
public:
virtual void vb1f() {}
virtual void vf() {}
};
class Base2
{
public:
virtual void vb2f() {}
virtual void vf() {}
};
class Drive : public Base1, Base2
{
public:
virtual void vdf() {}
virtual void vf() {}
void f() {}
};
int main()
{
Drive d;
return 0;
}
虚表中只有多个虚函数。顺序是⽗类Base1, ⽗类Base2,⼦类。
查看第⼀个虚表:其中注意到双⽅共有的虚函数 “vf”,在虚表中⼦类的虚函数覆盖了⽗类的需函数。另外⼦类的虚函数 ”vdf“ 被放在了第⼀个虚表的后⾯。
查看第⼆个虚表:第⼆个虚表指针在地⼀个虚表指针后⾯。同样⽅式可以看到第⼆个虚表只有⽗类 Base2 的虚成员函数,⽽且共有的虚函数被⼦类的虚函数 vf 覆盖。
虚继承(菱形继承)
单虚继承情况和单继承完全⼀样,这⾥忽略,直接描述虚继承的菱形继承情况