C语言结构体类型的定义和使用(二):结构体字节对齐

34 篇文章 6 订阅

0x00 前言

文章中的文字可能存在语法错误以及标点错误,请谅解;

如果在文章中发现代码错误或其它问题请告知,感谢!

本文档试验所使用的系统版本为:Linux version 3.13.0-24-generic,32位

0x01 结构体定义及结构体对齐意义

1 结构体定义简述

在C语言中,我们将关联的变量组成一个数据组合即结构体,构成结构体的元素类型可以是类似如int、char、float这些基本数据类型,也可以是例如数组、联合体这类的复合数据类型。这些成员构成的结构体在系统中按照其自然边界(alignment)分配内存空间。

2 结构体对齐意义

结构体对齐存的意义是可以简化CPU和内存之间的传输设计,提高访问速度

0x02 结构体对齐规则

1 结构体的起始地址、对齐方式以及其成员偏移大小

理论上,内存的基本单位使字节(byte),系统可以从任意地址访问某种基本类型数据。但实际上,系统对基本类型数据在内存中的存放有一定的规则。比如 系统会要求结构体的起始地址为 结构体对齐字节数 与 该结构体所含成员类型中最大值 二者之间最小者的整倍数(没有指定对齐字节数的情况下,结构体地址能够被该结构体所含成员类型中最大值 与 系统默认的对齐字节数二者最小值整除),例如结构体变量的首地址能够被其结构体对齐字节数大小所整除,例如当编译器将结构体设置为4字节对齐且结构体每个成员类型大小都大于等于4字节时,结构体整体必须从4字节对齐处存放。结构体总大小为 结构体对齐字节数 与 该结构体所含成员类型中最大值 二者之间最小者的整倍数(在没有人为指定对齐字节数的情况下,结构体总大小为 系统默认的对齐字节数 与 该结构体所含成员类型中最大值 二者之间最小者的整倍数),也就是结构体成员总大小除以对齐字节数必须可以整除,当出现整除不了的情况则在结构体最后一个成员充填字节数以满足,例如某一个结构体成员类型依次为int ,char,short,short,现在要求是4字节对齐,由于前三个成员加在一起为8个字节4+2(1+1)+2,若再加上short 类型的2字节,则一共10个字节,但是不能被4整除,所以最后一个short类型变量充填为4个字节,则最后一共为12个字节,可以被4整除。还有就是结构体每个成员相对于结构体首地址的偏移都是 结构体对齐字节数 与 该成员类型大小 二者之间的最小者 的整数倍(在没有人为指定对齐字节数的情况下,结构体总大小为 系统默认的对齐字节数 与 该成员类型大小 二者之间最小者的整倍数)。 上例中4字节对齐时,结构体的第三个成员short类型大小为2字节,但是它前两个成员int和char合为5字节,为了满足偏移可以整除short的2字节,系统会为char这个成员充填一个字节。

2 结构体对齐规则总结

通过上一小节,总结对齐规则如下,但首先我们要明白三个概念:

自身对齐值:数据类型本身的对齐值,对于结构体来说就是其最宽成员类型的长度或该成员类型长度(计算结构体起始地址/大小指前者,计算结构体偏移时指后者);

指定对齐值:编译器或编程人员指定的对齐值,一般默认为4字节对齐;

有效对齐值:自身对齐值和指定对齐值中较小的那个。

然后抛出对齐规则,对齐有三个个规则:

1.结构体的起始地址必须是该结构体有效对齐值的整数倍;
2.结构体大小必须是有效对齐值的整数倍;
3.结构体中每个成员与结构体首地址的偏移必须是有效对齐值的整数倍。

0x03 更改结构体缺省字节对齐方式

1#pragma pack (n)以及#pragma pack ()

要想字节对齐,可以使用#paragma pack设置,由其指定数据在内存中的对齐方式,使用方式如下:

#pragma pack (n) :编译器按照自定义的n个字节方式对齐
#pragma pack () :取消自定义字节对齐方式

