C++ 字节对齐


Reference:

  1. 字节对齐

相关文章:

  1. 向量化运算 和 EIGEN_MAKE_ALIGNED_OPERATOR_NEW

1. 概念

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

如果一个变量的内存地址正好位于它长度的整数倍,它就被称做自然对齐。比如 4 4 4 字节的 int 型,其起始地址应该位于 4 4 4 字节的边界上,即起始地址能够被 4 4 4 整除,也即对齐跟数据在内存中的位置有关。

2. 为什么要对齐

  • 一些平台严格要求,比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误;
  • 为了使CPU能够对变量进行快速的访问

为什么非对齐访问内存慢?举个例子:
如果 0x02~0x05 存了一个 int ,读取这个 int 就需要先读 0x01~0x04,留下 0x02~0x04 的内容,再读 0x05~0x08,留下 0x05 的内容,两部分拼接起来才能得到那个 int 的值,这样读一个 int 就要两次内存访问,效率就低了。

比较抽象的说法,比如 字节对齐 这几个字的笔画数可以想象成每个字的字节数。我们知道第三个字是‘对’,整个的第三笔是‘了’,但是第三四五六笔放在一起就没意义了。

3. 预编译命令#pragma pack(n)

sizeof 运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题有时候为了内存对齐需要补齐空字节。通常写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略
这里可以打印了一下 struct 的输出结果:

struct A
{
  int val1_m; // 4 bytes	
  double val2_m; // 8 bytes
};

A a_stru;
std::cout << "sizeof(a_stru):" << sizeof(a_stru) << std::endl;
std::cout << "address of a_stru.val1_m: " << &a_stru.val1_m << std::endl;
std::cout << "address of a_stru.val2_m: " << &a_stru.val2_m << std::endl;
sizeof(a_stru):16
address of a_stru.val1_m: 0x7ffe787bd690
address of a_stru.val2_m: 0x7ffe787bd698

可以看到 a_stru.val1_m 明明是 int 类型,只占用 4 4 4 个字节,但这里和下一个变量 a_stru.val2_m 之间的字节差为 8 8 8,这里就是编译器自动对齐了。

当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。这里引入与编译器指令#pragma pack

  • 语法详见官网

    #pragma pack( [show] | [push | pop] [, identifier], n )

  • 作用:指定 structure、union 和 class 的包对齐方式(pack alignment)

    #pragma pack(n) //指定对齐的字节数为 n,注意,数需要为2的幂
    #pragma pack() with no parameters is deprecated. Use #pragma pack(4) instead.
    #pragma pack(4) // 4-byte alignment
    #pragma pack(1) // 1-byte alignment

3.1 运算符 alignof

怎么确定成员的实际大小:

  1. 对于基本类型,对齐值即为 sizeof() 返回值;
  2. 对于其他类型可以使用alignof()来查询。
  • 语法详见官网
    alignof 操作符返回指定类型的以字节为单位的对齐方式,返回类型为 size_t。

    alignof( type )

4. 字节对齐规则

结构体中各个成员按照它们被声明的顺序在内存中顺序存储。对齐规则如下:

  1. 各个数据成员按各自数据类型对齐:
    对齐模数是该数据成员所占内存#pragma pack指定的数值中的较小者

  2. 整个结构体字节大小向结构体模数对齐:
    结构体模数#pragma pack指定的数值(32位机默认4字节、64位机默认16字节) 和 结构体内部最大的基本数据类型成员 长度中数值较小者结构体的长度应该是该模数的整数倍

根据规则2,再写一个对应示例:

struct B 
{
	int val3_m;			// 4 bytes	
	A val4_m;				// 12 bytes
	double val5_m;	// 8 bytes
	int val6_m;			// 4 bytes
};

B b_stru;
std::cout << "sizeof(b_stru):" << sizeof(b_stru) << std::endl;
std::cout << "address of b_stru.val3_m: " << &b_stru.val3_m << std::endl;
std::cout << "address of b_stru.val4_m: " << &b_stru.val4_m << std::endl;
std::cout << "address of b_stru.val5_m: " << &b_stru.val5_m << std::endl;
std::cout << "address of b_stru.val6_m: " << &b_stru.val6_m << std::endl;
sizeof(b_stru):40
address of b_stru.val3_m: 0x7ffc58ed5a10
address of b_stru.val4_m: 0x7ffc58ed5a18
address of b_stru.val5_m: 0x7ffc58ed5a28
address of b_stru.val6_m: 0x7ffc58ed5a30

