c++结构体内存对齐

什么是内存对齐?

一句话概括:一个结构体变量定义完之后,其在内存中的存储并不等于其所包含成员的大小之和。

现代计算机中内存空间都是按照字节(byte)进行划分的,所以从理论上讲对于任何类型的变量访问都可以从任意地址开始,但是在实际情况中,在访问特定类型变量的时候经常在特定的内存地址访问,所以这就需要把各种类型数据按照一定的规则在空间上排列,而不是按照顺序一个接一个的排放,这种就称为内存对齐。

几个基本概念

对齐值

(1)基本数据类型自身对齐值:基本数据类型的自身所占空间大小。

(2)指定对齐值:使用#pragam pack(value)时,指定的对齐值value。

(3)结构体或类的自身对齐值:包含成员中对齐值最大的那个值。

有效对齐值

有效对齐值:是给定值#pragma pack(n) 与 结构体中最长数据类型长度,两者中较小的那个。

偏移量offset

偏移量可以简单的理解为:对象地址与成员变量地址之间的距离。具体为,结构体第一个成员的偏移量是0(起始点与数据本身开始点是同一个点),以后每个成员相对于结构体首地址的偏移量都是 该成员大小与有效对齐值中较小那个 的整数倍,如有需要编译器会在成员之间加上填充字节。

计算总大小

也就是sizeof的值。结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

默认对齐规则

1.基本数据成员对齐

如果结构体只包含基本类型的成员。

  • 对于结构体的各个成员,第一个成员的偏移量(offset)为0,也就是说第一个成员总是存储在结构体变量开辟的空间的起始地址
  • 其他成员相对于结构体首地址的offset,是该成员大小的整数倍,如有需要编译器会在成员之间加上填充字节。

2. 结构体成员对齐

  • 如果包含了结构体成员,则结构体成员的起始位置为子结构体有效对齐值的整数倍。
  • 嵌套结构体大小为所有成员(包括嵌套的结构体的成员)的最大对齐数的整数倍。

3. 结构体总大小对齐

除了结构成员需要对齐,结构本身也需要对齐。结构体的总大小,也就是sizeof结构体的值,必须要是其内部最大成员对齐值(或者说自身对齐值)的整数倍,不足的要补齐。

示例

以下示例测试环境:

Ubuntu 22.04.3 LTS + GCC 11.4.0 x86_64 64 位环境。

有效对齐值为1

struct A1 {
  char a;
};

struct A12 {
  char a;
  bool b;
};

struct A13 {
  char a;
  bool b;
  bool c;
};

void align1() {
  std::cout << "sizeof A1: " << sizeof(A) << std::endl;
  std::cout << "sizeof A12: " << sizeof(A12) << std::endl;
  std::cout << "sizeof A13: " << sizeof(A13) << std::endl;
}

输出结果 

 sizeof A1: 1
sizeof A12: 2
sizeof A13: 3

 有效对齐值为2

struct A21 {
  short a;
};

struct A22 {
  short a;
  bool b;
};

struct A23 {
  bool b;
  short a;
};

struct A24 {
  bool b;
  short a;
  bool c;
};

void align2() {
  std::cout << "sizeof A21: " << sizeof(A21) << std::endl;
  std::cout << "sizeof A22: " << sizeof(A22) << std::endl;
  std::cout << "sizeof A23: " << sizeof(A23) << std::endl;
  std::cout << "sizeof A24: " << sizeof(A24) << std::endl;
}

 输出

sizeof A21: 2
sizeof A22: 4
sizeof A23: 4
sizeof A24: 6

内存示意图

  有效对齐值为4

struct A41 {
  int a;
};

struct A42 {
  int a;
  bool b;
};

struct A43 {
  int a;
  bool b;
  short c;
};

struct A44 {
  bool a;
  int b;
  short c;
};

struct A45 {
  bool a;
  int b;
  short c;
  int d;
};

void align4() {
  std::cout << "sizeof A41: " << sizeof(A41) << std::endl;
  std::cout << "sizeof A42: " << sizeof(A42) << std::endl;
  std::cout << "sizeof A43: " << sizeof(A43) << std::endl;
  std::cout << "sizeof A44: " << sizeof(A44) << std::endl;
  std::cout << "sizeof A45: " << sizeof(A45) << std::endl;
}

输出

sizeof A41: 4
sizeof A42: 8
sizeof A43: 8
sizeof A44: 12
sizeof A45: 16

内存示意图

有效对齐值为8 


struct OL {
  long a;
};

struct OIL {
  int a;
  long b;
};

struct OLL {
  long a;
  long b;
};

struct OLC {
  long a;
  char b;
};

struct OLS {
  long a;
  short b;
};

struct OLI {
  long a;
  int b;
};

struct OLD {
  long a;
  double b;
};

void align8() {
  std::cout << "sizeof OL: " << sizeof(OL) << std::endl;
  std::cout << "sizeof OIL: " << sizeof(OIL) << std::endl;
  std::cout << "sizeof OLL: " << sizeof(OLL) << std::endl;
  std::cout << "sizeof OLC: " << sizeof(OLC) << std::endl;
  std::cout << "sizeof OLS: " << sizeof(OLS) << std::endl;
  std::cout << "sizeof OLI: " << sizeof(OLI) << std::endl;
  std::cout << "sizeof OLD: " << sizeof(OLD) << std::endl;
}