现在举一个例子,在代码中使用#paragma pack对齐4个结构体,对齐字节数分别为默认对齐(即去掉#paragma pack),1、4、8,然后将这4个结构体大小打印出来:

#include<stdio.h>
#include<string.h>

#pragma pack(1)  //指定对齐字节,默认对齐时去掉

struct mystruct1
{
	char  a;	
	short b;
	short c;
}aa;

struct mystruct2
{
	char  a;
	char b;	
	int c;
	short d;
}bb;

struct mystruct3
{
	char  a;
	int c;
	short d;
	char b;	
}cc;

struct mystruct4
{
	char  a;
	int b;
	short c;
	double d;	
}dd;

#pragma pack()	//默认对齐时去掉
int main()
{
	printf("sizeof(struct1) = %d\n",sizeof(aa));
	printf("sizeof(struct2) = %d\n",sizeof(bb));
	printf("sizeof(struct3) = %d\n",sizeof(cc));
	printf("sizeof(struct4) = %d\n",sizeof(dd));
	return 0;
}

编译及运行结果如下:
默认对齐:
在这里插入图片描述
1字节对齐:
在这里插入图片描述
4字节对齐:
在这里插入图片描述
8字节对齐:
在这里插入图片描述
可以看到 ,#pragma pack(n)中不同的字节对齐数,有可能会导致同样的结构体最终的大小不同,下面分析大小不同的原因:
#pragma pack(1):
结构体aa自身对齐值为2,指定对齐值为1,则有效对齐值为1,最终大小为5;
结构体bb自身对齐值为4,指定对齐值为1,则有效对齐值为1,最终大小为8;
结构体cc自身对齐值为4,指定对齐值为1,则有效对齐值为1,最终大小为8;
结构体dd自身对齐值为8,指定对齐值为1,则有效对齐值为1,最终大小为15;

#pragma pack(4):
结构体aa自身对齐值为2,指定对齐值为4,则有效对齐值为2,最终大小为6;
结构体bb自身对齐值为4,指定对齐值为4,则有效对齐值为4,最终大小为12;
结构体cc自身对齐值为4,指定对齐值为4,则有效对齐值为4,最终大小为12;
结构体dd自身对齐值为8,指定对齐值为4,则有效对齐值为4,最终大小为20;

#pragma pack(8):
好的,到目前为止,若按照对齐规则,不看实际输出,#pragma pack(8)应该如下:
结构体aa自身对齐值为2,指定对齐值为8,则有效对齐值为2,最终大小为6;
结构体bb自身对齐值为4,指定对齐值为8,则有效对齐值为4,最终大小为12;
结构体cc自身对齐值为4,指定对齐值为8,则有效对齐值为4,最终大小为12;
结构体dd自身对齐值为8,指定对齐值为8,则有效对齐值为8,最终大小为24;

但是,仔细看看8字节对齐时的输出结果,dd结构体的大小是20,而不是24。使用gcc -v查看我的gcc编译版本为:gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3) ,应该没有什么问题,所以现在将代码运行在vs2010中再看编译结果:
在这里插入图片描述
这次输出结构体dd的大小符合规则定义。导致这个原因可能是 #pragma pack(N) 中的 N 超过默认对齐字节数,即如果默认对齐是 4 的话,N的取值可以是 1、2、4,超过4 之后作为 4 处理,但是在 Windows 等系统上没有这个限制。这个跟Linux GCC编译有关,更多关于GCC字节对齐问题,参考这篇文档:
http://blog.chinaunix.net/uid-8074738-id-2517383.html

因为这篇文章主要写字节数对齐,达到对齐目的即可,所以对#pragma pack() 的进一步用法例如对齐字节数压入栈顶以及弹出栈顶对齐字节数等,可以参考这一篇文章:
https://www.jianshu.com/p/90a6eef329ec

2__attribute__((aligned (n)))以及 __ attribute__ ((packed))

