#pragma pack(1) 结构体字节对齐——结构体收发乱码问题复盘(一)

目录

 

背景

结构体字节对齐

当结构体为空的时,在C++中占1字节(在C中占0字节)

若结构体中所有数据类型都相同,则其所占空间为 成员数据类型长度×成员个数

结构体的大小与结构体内元素的排列顺序有很大关系

 结构体中含有结构体的情况

结构体中包含静态成员对象的情况

#pragma pack(n)的使用

指针变量类型强转

结构体字节对齐所引发的问题

感谢:https://www.cnblogs.com/longlybits/articles/2385343.html


背景

最近工作中遇到一个问题,在job1中给一个结构体中赋值,把这个结构体给发送给job2,发现job1中看的结构体内部每个值的内容,到job2中后,就都错了,排查了很长时间也不得其解,后再大神的指点下,发现是结构体字节对齐这出的错,特此记录

结构体字节对齐

当结构体为空的时,在C++中占1字节(在C中占0字节)

若结构体中所有数据类型都相同,则其所占空间为 成员数据类型长度×成员个数

结构体的大小与结构体内元素的排列顺序有很大关系

typedef struct obj1
{
    int a;
    char b;
    short c;
}S1;

//sizeof(S1) = 8

不考虑#pragma pack(n)所带来的影响,S1所占用的内存对齐方式,分析如下:首先,int类型占用4字节,char类型占用1字节,short类型占用2字节,int类型所占用的内存最大,由于结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。故存放方式为:

|--------int--------|   4字节

|char|----|--short--|   4字节

需要注意的是,对齐方式是以结构体中变量所占空间的大小为分配给其他类型变量的空间上限(换做人话说:

1.最长的是4字节,那剩下的变量所占的空间肯定超不过4字节,

2.且如果分配给其的对齐空间4字节没有用完,还有余量可以放下下一个变量,那就放,否则,就另开一个4字节,重新放新的,空余的地方那就闲在那空着,回头填充)

typedef struct obj2
{
    char a;
    int b;
    short c;
}S2;

//sizeof(S2) = 12

 放同样的几个变量,调换一下顺序,结构体所占用的空间就多了4字节,对齐方式应为:

|char|--------------|   4字节

|--------int--------|   4字节

|--short--|---------|   4字节

 结构体中含有结构体的情况

typedef struct obj2
{
    bool a;
    S1 s1;  //结构体S1充当结构体S2中的一个变量
    short b;
}S2;

//sizeof(S2) = 16

 之前S1所占的空间是8字节,可以解释的方式就是:结构体S1中最长的数据类型是int,占4个字节,结构体S1是以4字节进行对齐的,且该4字节充当了S1的数据类型所占空间长度,其他的bool也是4字节,short是2字节,对齐方式取4字节为最长,为:

|--------bool-------|   4字节

|--------int--------|   4字节  --S1

|char|----|--short--|   4字节  --S1

|--short--|---------|   4字节

总结成结论就是,结构体1里面套结构体2时,结构体1内的其他变量的数据类型长度比结构体2的内部对齐长度长的,即按照前者作为结构体1的对齐长度,否则,按后者,当然,结构体2实际占空间,还是占多数空间

再多举个例子:

typedef struct obj3
{
    bool a;
    S1 s1;
    double b;
    int c;
}S3;

//sizeof(S3)=32

这个里面,虽然S1还是以4字节对齐的,但有个double类型的变量,其数据类型所占字节为8,是结构体里面最长的,故该结构体的对齐方式如下:

|--------bool--------|    8字节

|---------s1---------|    8字节

|--------double------|    8字节

|----int----|--------|    8字节

 共计32字节。

结构体中包含静态成员对象的情况

typedef struct obj4
{
    int a;
    short b;
    static int c;
}S4;

//sizeof(S4) = 8

静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。其在内存中存储方式如下:

|--------int--------|   4字节

|--short-|----|----|    4字节

 

#pragma pack(n)的使用

用法:使用了#pragma pack(n)命令强制对齐标准,则取n和结构体中最长数据类型占的字节数两者之中的小者作为对齐标准。

背景:计算机系统对基本数据类型在内存中的存放是有一定限制的,通常我们会要求这些数据的首地址的值是某个数的倍数(4或8),这就是所谓的内存对齐。

默认对齐系数:而每个平台上的编译器都有着自己的“默认对齐系数”(32位机一般为4,64位机一般为8),但我们可以通过预编译命令#pragma pack(k),k = 1,2,4,8,16来改变这个系数,其中的k就是对齐系数;

因为结构体对齐的时候是取结构体中最长数据类型与对齐系数最小的进行,且一般的数据类型长的不过也就4字节或8字节了,所以,结构体在对齐的时候,对齐系数如果没有额外设置,都会自己按上面的结构体字节对齐来显示的。

