C++内存布局(一)

发布时间:2023年12月20日

温故而知新,本文浅聊和回顾下C++内存布局的知识。

一、c++内存布局

C++的内存布局主要包括以下几个部分:

  • 代码段:存储程序的机器代码。
  • .数据段:存储全局变量和静态变量。数据段又分为初始化数据段(存储初始化的全局变量和静态变量)和未初始化数据段(存储未初始化的全局变量和静态变量)。
  • :用于动态内存分配。当你使用new或malloc函数时,内存会从堆中分配。
  • :用于存储局部变量和函数调用的信息(例如返回地址和参数)。当你调用一个函数时,一个新的栈帧会被压入栈,当函数返回时,这个栈帧会被弹出。
  • 常量段:存储常量字符串和其他常量。

代码示例

#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++ 类的内存布局

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) 
  • 成员函数:在C++中,成员函数并不直接存储在每个对象中。相反,所有对象共享同一个成员函数的副本。成员函数的代码存储在代码段,而不是每个对象的内存空间。因此,成员函数不影响类的sizeof大小。
    当你调用一个对象的成员函数时,编译器会自动将对象的地址作为隐藏参数传递给成员函数。这个隐藏参数通常被称为this指针。通过this指针,成员函数可以访问调用它的对象的数据成员。
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";

在这里插入图片描述

文章来源:https://blog.csdn.net/weixin_39568531/article/details/135067466
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。