温故而知新,本文浅聊和回顾下C++内存布局的知识。
C++的内存布局主要包括以下几个部分:
#include <iostream>
int global_var = 0; // 初始化的全局变量,存储在初始化数据段
int uninit_global_var; // 未初始化的全局变量,存储在未初始化数据段
void foo() {
int local_var = 0; // 局部变量,存储在栈
static int static_local_var = 0; // 静态局部变量,存储在初始化数据段
int* dynamic_var = new int(0); // 动态分配的内存,地址在堆,dynamic_var指针变量的生命周期是foo函数栈
std::cout << "local_var: " << &local_var << std::endl;
std::cout << "static_local_var: " << &static_local_var << std::endl;
std::cout << "dynamic_var: " << dynamic_var << std::endl;
delete dynamic_var; // 释放动态分配的内存
}
int main() {
std::cout << "global_var: " << &global_var << std::endl;
std::cout << "uninit_global_var: " << &uninit_global_var << std::endl;
foo();
return 0;
}
C++类的内存布局主要取决于类的数据成员和继承关系。以下是一些基本的规则:
// x64
#pragma pack(push,4) //指定4字节对齐
class TmpClass{}; // 空类sizeof,大小为1
class NoVirtual
{
public:
int m_i; // 4字节
double m_d; // 8字节
shared_ptr<int> m_ptr; // 8字节 ==》64bit system ;4字节 ==》 32bit system
};
#pragma pack(pop)
sizeof
大小。class NoVirtual
{
void dc(){} // 成员函数,内存在代码段
public:
int m_i; // 4字节
double m_d; // 8字节
shared_ptr<int> m_ptr; // 8字节 ==》64bit system ;4字节 ==》 32bit system
};
静态成员变量:静态成员变量不属于类的任何一个对象,它们在所有对象之间共享。静态成员变量存储在全局数据段,而不是对象的内存空间。
静态成员函数:静态成员函数也不属于类的任何一个对象。它们没有this指针,因此不能访问类的非静态成员。静态成员函数的地址存储在代码段。
继承:如果一个类继承自一个或多个基类,那么基类的数据成员会先于派生类的数据成员存储在内存中。如果有多个基类,那么基类的数据成员按照它们在类定义中的顺序存储。
class Iface
{
public:
Iface(){MYTRACE();}
virtual ~Iface(){MYTRACE();}
virtual void Ifun() = 0;
};
// 继承
class MemLayout : public Iface
{
public:
MemLayout(){ MYTRACE(); }
~MemLayout(){ MYTRACE(); }
virtual void Ifun() override { MYTRACE(); }
virtual void dc0(){ MYTRACE(); }
virtual void dc1(){ MYTRACE(); }
private:
int m_num = 0;
static std::string m_desc;
};
std::string MemLayout::m_desc = "hello";
virtual
关键字修饰),那么编译器会为这个类生成一个虚函数表(vtable
: 函数指针数组),并在每个对象中添加一个指向虚函数表的指针(vptr
)。虚函数表中存储了虚函数的地址。如果一个类继承自一个有虚函数的基类,那么它会继承基类的虚函数表。class VirtualClass
{
virtual void dc(){}
public:
int m_i; // 4字节
double m_d; // 8字节
shared_ptr<int> m_ptr; // 8字节 ==》64bit system ;4字节 ==》 32bit system
};
vbtable
),并在每个对象中添加一个指向虚基类表的指针。虚基类表中存储了虚基类的偏移量。*直白点说,虚继承的派生类的实例化对象,会包含多张虚函数表的(下面有图为证)~,具体有几个vptr
,我们会在《C++内存布局(二)》中详细研究下,尤其是多重继承和钻石继承的场景下。class NoVirtual
{
void dc(){}
public:
int m_i; // 4字节
double m_d; // 8字节
shared_ptr<int> m_ptr; // 8字节 ==》64bit system ;4字节 ==》 32bit system
};
class Iface
{
public:
Iface(){MYTRACE();}
virtual ~Iface(){MYTRACE();}
virtual void Ifun() = 0;
};
// 虚继承
class MemLayout : virtual public Iface
{
public:
MemLayout(){ MYTRACE(); }
~MemLayout(){ MYTRACE(); }
virtual void Ifun() override { MYTRACE(); }
virtual void dc0(){ MYTRACE(); }
virtual void dc1(){ MYTRACE(); }
private:
int m_num = 0;
static std::string m_desc;
};
std::string MemLayout::m_desc = "hello";