结果

sizeof OL: 8
sizeof OIL: 16
sizeof OLL: 16
sizeof OLC: 16
sizeof OLS: 16
sizeof OLI: 16
sizeof OLD: 16

内存示意图

 带虚函数的类对齐

如果类定义了虚函数,编译器会在对象成员之前插入一个指向虚函数表的指针。在64位系统之中,指针大小为8个字节。

class BaseA {
public:
  virtual void Foo() {}
  int a;
};

class BaseB {
public:
  virtual void Bar() {}
  short b;
};

class BaseC {
  int c;
};

class ChildAB : public BaseA, public BaseB {
  int d;
};

class ChildAC1 : public BaseA, public BaseC {};

class ChildAC2 : public BaseA, public BaseC {
  int d;
};

class ChildCA : public BaseC, public BaseA {
  int d;
};

void align_virtual() {
  std::cout << "sizeof BaseA: " << sizeof(BaseA) << std::endl;
  std::cout << "sizeof BaseB: " << sizeof(BaseB) << std::endl;
  std::cout << "sizeof ChildAB: " << sizeof(ChildAB) << std::endl;
  std::cout << "sizeof ChildAC1: " << sizeof(ChildAC1) << std::endl;
  std::cout << "sizeof ChildAC2: " << sizeof(ChildAC2) << std::endl;
  std::cout << "sizeof ChildCA: " << sizeof(ChildCA) << std::endl;
}

输出

sizeof BaseA: 16
sizeof BaseB: 16
sizeof ChildAB: 32
sizeof ChildAC1: 16
sizeof ChildAC2: 24
sizeof ChildCA: 24

 

结构体字段对齐

/*
struct OLS {
  long a;
  short b;
};
*/

struct ISS {
  short ma;
  int mb;
  OLS mc;
  short md;
};

void align_struct() {
  ISS vi;
  ISS *p = &vi; // vscode调试时候获取地址值方便查看内存
  vi.ma = 0x0102;
  vi.mb = 0x03040506;
  vi.mc.a = 0x0101010101010101;
  vi.mc.b = 0;
  vi.md = 0x1234;
  std::cout << "sizeof ISS - OLS: " << sizeof(vi.mc) << std::endl;
  std::cout << "sizeof ISS: " << sizeof(vi) << std::endl;
}

结果

sizeof ISS - OLS: 16
sizeof ISS: 32

 结构体成员mc的有效对齐值是8,所以整个ISS的有效对齐值也是8。

 调试的时候查看内存

md的位置在mc内存对齐之后的位置。 

可以看出结构体成员本身是内存对齐的,它本身所占的大小也满足内存对齐规则。紧跟的其他成员必须在它对齐之后的内存位置,没办法去填充前一个结构体成员的内存空间使其对齐。

空结构体字段占1个字节

struct NullS {};

struct NullChild : public NullS {
  int a;
};

struct ON {
  NullS a;
};

struct OIN {
  int a;
  NullS b;
};

struct ONI {
  NullS a;
  int b;
};

struct ONSI {
  NullS a;
  short b;
  int c;
};

struct ONCCBI {
  NullS a;
  char b;
  char c;
  bool d;
  int f;
};

struct ONCCBBI {
  NullS a;
  char b;
  char c;
  bool d;
  bool e;
  int f;
};

void align_null_struct() {
  std::cout << "sizeof NullS: " << sizeof(NullS) << std::endl;
  std::cout << "sizeof NullChild: " << sizeof(NullChild) << std::endl;
  std::cout << "sizeof ON: " << sizeof(ON) << std::endl;
  std::cout << "sizeof OIN: " << sizeof(OIN) << std::endl;
  std::cout << "sizeof ONI: " << sizeof(ONI) << std::endl;
  std::cout << "sizeof ONSI: " << sizeof(ONSI) << std::endl;
  std::cout << "sizeof ONCCBI: " << sizeof(ONCCBI) << std::endl;
  std::cout << "sizeof ONCCBBI: " << sizeof(ONCCBBI) << std::endl;
}

结果

sizeof NullS: 1
sizeof NullChild: 4
sizeof ON: 1
sizeof OIN: 8
sizeof ONI: 8
sizeof ONSI: 8
sizeof ONCCBI: 8
sizeof ONCCBBI: 12

C++语言标准中规定了这样一个原则:“no object shall have the same address in memory as any other variable”,即任何不同的对象不能拥有相同的内存地址。如果空类对象大小为0,那么此类数组中的各个对象的地址将会一致,明显违反了此原则。

C++编译器会在空类或空结构体中增加一个虚设的字节,以确保不同的对象都具有不同的地址。

本次测试得出几个结论:

  1. 空结构体的大小为1
  2. struct{} 作为结构体字段时,它也占用1个字节,需要内存对齐。
  3. 空结构体作为基类时候,不会增加子类的空间占用。

修改对齐值

使用#pragam pack(n),和alignas更改编译对齐方式,但是本文不讨论。

  • 29
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值