class 什么时候会触发内存对齐?

在 C++ 中,类(class)的内存对齐会在以下情况下触发:

  1. 类的成员变量类型具有对齐要求
  2. 类中包含继承关系时,基类与派生类的对齐可能受影响
  3. 内存对齐受架构(如 32 位、64 位)和编译器的实现策略影响

下面详细解析 什么时候会触发内存对齐 以及 内存对齐的规则


内存对齐触发的条件

1. 类的成员变量类型的对齐要求

每种数据类型都有其对齐要求(alignment requirements),编译器会将类的成员变量按照其对齐要求进行排列,必要时填充字节(padding)以满足对齐规则。
对齐的关键点:

  • 数据类型的对齐要求是由其大小决定的,例如:
    • char 通常为 1 字节对齐。
    • int 通常为 4 字节对齐。
    • double 通常为 8 字节对齐(在 64 位系统上)。
  • 成员变量的起始地址必须是其对齐要求的倍数。

例子:

#include <iostream>
#include <cstddef>
class Example {
char a; // 1 字节
int b; // 4 字节
char c; // 1 字节
};

int main() {
std::cout << “Size of Example: “ << sizeof(Example) << std::endl;
return 0;
}

分析:

  1. char a 占 1 字节。
  2. 接下来的 int b 需要对齐到 4 字节,因此在 char a 后插入 3 字节的填充。
  3. char c 占 1 字节,但类的大小最终需要满足最严格的成员对齐(int 的对齐是 4 字节),因此添加填充到总大小为 12 字节。

输出结果:

Size of Example: 12

2. 基类和派生类的对齐

当类有继承关系时,基类的对齐要求会影响派生类的内存布局,通常派生类的起始地址需要满足基类的对齐要求。

例子:

#include <iostream>
#include <cstddef>
class Base {
double d; // 8 字节对齐
};

class Derived : public Base {
char c; // 1 字节
};

int main() {
std::cout << “Size of Base: “ << sizeof(Base) << std::endl;
std::cout << “Size of Derived: “ << sizeof(Derived) << std::endl;
return 0;
}

分析:

  1. Base 只有一个 double,占 8 字节。
  2. Derived 需要满足 Base 的 8 字节对齐,因此 char c 后会填充 7 字节,保证整个 Derived 的大小是 Base 的倍数。

输出结果:

Size of Base: 8
Size of Derived: 16

3. 虚函数表指针(vptr)的对齐

如果类中包含虚函数,编译器会为每个对象增加一个虚函数表指针(vptr),这个指针通常遵循指针类型的对齐要求(通常是 4 字节或 8 字节)。

例子:

#include <iostream>

class Base {
virtual void func() {}
};

class Derived : public Base {
char c; // 1 字节
};

int main() {
std::cout << “Size of Base: “ << sizeof(Base) << std::endl;
std::cout << “Size of Derived: “ << sizeof(Derived) << std::endl;
return 0;
}

分析:

  1. Base 中有虚函数,因此包含一个 vptr 指针(64 位系统上占 8 字节)。
  2. Derived 继承了虚函数表指针,同时增加了 char c,因此最终大小需要对齐到 8 字节。

输出结果:

Size of Base: 8
Size of Derived: 16

4. 对齐受类对齐属性(alignment specifier)影响

C++11 提供了 alignas 关键字,可以显式设置类或变量的对齐要求。

例子:

#include <iostream>
#include <cstddef>
struct alignas(16) Aligned {
char c;
};

int main() {
std::cout << “Size of Aligned: “ << sizeof(Aligned) << std::endl;
std::cout << “Alignment of Aligned: “ << alignof(Aligned) << std::endl;
return 0;
}

输出结果:

Size of Aligned: 16
Alignment of Aligned: 16

5. 编译器和目标平台对齐策略

  • 自然对齐(natural alignment):
    大多数编译器会默认对齐到数据类型的大小倍数(如 int 为 4 字节对齐)。
  • 强制对齐(forced alignment):
    某些平台可能强制对齐到更大的倍数(如 64 位系统可能对齐到 8 字节)。
  • 数据结构的整体对齐:
    类的大小会向最严格对齐要求对齐。

如何查看类的内存布局

  1. 使用 sizeof 查看类的大小
    std::cout << sizeof(ClassName) << std::endl;
  2. 使用 offsetof 查看成员偏移
    std::cout << offsetof(ClassName, memberName) << std::endl;
  3. 使用调试工具(如 GDB)查看布局

总结

  • 类的内存对齐会在以下场景触发:
    1. 成员变量的类型有对齐要求。
    2. 类中包含继承关系。
    3. 类有虚函数表指针。
    4. 显式使用 alignas 设置对齐。
  • 影响内存对齐的因素:
    1. 成员变量的类型。
    2. 编译器实现和目标平台。
    3. 类的继承关系和虚函数。
  • 优化建议:
    • 将对齐要求较高的成员变量放在类的开头,减少填充字节。

Comments

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注