C语言中结构体对齐方式的问题

本文详细解释了C语言中结构体的对齐方式及其影响,包括默认对齐、使用#pragma pack指令自定义对齐,并通过实例展示了不同对齐设置下结构体成员的布局和结构体的大小变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       最近项目要和服务器通信,采用自定义的协议,而本人有只会结构体定义和位域的方法,导致有些问题解决不了。综合网上各位

大神的指教才解决。主要就是对齐方式的问题吧。以前也没怎么用到过,现在既然遇到就记录下来吧。


      在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间;各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。  例如,下面的结构各成员空间分配情况。  

       

  1. struct  tagTest  {    
  2. char   x1;    
  3. short  x2;    
  4. float  x3;    
  5. char   x4;    
  6. };  

        结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。

       编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。

     

下面举例说明其用法。
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#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。
下面这段代码是由  zhao4zhong1  赵4老师  提供的,研究后大概就会明白的。
#include <stdio.h>
#define field_offset(s,f) (int)(&(((struct s *)(0))->f))
struct AD  { int a; char b[13]; double c;};
#pragma pack(push)
#pragma pack(1)
struct A1  { int a; char b[13]; double c;};
#pragma pack(2)
struct A2  { int a; char b[13]; double c;};
#pragma pack(4)
struct A4  { int a; char b[13]; double c;};
#pragma pack(8)
struct A8  { int a; char b[13]; double c;};
#pragma pack(16)
struct A16 { int a; char b[13]; double c;};
#pragma pack(pop)
int main() {
    printf("AD.a %d\n",field_offset(AD,a));
    printf("AD.b %d\n",field_offset(AD,b));
    printf("AD.c %d\n",field_offset(AD,c));
    printf("AD sizeof %d\n", sizeof(AD));
    printf("\n");
    printf("A1.a %d\n",field_offset(A1,a));
    printf("A1.b %d\n",field_offset(A1,b));
    printf("A1.c %d\n",field_offset(A1,c));
    printf("A1 sizeof %d\n", sizeof(A1));
    printf("\n");
    printf("A2.a %d\n",field_offset(A2,a));
    printf("A2.b %d\n",field_offset(A2,b));
    printf("A2.c %d\n",field_offset(A2,c));
    printf("A2 sizeof %d\n", sizeof(A2));
    printf("\n");
    printf("A4.a %d\n",field_offset(A4,a));
    printf("A4.b %d\n",field_offset(A4,b));
    printf("A4.c %d\n",field_offset(A4,c));
    printf("A4 sizeof %d\n", sizeof(A4));
    printf("\n");
    printf("A8.a %d\n",field_offset(A8,a));
    printf("A8.b %d\n",field_offset(A8,b));
    printf("A8.c %d\n",field_offset(A8,c));
    printf("A8 sizeof %d\n", sizeof(A8));
    printf("\n");
    printf("A16.a %d\n",field_offset(A16,a));
    printf("A16.b %d\n",field_offset(A16,b));
    printf("A16.c %d\n",field_offset(A16,c));
    printf("A16 sizeof %d\n", sizeof(A16));
    printf("\n");
    return 0;
}
//AD.a 0
//AD.b 4
//AD.c 24
//AD sizeof 32
//
//A1.a 0
//A1.b 4
//A1.c 17
//A1 sizeof 25
//
//A2.a 0
//A2.b 4
//A2.c 18
//A2 sizeof 26
//
//A4.a 0
//A4.b 4
//A4.c 20
//A4 sizeof 28
//
//A8.a 0
//A8.b 4
//A8.c 24
//A8 sizeof 32
//
//A16.a 0
//A16.b 4
//A16.c 24
//A16 sizeof 32
//

好咯,第一篇博客就这样咯~感谢优快云上各位大神提供的资料啊,受益匪浅啊,加入优快云没多久,以后有啥就写写博客吧,不喜勿喷~~~O(∩_∩)O

### C语言结构体对齐的原理和方法 #### 一、结构体对齐的基本概念 在C语言中,为了提高数据访问效率以及满足硬件架构的要求,编译器会对结构体中的成员进行内存对齐处理。这种对齐遵循一定的规则,主要包括以下几个方面: 1. **第一个成员的位置** 结构体的第一个成员始终存储在其分配空间的起始地址上[^2]。 2. **其他成员的对齐规则** 对于后续成员,它们会被放置在一个特定数值(称为对齐数)的整数倍地址处。这个对齐数通常是该成员类型的大小或者编译器指定的默认对齐值两者之间的最小值。 3. **填充字节的作用** 如果某个成员无法直接放在下一个可用位置而违反上述规则,则会通过插入额外的填充字节来调整其位置,从而实现正确的对齐[^3]。 4. **整体大小调整** 整个结构体最终占据的空间也需是对其中最宽基本类型成员大小的一个整数倍;如果必要的话,在最后一个字段后面还会增加一些尾部填充以达到这一目标。 #### 二、影响因素分析 - **平台差异** - 不同的操作系统可能有不同的默认对齐策略。例如,在Windows环境下使用Microsoft Visual Studio开发工具链时,默认情况下采用的是8字节边界对齐模式;而在大多数Linux发行版里则倾向于更紧凑些即按4字节单位排列。 - **自定义指令控制** - 开发者可以利用预处理器命令`#pragma pack(n)`来自行设定新的打包参数n (通常为1,2,4,...),以此改变当前作用域内的所有新声明对象内部组件间的间隔程度[#1]. #### 三、实例解析 考虑以下代码片段及其运行结果解释: ```c #include<stdio.h> #pragma pack(8)//交代按照8字节对齐宽度 struct A { char x;//占用1字节 char y; char z; }; struct B { char x; int y;//占用4字节 char z; }; int main(){ struct A a; struct B b; printf("%d\n",sizeof(a)); // 输出3 printf("%d\n",sizeof(b)); // 输出12 return 0; } ``` 对于结构A而言,由于三个字符型变量连续分布并无任何中间隔开的需求所以实际消耗正好等于各自长度之和也就是3字节[^1]。 然而针对B来说情况变得复杂起来因为存在不同类型的数据混合在一起的情况: - 首先是单字节的'x',它自然位于偏移量为零的地方; - 接下来的'int y'(四字节),考虑到前面已经用了1个字节因此这里需要补齐至最近合适的四位点再开始计数故产生了三个空闲位使得y真正意义上的起点应该是第四个单元格而非第二个; - 最后的另一个单独存在的char 'z'同样也要顾及之前留下的空白进而再次跳过直至找到适合自己的八分之一界限才正式安顿下来再加上必要的结尾补充总共构成了十二个有效区域^. #### 四、总结说明 综上所述,C编程环境里的复合资料形式——结构体并非简单地把各个组成部分堆叠起来就算完成而是经过精心设计过的布局过程旨在兼顾性能表现的同时又能保持良好的可移植特性.[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值