CC++ 字节对齐问题

C/C++ 字节对齐问题

对齐用法详解

  • 什么是对齐,以及为什么要对齐: 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。

  • 对齐的实现:通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。 但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。

作用:指定结构体、联合以及类成员的packing alignment;

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

说明:

1,pack提供数据声明级别的控制,对定义不起作用;

2,调用pack时不指定参数,n将被设成默认值;

3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降。

语法具体分析

1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;

2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;

3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;

4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;

5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

对齐方式

​ 对于 struct 或 union 中的 struct 或者 union 来说,它们的字节对齐标准就是它的所有成员中字节数最大的数据的字节数。

一般情况下 C/C++ 的变量所占用的字节数

​ char: 1字节;

​ short: 2字节;

​ int: 4字节;

​ long: 4字节;

​ long long: 8字节;

​ float: 4字节;

​ double: 8字节;

​ bool: 1字节;

*struct 中字节对齐需要满足的条件:

​ 1、某个变量存放的起始位置相对于结构的起始位置的偏移量是该变量字节数的整数倍;

​ 2、结构所占用的总字节数是结构种字节数最长的变量的字节数的整数倍。

*union 中字节对齐需要满足的两个条件:

​ 1、unoin 的大小必须足够容纳最宽的成员;

​ 2、union 的大小需要能够被其所包含的基础成员类型的大小所整除。

VC提供了 #pragma pack(n) 用来自定义字节对齐方式

​ 例:

 1 #pragma pack(push)//保存对齐状态
 2 #pragma pack(4)//设定4字节对齐
 3 struct test
 4 {
 5 	   char m1;
 6     double m4;
 7     int m3;
 8 };
 9 #pragma pack(pop)//恢复对齐状态

解析

以上结构体的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1大小为1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于4),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(8),那么我们可以得到结构的大小为24。

实例:

1、

 1 #include <stdio.h>
 2 union
 3 {
 4     char x[5];
 5     int i;
 6 }a;
 7 
 8 int main()
 9 {
10     a.x[0] = 10;
11     a.x[1] = 1;
12     printf("%d\n", a.i);
13     printf("%d\n", sizeof(a));
14     return 0;
15 } 

输出为 266 8

解析:

对 union 分配内存涉及字节对齐问题,在上方已有详细描述,在此只简单解释一番。union 分配的内存必须是 union 中所有基本数据类型的倍数。
在此题中即为 1 和 4 的倍数,又 char x[5] 占用 5 个字节,故 union 分配的内存大小应为 8 个字节。
windows 系统中高字节在后,低字节在前。而 char x[5] 只有前两个元素有值,即两个值只占 2 个字节,也即 union 中的 int 型数据中只有低两位上有值。
即 i 的二进制表示为:
00000000 00000000 00000001 00001010
即: 2^1 + 2^3 + 2^8 = 266
也相当于十六进制 0x010A, 即: 10 * 16^0 + 1 * 16^2 = 266

这里补充一下进制转换问题,也是我一并搜集来的:

进制互相转换

十进制0123456789101112131415
十六进制0123456789ABCDEF
二进制0000000100100011010001010110011110001001101010111100110111101111

十六进制数字转换十进制数字:

0x2AF5转换十进制:

5 * 16^0 = 5

F * 16^1 = 240

A * 16^2 = 2560

2 * 16^3 = 8192

---------------

5 + 240 + 2560 + 8192 = 10997

十进制数字转换十六进制数字:

2500转换十六进制:

2500 / 16 = 156 …… 4

156 / 16 = 9 …… 12©

9 / 16 = 0 …… 9

----------------------

得到4C9,倒转9C4,即2500的十六进制表示是: 0x9C4

二进制数字转换十六进制数字:

101110011011.1001

采取四合一法,即以二进制的小数点为分界点,向左(或向右)每四位取一位。

组分好以后,对照二进制与十六进制数的对应表,将四位二进制按权相加,得到的数就是一位十六进制数,然后按顺序排列,小数点的位置不变。

结果为:B9B.9

需要注意的是,在向左(或向右)取四位时,取到最高位(最低位)如果无法凑足四位,就可以在小数点的最左边(或最右边)补0,进行换算。

十六进制数字转换二进制数字:

对照二进制与十六进制数的对应表,将十六进制数分为二进制数字,用四位二进制数字按权相加,最后得到二进制数字,小数点依旧。

2、

 1 #include <iostream>
 2 #pragma pack(4)
 2 using namespace std;
 3 
 4 typedef struct A
 5 {
 6     char aChar;
 7     int aInt_2;
 8     short aInt;
 9 }TypeA;