自定义取消对齐:使用#pragma pack()取消自定义字节对齐方式

 struct和union中的字节对齐通常是先局部对齐,再全局对齐。

局部对齐:数据的偏移地址是自身所占内存大小和对齐系数中较小的那个的倍数;

全局对齐:总内存是最大数据结构所占内存和对齐系数中较小的那个的倍数,union是数据可重合。

#pragma pack(show):查看当前采用的对齐方式 https://www.jianshu.com/p/d994731f658d

指针变量类型强转

背景:一般情况下,结构体的对齐方式无论是按照默认对齐或是通过命令强制对齐,除了对程序有运行速度,效率上再或者是存储空间上的的影响外,产生的问题不算大,但是,一旦牵扯到强转,那影响可就大了,就如同本次我遇到的这个问题,再一个job下构建的一个结构体,强转其指针后,作为内容转发给另一个job,另一个收到后的就是乱码了。这引出的问题还是蛮大的,而且,不好排查。

指针变量类型强转的问题比较复杂,请看后篇博客,单独复盘。

 

结构体字节对齐所引发的问题

如下博客所举出的一些例子也同样具有参考和借鉴的价值:

c语言struct结构体强制类型转换:https://blog.youkuaiyun.com/blog_xu/article/details/84374473

 

感谢:https://www.cnblogs.com/longlybits/articles/2385343.html

### 关于 `#pragma pack(push,1)` 的正确使用 `#pragma pack(push, n)` 和 `#pragma pack(pop)` 是种用于控制结构体内存对齐的方式。通过这种方式可以临时修改编译器的字节对齐设置,并在完成后恢复原来的对齐方式。如果发现 `#pragma pack(push,1)` 未能成功取消结构体字节对齐,可能是由于以下几个原因: #### 原因分析 1. **未正确匹配 `push` 和 `pop`** 如果没有正确配对 `#pragma pack(push, n)` 和 `#pragma pack(pop)`,可能会导致后续代码仍然处于错误的对齐模式下[^3]。 2. **编译器行为差异** 不同的编译器可能对 `#pragma pack` 的实现略有不同。某些编译器可能存在特定的行为或限制,这可能导致预期效果无法达成[^4]。 3. **作用范围不明确** `#pragma pack(push, n)` 的作用仅限于它所在的源文件或者指定范围内。如果结构体定义超出了这个范围,则不会受到影响[^1]。 --- ### 解决方案 为了确保 `#pragma pack(push,1)` 能够正常工作并取消结构体字节对齐,请遵循以下方法: #### 方法:严格配对 `push` 和 `pop` 确保每次调用 `#pragma pack(push, n)` 后都有对应的 `#pragma pack(pop)` 来恢复原始对齐方式。例如: ```cpp #pragma pack(push, 1) // 设置为1字节对齐 struct Test { char a; int b; }; #pragma pack(pop) // 恢复原对齐方式 ``` 这样能够保证在定义结构体期间应用了所需的对齐规则,而在其他地方保持原有的对齐设置[^3]。 #### 方法二:验证当前编译器支持情况 确认所使用的编译器是否完全支持 `#pragma pack(push/pop)` 功能。部分较老版本的编译器可能不具备完整的功能集。对于现代主流编译器(如 GCC、Clang 或 MSVC),通常都已提供良好的支持。 #### 方法三:检查全局影响 有时即使设置了局部的对齐选项,也可能受到更高层次上的全局配置干扰。可以通过显式声明全局默认值来排除这种可能性: ```cpp #pragma pack(8) // 设定全局最大对齐为8字节(假设) #pragma pack(push, 1) // 局部调整至1字节对齐 struct Example { char x; long y; }; #pragma pack(pop) // 结束后恢复正常 ``` 此操作有助于隔离潜在冲突因素的影响[^2]。 --- ### 示例代码展示 以下是基于上述原则的个完整例子演示如何利用 `#pragma pack(push,1)` 实现精确控制结构体大小的功能。 ```cpp #include <iostream> // 开始记录初始状态前的状态 #pragma pack(push) // 切换到1字节边界对齐 #pragma pack(1) struct OneByteAlignedStruct { char ch; int integer; }; // 返回标准对齐设定 #pragma pack(pop) int main() { std::cout << "Size of OneByteAlignedStruct: " << sizeof(OneByteAlignedStruct) << " bytes." << std::endl; return 0; } ``` 运行以上程序应显示结果为5字节长度,表明确实采用了紧凑形式存储成员变量。 --- ### 注意事项 尽管减少内存消耗看似有利,但频繁切换字节对齐策略会增加维护复杂度以及降低性能优化机会。因此建议只针对必要场景才启用此类特性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值