__attribute __ 关键字允许开发人员在定义struct、union、变量等类型时指定其特殊属性。使用时要在这个关键字后面使用双括号表明属性内容。__attribute__不属于标准C语言,它是GCC对C语言的一个扩展用法。

attribute((aligned(n))):此属性指定了指定类型的变量的最小对齐数(以字节为单位)。若结构体中有成员的长度大于n,则按照最大成员的长度来对齐。如果使用缺省属性(aligned()),编译器编译器根据目标机制采用最大最有益的方式对齐。

attribute ((packed)):此属性取消在编译过程中的优化对齐。

注意:和上一小节试验的结果一样,使用__attribute__ 会对齐属性中制定的有效对齐数会受到GCC编译器的限制,即如果系统默认对齐数是4字节对齐,若指定16字节对齐,那么最后对齐数会是4字节对齐。

另外, __attribute __ 除了可以设置字节对齐属性,还有可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute)等,各属性的内容以及用例可以参考以下文档:
https://www.jianshu.com/p/29eb7b5c8b2d

举例如下:

#include <stdio.h>
struct A{
  char a;     
  unsigned short b;         
  int  c;             
  long d;              
  unsigned long long e;
  char f;               
};

struct B{
  char a;     
  unsigned short b;         
  int  c;             
  long d;              
  unsigned long long e;
  char f;               
}__attribute__((aligned));

struct C{
  char a;     
  unsigned short b;         
  int  c;             
  long d;              
  unsigned long long e;
  char f;               
}__attribute__((aligned(1)));


struct D{
  char a;     
  unsigned short b;         
  int  c;             
  long d;              
  unsigned long long e;
  char f;               
}dd __attribute__((aligned(4)));

struct E{
  char a;     
  unsigned short b;         
  int  c;             
  long d;              
  unsigned long long e;
  char f;               
}__attribute__((aligned(8)));

struct F{
  char a;     
  unsigned short b;         
  int  c;             
  long d;              
  unsigned long long e;
  char f;               
}__attribute__((packed));

int main()
{
  printf("aa = %d, bb = %d, cc = %d, dd = %d, ee = %d, ff = %d\n",
  sizeof(struct A), sizeof(struct B), sizeof(struct C), sizeof(struct D), sizeof(struct E), sizeof(struct F));
  return 0;
}

编译及运行结果如下:
在这里插入图片描述
结构体A自身对齐值为8,指定对齐值为系统默认4,则有效对齐值为4,最终大小为24;
结构体B中,attribute((aligned))没有参数,则编译器根据目标机制采用最大最有益的方式对齐,本例试验中系统规定为16字节对齐,所以最终大小为32;
结构体C中,attribute((aligned(1))),意为采用1字节对齐,但是该结构体中有一个成员类型大小为8,所以采用8字节对齐,最终大小为24;
结构体D中,attribute((aligned(4))),意为采用4字节对齐,但是该结构体中有一个成员类型大小为8,所以采用8字节对齐,最终大小为24;
结构体E中,attribute((aligned(8))),意为采用8字节对齐,该结构体中成员类型最大为8,和指定的对齐字节数一样,所以采用8字节,最终大小为24;
结构体F使用__attribute__ ((packed)),即取消优化对齐,则最终大小为各个类型变量值累加为20。

以上。
参考文档:
1.https://zhuanlan.zhihu.com/p/44625744
2.https://www.cnblogs.com/ransn/p/5081198.html
3.https://www.cnblogs.com/motadou/archive/2009/01/17/1558438.html
4.https://www.cnblogs.com/heart-flying/p/9556401.html
5.http://blog.chinaunix.net/uid-8074738-id-2517383.html
6.https://www.jianshu.com/p/90a6eef329ec
7.https://blog.csdn.net/fengbingchun/article/details/81321419
8.https://www.jianshu.com/p/29eb7b5c8b2d
9.http://www.voidcn.com/article/p-plcsmbnd-ur.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值