结构体对齐

    #pragma pack(4)
  class TestB
  {
  public:
    int aa;
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestB);
  这里nSize结果为12,在预料之中。

  现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestC);
  按照正常的填充方式nSize的结果应该是8,为什么结果显示nSize为6呢?

事实上,很多人对#pragma pack的理解是错误的。
#pragma pack规定的对齐长度,实际使用的规则是:
结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
也就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。
而结构整体的对齐,则按照结构体中最大的数据成员 和 #pragma pack指定值 之间,较小的那个进行。

具体解释
      #pragma pack(4)
  class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
这个类实际占据的内存空间是9字节
类之间的对齐,是按照类内部最大的成员的长度,和#pragma pack规定的值之中较小的一个对齐的。
所以这个例子中,类之间对齐的长度是min(sizeof(int),4),也就是4。
9按照4字节圆整的结果是12,所以sizeof(TestB)是12。


如果
#pragma pack(2)
    class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
//可以看出,上面的位置完全没有变化,只是类之间改为按2字节对齐,9按2圆整的结果是10。
//所以 sizeof(TestB)是10。

最后看原贴:
现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;//第一个成员,放在[0]偏移的位置,
    short b;//第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
    char c;//第三个,自身长为1,放在[4]的位置。
  };
//整个类的大小是5字节,按照min(sizeof(short),4)字节对齐,也就是2字节对齐,结果是6
//所以sizeof(TestC)是6。

当数据定义中出现__declspec( align() )时,指定类型的对齐长度还要用自身长度和这里指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然后取其中较大的。

可以这样理解, __declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
__declspec( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:
在__declspec( align() )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec( align() )。
当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,取其中较大的作为整个结构的对齐长度。
特别的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作用。

### 结构体对齐原理 结构体对齐的核心在于优化 **数据存取执行效率** 和减少不必要的 **存储空间浪费** 的平衡[^1]。在嵌入式系统开发中,这种权衡尤为重要,因为内存资源有限而性能需求较高[^2]。 #### 数据对齐规则 1. **成员变量的对齐位置**: - 第一个成员始终位于偏移量为 `0` 的地址上。 - 后续成员会根据其类型的大小和编译器设定的最大对齐数进行调整,使其地址是对齐数的整数倍。对齐数通常是该成员类型大小与全局默认对齐数之间的最小值[^5]。 2. **整体结构体大小**: - 整个结构体的大小会被扩展为其内部最大对齐数的整数倍。这样可以确保当多个相同结构体组成数组时,每个结构体实例仍然能够保持良好的对齐状态[^4]。 3. **嵌套结构体处理**: - 如果结构体内包含另一个子结构体,则子结构体会以其自身的最大对齐数为准进行填充并扩展至合适的边界长度;最终整个父级结构体也会基于所有成员(包括嵌套部分)的最大对齐数值做进一步扩充。 ### 实现方法 可以通过预处理器指令或者特定函数来改变默认行为: - 使用 `#pragma pack(n)` 设置新的字节打包模式 (n 表示指定的新对齐单位),从而强制覆盖原有自动计算出来的对齐策略; ```c #include <stdio.h> #pragma pack(1) // 手动设置紧凑排列 typedef struct { char a; int b; short c; } MyStruct; int main() { printf("Size of MyStruct: %lu\n", sizeof(MyStruct)); // 输出应为7而非通常情况下的12或更多 return 0; } ``` - 利用标准库头文件 `<stdalign.h>` 提供的功能实现更灵活精确地控制对象间的相对定位关系。 另外需要注意的是,在实际项目里修改这些参数可能带来兼容性和移植性方面的问题,因此需谨慎操作。 ### 示例分析 假设存在如下定义: ```c struct Example { char ch; /* size=1 */ double db; /* size=8 */ }; ``` 如果没有特别指示,默认情况下可能会有以下布局: | 地址 | 类型 | |------|------------| | 0x00 | char(ch) | | 0x01 ~ 0x07 | 填充位 | | 0x08 ~ 0x10 | double(db)| 这里为了使双精度浮点数能高效加载到寄存器内工作,它前面预留了足够的空白区域作为补偿措施。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值