这里神奇的地方比较多,一行行看:

  1. 初始地址从 0x7ffc58ed5a10 开始,int 大小为 4 字节,但是下一个跳到了 0x7ffc58ed5a18,中间差 8 个字节。这是因为 A 是一个结构体,#pragma pack 默认 16 字节,而结构体内最大基本数据占 8 个字节,所以该结构体模数为 8,该结构体以 8 字节对齐。
  2. 0x7ffc58ed5a18 到 0x7ffc58ed5a28 中间 16 个字节,前文示例已说明。
  3. 0x7ffc58ed5a28 到 0x7ffc58ed5a30 中间 8 个字节,很正常,double 类型,没什么好说的。
  4. 这样算下来也就占用了 4+4+16+8+4=36 个字节,为啥最终得数是40?因为前文所说,整个结构体字节大小向 结构体模数 和 结构体内部最大的基本数据类型成员长度中数值较小者 对齐,所以结构体大小需要是 8 的整数倍,末尾有 4 个字节的空字节,为 4+4+16+8+4+4=40 字节。

这个时候再引入 #pragma pack 看看,

#pragma pack(4)
struct A
{
  int val1_m; // 4 bytes	
  double val2_m; // 8 bytes
};

#pragma pack(1)
struct B 
{
	int val3_m;			// 4 bytes	
	A val4_m;				// 12 bytes
	double val5_m;	// 8 bytes
	int val6_m;			// 4 bytes
};

A a_stru;
std::cout << "sizeof(a_stru):" << sizeof(a_stru) << std::endl;
std::cout << "address of a_stru.val1_m: " << &a_stru.val1_m << std::endl;
std::cout << "address of a_stru.val2_m: " << &a_stru.val2_m << std::endl;

B b_stru;
std::cout << "sizeof(b_stru):" << sizeof(b_stru) << std::endl;
std::cout << "address of b_stru.val3_m: " << &b_stru.val3_m << std::endl;
std::cout << "address of b_stru.val4_m: " << &b_stru.val4_m << std::endl;
std::cout << "address of b_stru.val5_m: " << &b_stru.val5_m << std::endl;
std::cout << "address of b_stru.val6_m: " << &b_stru.val6_m << std::endl;

输出:

sizeof(a_stru):12
address of a_stru.val1_m: 0x7ffedaaf13d4
address of a_stru.val2_m: 0x7ffedaaf13d8
sizeof(b_stru):28
address of b_stru.val3_m: 0x7ffedaaf13e0
address of b_stru.val4_m: 0x7ffedaaf13e4
address of b_stru.val5_m: 0x7ffedaaf13f0
address of b_stru.val6_m: 0x7ffedaaf13f8

可以看到这里就没有使用空字节补齐了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++中的字节对齐(内存对齐)是指在分配内存时,将变量或结构体的起始地址对齐到特定的字节边界。这样做有助于提高内存访问的效率和性能。字节对齐的规则可以通过编译器选项或特定的关键字进行控制。 以下是关于C++字节对齐的一些重要概念和规则: 1. 默认对齐: - 编译器会使用默认的对齐规则来分配内存。通常,默认对齐值是被编译器设置的,一般为结构体成员中最大的对齐值。 2. 对齐值: - 对齐值是指要求变量或结构体的起始地址必须是该值的倍数。常见的对齐值有1、2、4、8等。 3. 对齐修饰符: - C++11引入了对齐修饰符 `alignas`,允许开发者显式地指定变量或结构体的对齐值。 4. 结构体字节对齐: - 结构体的字节对齐规则是,结构体的起始地址必须是其成员中最大对齐值的倍数。 - 编译器会在结构体成员之间插入填充字节,以保证对齐要求。 5. 类对象字节对齐: - 类对象的字节对齐规则与结构体类似,但还受到继承关系的影响。 - 派生类的起始地址必须满足其成员的对齐要求,并且满足其基类中最大对齐值的倍数。 为了控制字节对齐,可以使用编译器提供的特定选项(如`#pragma pack`)或关键字(如`alignas`)。具体的字节对齐规则和选项可能因编译器和平台而异,因此在编写代码时最好参考特定编译器的文档。正确的字节对齐可以提高内存访问性能,并确保与其他代码或外部系统的兼容性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泠山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值