C++类的内存对齐

C++类的内存对齐

重点

  1. 内存对齐的定义
  2. 为什么需要内存对齐
  3. C++内存结构
  4. 类的对齐方式
  5. 带有成员函数、静态变量、虚函数的类的内存分布

内存对齐定义

什么是内存对齐:

内存对齐是从硬件层面出现的概念。可执行程序是由一系列CPU指令构成的,其中有一些指令是需要访问内存的。在很多CPU架构下,这些指令都要求操作的内存地址(更准确的说,操作内存的起始地址)能够被操作的内存大小整除,满足这个要求的内存访问叫做对齐内存的访问aligned memory access),否则就是未对齐内存的访问unaligned memory access)。如果访问未对齐的内存会出现什么结果呢?这个要看CPU。

  • 有些CPU架构可以访问未对齐的内存,但是会有性能上的影响。典型的就是 x86 架构CPU
  • 有些CPU会抛出异常
  • 有些CPU不会抛出任何异常,会静默地访问错误的地址
  • 近几年也有些CPU的一部分指令可以正常访问未对齐的内存,同时不会有性能影响

因为每个CPU对未对齐内存的访问的处理方式都不一样,所以访问未对齐的内存是要尽量避免的。所以就出现了 C/C++ 的内存对齐机制。

为什么要内存对齐:
  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

C++内存结构

  1. 栈区 又叫堆栈 -- 非静态局部变量 / 函数参数 / 返回值等等,栈是向下增长的。

  2. 内存映射段 是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信

  3. 堆区 用于程序运行时动态内存分配,堆是可以上增长的。

  4. 数据段 -- 存储全局数据和静态数据。

  5. **代码段 **-- 可执行的代码(函数) / 字面量

img

类的对齐方式

类的对齐原则与结构体一致

类的内部存储结构与结构体类似,一般情况下二者的对齐原则一致

数据成员对齐规则

结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset(偏移)为0的地方,以后每个数据成员的对齐按照 #pragma pack 指定的数值和这个数据成员自身长度中,比较小的那个进行.

结构(或联合)的整体对齐规则
在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照 #pragma pack 指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

结构体(类)作为成员
如果一个结构里有某些结构体成员,则内部结构体成员要从成员最大元素大小的整数倍和 #pragmapack指定的数值中最小的一个的整数倍的地址开始存储。

添加成员函数后,类的大小不变

成员函数属于可执行代码,存储在代码段,通常不占用内存。 当我们定义一个函数时,只是给函数名分配一个指向该函数的代码区的地址,这个地址指向的是函数的机器指令,这些指令被存储在代码区,代码区是一个只读的内存区域,不属于堆和栈。当我们调用函数时,会根据代码区里的代码分配内存,这时才会占用内存。‌

此外,函数中的形式参数在未出现函数调用时并不占用内存中的存储单元,只有在发生函数调用时,形式参数才会被临时分配内存单元。

因此,成员函数不会影响类的大小,考虑内存对齐时不用担心函数。

继承后,子类的对齐方式取决于当前成员变量和继承的所有成员变量统一比较

继承后,子类共享父类的成员变量和成员函数。

添加静态成员,类大小不变

静态成员变量属于静态类型,存储在数据段,不影响类的大小

添加虚函数后,类的大小会改变,因为类会额外存储虚函数表指针

使用虚函数后,类会存储一个名为虚函数表指针的指针变量vptr,用于指向虚函数表,该变量对外不可见。指针占用8字节,因此需要额外考虑这个8字节的成员。

虚函数表(Virtual Function Table)‌是C++中实现多态的一种机制,主要用于解决虚函数的调用问题。虚函数表是一块连续的内存区域,每个内存单元中记录一个JMP指令的地址。编译器会为每个包含虚函数的类创建一个虚函数表,该表包含了该类所有虚函数的地址。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。‌

每个对象实例中都有一个指向其类的虚函数表的指针,这个指针通常位于对象实例的最前面,以确保访问虚函数表的效率。通过这个指针,可以遍历虚函数表中的函数指针,并调用相应的函数

子类继承父类的虚函数时,可以通过虚函数表指针调用相应的函数。如果子类重写了父类的虚函数,虚函数表中的地址会发生变化。

示例

#include <iostream>
using namespace std;

class Person {
private:
    int m_id; // sizeof int = 4
    char m_name[5];
};

struct Node1 {
    int id;
    char m_name[5];
};

// add member function

class Person1 {
public:
    void setId(int id) {
        m_id = id;
    }
private:
    int m_id; // sizeof int = 4
    char m_name[5];
};

// add member virtual function

class Person2 {
public:
    virtual void func() = 0;
private:
    int m_id; // sizeof int = 4
    char m_name[5];
};

// add static member

class Person3 {
public:
    static int m_count;
private:
    int m_id; // sizeof int = 4
    char m_name[5];
};

// inheritance
class Student : public Person 
{

private:
    int m_score;
    std::string m_school;
};

class Student2 : public Person2 {
public:
    void func() override {}
private:
    int m_score;
    std::string m_school;
};

// 32位机,#pragram pack(n) n = 4
// 64位机,#pragram pack(n) n = 8

int main() {
    // 普通类的内存对齐和结构体对齐法则一致
    cout << "=====compare class and struct=====\n";
    cout << "alignof Node1: " << alignof(Node1) << endl;
    cout << "sizeof Node1: " << sizeof(Node1) << endl;
    cout << "alignof Person: " << alignof(Person) << endl;
    cout << "sizeof(Person) = " << sizeof(Person) << endl;

    cout << "======add member function=====\n";
    // 添加成员函数,类大小不变,因为函数存储在代码区,不占类的内存
    cout << "sizeof Person: " << sizeof(Person) << endl;
    cout << "sizeof Person1: " << sizeof(Person1) << endl;

    cout << "======add static member=====\n";
    // 添加静态成员,类大小不变,因为静态成员存储在数据区,不占类的内存
    cout << "sizeof Person: " << sizeof(Person) << endl;
    cout << "sizeof Person3: " << sizeof(Person3) << endl;
    
    cout << "=====inheritance=====\n";
    // 继承后, 父类对齐方式不变,子类对齐方式取决于当前所有类成员变量的最大对齐方式
    cout << "alignof Student: " << alignof(Student) << endl;
    cout << "sizeof(Student) = " << sizeof(Student) << endl;

    cout << "=====add virtual function=====\n";
    // 添加虚函数后,类内部会存储一个虚函数指针,指向虚函数表,因此会比普通类大8字节
    // | vptr | m_id | m_name | m_score | m_school |
    cout << "sizeof Person1: " << sizeof(Person1) << endl;
    cout << "sizeof Person2: " << sizeof(Person2) << endl;

    cout << "sizeof Student1: " << sizeof(Student) << endl;
    cout << "sizeof Student2: " << sizeof(Student2) << endl;
    return 0;
}
=====compare class and struct=====
alignof Node1: 4
sizeof Node1: 12
alignof Person: 4
sizeof(Person) = 12
======add member function=====
sizeof Person: 12
sizeof Person1: 12
======add static member=====
sizeof Person: 12
sizeof Person3: 12
=====inheritance=====
alignof Student: 8
sizeof(Student) = 48
=====add virtual function=====
sizeof Person1: 12
sizeof Person2: 24
sizeof Student1: 48
sizeof Student2: 56

参考

【C++】 类的内存对齐、虚函数表_c++ 虚函数 没对齐-CSDN博客

C++ 类的内存分布_c++类内存分布-CSDN博客

posted @ 2024-11-21 18:50  RunTimeErrors  阅读(5)  评论(0编辑  收藏  举报