10 
11 typedef struct B
12 {
13     char bChar[3];
14     TypeA bA;
15     double bDouble;
16 }TypeB;
17 
18 int main()
19 {   
20     TypeA a;
21     TypeB b;
22     cout << sizeof(a) << endl << sizeof(b) << endl;
23     return 0;
24 }

输出为 12 24

解析:

由上述对字节对齐问题的讨论很容易便可以得出此题的答案。
sizeof(TypeA) = 1 + 3 + 4 + 2 = 10;
sizeof(TypeB) = 3 + 1 + 12 + 8 = 24。之所以补上 1 个字节是因为 TypeA 类型在 struct TypeB 中的默认对齐方式
是 4 个字节(即 int 的字节大小,也即 struct TypeA 中最长的字节)。

<think>首先,用户的问题是:“我想理解或解决C++结构体在内存中的字节对齐问题 请问C++ 结构体 字节对齐 内存布局”。这是一个关于C++结构体字节对齐和内存布局的问题。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式,例如:$x^2$。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1]。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。 参考站内引用:用户提供了几个引用,我需要参考这些,但用户说“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答”。用户没有指定上一次问题,所以我应该基于当前问题回答。 关键内容: - 引用[1]到[5]讨论了C++结构体字节对齐的规则和示例。 - 规则包括:字段对齐、整个结构体大小调整、最大对齐参数等。 核心字节对齐规则: 1. 每个成员变量的起始地址必须是其类型大小的倍数。 2. 结构体的总大小必须是其最大成员大小的倍数。 3. 编译器可能会在成员之间插入填充字节以满足对齐要求。 我需要构建一个清晰、逐步的解释: 1. 介绍字节对齐的概念。 2. 解释对齐规则。 3. 提供示例。 4. 讨论为什么需要字节对齐(性能原因)。 5. 如何控制对齐(如使用#pragma pack)。 使用中文回答。 生成相关问题:在回答结束后,添加一个“相关问题”部分。 结构回答: - 开头:简要介绍问题。 - 主体:逐步解释规则、示例、内存布局。 - 结尾:总结和生成相关问题。 确保使用LaTeX格式: - 行内数学:如成员大小$sizeof(type)$。 - 独立公式:如果需要,例如计算大小。 示例: - 使用引用中的例子,如struct ISS或struct CC。 引用标识:在回答中引用参考时添加[^n],但用户提供的引用是给我的参考,不是直接引用内容。我应该基于知识回答,但引用这些参考。 用户说:“上述引用仅供参考”,所以我应该使用这些信息来构建回答,但不必直接复制。 最终回答结构: 1. 什么是字节对齐?为什么重要? 2. C++结构体字节对齐规则。 3. 示例分析。 4. 如何查看和修改对齐。 5. 总结。 6. 相关问题。 生成相关问题: - 类似示例中的,如“如何计算结构体大小?”、“如何避免不必要的填充?”等。</think>### C++结构体字节对齐与内存布局详解 在C++中,结构体(struct)的内存布局涉及字节对齐(byte alignment),这是编译器为优化内存访问性能而采用的规则。如果未对齐,CPU访问未对齐数据可能引发性能下降或硬件异常(如某些架构)。我将逐步解释核心规则、内存布局原理,并提供实用示例。所有分析基于C++标准(C++11及以上)和常见编译器行为(如GCC、MSVC)。 #### 1. **字节对齐的基本概念** 字节对齐要求每个结构体成员的起始地址必须是其类型大小的整数倍: - 例如,`int`类型通常大小为4字节(32位系统),因此其起始地址必须是4的倍数(如0x0000、0x0004)。 - 对齐规则由**最大对齐参数**决定:结构体总大小必须是所有成员中最大对齐值的整数倍。 - 为什么需要对齐?CPU访问对齐数据更高效,减少内存总线周期;未对齐数据可能导致分段错误或性能损失[^1][^3]。 数学上,成员起始地址需满足: $$\text{address} \% \text{alignof(type)} = 0$$ 其中$\text{alignof(type)}$是类型的对齐值(通常等于$sizeof(\text{type})$)。 #### 2. **核心对齐规则** C++结构体字节对齐遵循三条规则[^2][^3][^5]: 1. **成员对齐规则**:每个成员起始地址必须为其类型大小的倍数。 - 例如,`short`(2字节)起始地址需是2的倍数。 2. **填充规则**:编译器在成员间插入填充字节(padding)以满足对齐要求。 - 填充内容未定义(通常为0)。 3. **整体对齐规则**:结构体总大小必须是最大成员对齐值的整数倍。 - 最大对齐值:取所有成员类型对齐值的最大值。 #### 3. **内存布局分析示例** 我们以用户引用中的`struct ISS`为例(引用[1]),逐步分析内存布局: ```cpp struct OLS { long a; // 假设long为8字节(64位系统) short b; // 2字节 }; // sizeof(OLS) = 16? 规则计算后解释 struct ISS { short ma; // 2字节 int mb; // 4字节 OLS mc; // 嵌套结构体 short md; // 2字节 }; ``` **步骤1: 计算成员对齐和填充** - `ma`(short, 2字节):起始地址0x0000(假设基址0),大小2字节。 - `mb`(int, 4字节):起始地址需为4的倍数。当前地址0x0002不满足,插入2字节填充 → 地址0x0004开始,大小4字节。 - `mc`(OLS, 嵌套结构体): - 先计算`OLS`:`long a`(8字节)需8字节对齐,起始地址0x0008;`short b`(2字节)起始地址0x0010。但`OLS`整体大小需为最大对齐值(8字节)的倍数:当前大小10字节(8+2),需补6字节填充 → `sizeof(OLS) = 16`字节。 - `mc`在`ISS`中起始地址:`mb`结束于0x0007,下一地址0x0008满足8字节对齐 → 直接放置。 - `md`(short, 2字节):起始地址0x0018(`mc`结束于0x0017),大小2字节。 **步骤2: 计算整体大小并填充** - 当前总大小:`ma`(2) + 填充(2) + `mb`(4) + `mc`(16) + `md`(2) = 26字节。 - 最大对齐值:取`ma`(2)、`mb`(4)、`mc`(8)、`md`(2)的最大值 → 8字节。 - 26不是8的倍数,需在末尾补6字节填充 → `sizeof(ISS) = 32`字节。 内存布局可视化(单位:字节): | 地址范围 | 内容 | 说明 | |----------|------------|---------------| | 0x0000-0x0001 | `ma` | short数据 | | 0x0002-0x0003 | 填充 | 满足`mb`对齐 | | 0x0004-0x0007 | `mb` | int数据 | | 0x0008-0x000F | `mc.a` | long数据 (OLS部分) | | 0x0010-0x0011 | `mc.b` | short数据 (OLS部分) | | 0x0012-0x0017 | 填充 | OLS内部填充 | | 0x0018-0x0019 | `md` | short数据 | | 0x001A-0x001F | 填充 | 整体对齐填充 | 验证:引用[1]中调试输出`sizeof(ISS) = 32`,符合计算。 #### 4. **影响对齐的因素与控制方法** - **编译器默认行为**:编译器根据目标平台自动对齐(如x86-64通常8字节对齐)。 - **修改对齐**: - 使用`#pragma pack(n)`:强制按n字节对齐(n=1,2,4...)。 ```cpp #pragma pack(1) // 1字节对齐,无填充 struct TightPacked { int a; // 4字节 char b; // 1字节 double c; // 8字节 }; // sizeof = 13 (无填充) #pragma pack() // 恢复默认 ``` - C++11的`alignas`:指定成员对齐。 ```cpp struct CustomAlign { alignas(16) int a; // 强制16字节对齐 char b; }; ``` - **性能权衡**:紧密对齐(如`#pragma pack(1)`)节省内存,但可能降低访问速度[^3][^5]。 #### 5. **常见问题与优化建议** - **为什么结构体大小不等于成员之和?** 填充字节导致,如引用[3]中`struct CC`大小32而非25。 - **优化内存**: - 重排成员:按对齐值降序排列(如double→int→short→char),减少填充。 ```cpp // 优化前:sizeof ≈ 12 (含填充) struct BadOrder { char a; double b; char c; }; // 优化后:sizeof = 16 (更紧凑) struct GoodOrder { double b; int a; char c; }; ``` - 使用位域(bit-field):对bool或小范围整数压缩。 ```cpp struct BitField { unsigned a : 4; // 4位 unsigned b : 4; }; // sizeof可能为1字节 ``` - **调试工具**:使用`offsetof`宏查看成员偏移,或编译器选项输出内存布局。 #### 6. **总结** 字节对齐C++结构体内存布局的核心机制,由成员对齐、填充和整体对齐规则构成。默认行为平衡性能与内存,但可通过`#pragma pack`或成员重排优化。理解这些规则有助于避免内存浪费和访问错误。实际开发中,使用`sizeof`和`offsetof`验证布局[^2][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值