结构体对齐规则及举例
结构体概念
- 一种数据结构;
- C语言中聚合数据类型的一类;
- 可以被声明为变量、数组、指针等,用以实现比较复杂的数据结构;
- 是一系列元素的集合,这些元素被称为结构体成员;
- 结构体成员需要用结构体名访问。
结构体声明
struct A{ //struct是关键字, A是结构体标志
int a; //a,b是结构体成员列表
char b;
}s1; //s1是结构体变量
注意事项:
- 一般情况下,声明出现三个部分的两个部分即可(标志或者变量可省略)
- 结构体可以包含其他结构体,也可以包含指向自己的指针
- 结构体互相包含时,需要对其中一个进行不完整声明
C语言中char、short、int、long各占多少字节
编程语言上的32位与64位差异主要体现在基本类型的位长上。C/C++等语言仅仅定义了这些基本数据类型之间的关系,并没有严格定义它们的字长。不同操作系统平台上存在不同。注意占据的字节数跟操作系统没有关系,跟使用的编译器有关,和编译器的数据模型相关
其中,LP64,ILP64,LLP64是64位平台上的字长模型,ILP32和LP32是32位平台上的字长模型。LP64指long和pointer是64位,ILP64指int、long和pointer是64位,LLP64指long long和pointer是64位,ILP32指int、long和pointer是32位,LP32指long和pointer是32位的。
32位windows采用的是ILP32数据模型,64位windows采用的是LLP64数据模型,32位Linux采用的是ILP32模型,64位Unix / Linux使用的是LP64模型。
在标准C中规定,长整型(无论无符号或者有符号)至少占用32位,标准没有规定 long 长度,这是编译器和系统选择的。在OS的ABI中应当有数据类型大小的定义。
以下引自维基 64位数据模型
- 在32位程序中,指针和数据类型(例如整数)通常具有相同的长度。在64位计算机上不一定是这样。因此,在C等编程语言及其后代(例如C ++和Objective-C )中混合数据类型可能适用于32位实现,但不适用于64位实现。
- 在许多用于64位计算机上C和C派生语言的编程环境中,int变量仍然是32位宽,但是长整数和指针是64位宽。这些被描述为具有LP64 数据模型。另一个替代方法是ILP64数据模型,其中所有三种数据类型均为64位宽,甚至是短整数也为64位宽的SILP64。但是,在大多数情况下,所需的修改相对较小且简单明了,许多编写良好的程序可以简单地为新环境重新编译而无需更改。另一种替代方法是LLP64模型,它通过使两个保持与32位代码的兼容性int和long为32位。LL指的是long long整数类型,在包括32位环境在内的所有平台上至少为64位。
数据类型转换原则
C语言中编译器字节对齐
自然对齐
对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。
字节对齐
在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”.
为什么要字节对齐
需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误,举个例:
char ch[8];
char *p = &ch[1];
int i = *(int *)p;
运行时会报segment error,而在x86上就不会出现错误,只是效率下降。
正确处理字节对齐
对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
联合 :按其包含的长度最大的数据类型对齐。
结构体: 结构体中每个数据类型都要对齐。
比如有如下一个结构体:
struct stu
{
char sex;
int length;
char name[10];
};
struct stu my_stu;
由于在x86下,GCC默认按4字节对齐,它会在sex后面跟name后面分别填充三个和两个字节使length和整个结构体对齐。于是我sizeof(my_stu)会得到长度为20,而不是15.
__attribute__选项
我们可以按照自己设定的对齐大小来编译程序,GNU使用__attribute__选项来设置,比如我们想让刚才的结构按一字节对齐,我们可以这样定义结构体
struct stu
{
char sex;
int length;
char name[10];
}attribute ((aligned (1)));
struct stu my_stu;
则sizeof(my_stu)可以得到大小为15。上面的定义等同于
struct stu
{
char sex;
int length;
char name[10];
}attribute ((packed));
struct stu my_stu;
attribute((packed))得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐.
什么时候需要设置对齐
1.在设计不同CPU下的通信协议;
2.编写硬件驱动程序时寄存器的结构这两个地方都需要按一字节对齐;
3.文件系统中数据存储(项目代码中使用的原因)
更改C编译器的缺省字节对齐方式
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
- 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
- 使用伪指令#pragma pack (),取消自定义字节对齐方式。
另外,还有如下的一种方式:
- attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
- attribute ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
结构体对齐规则
- 计算结构体大小不是元素单纯相加;
- 32位CPU取四个字节比一个字节更高效方便;
- 若每个元素首地址都是4的整数倍,取数据元素就能更高效方便;
- 每个特定平台上编译器都有自己的默认的对齐系数(对齐模数),可通过编译命令来改变这一系数;
- #pragma pack(n);//n=1,2,4,8,16,其中n就是你想要指定的系数。
- 一般情况下32位默认4字节对齐。
对齐原因
- 平台原因(移植原因)
不是所有的硬件平台都能访问任意地址的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据的,否则抛出硬件异常。 - 性能原因
数据结构(尤其是栈),应该尽可能在自然边界上对齐,因为在访问为对齐的内存时,处理器需要访问两次,而对齐的内存处理器只需要访问一次。
对齐规则
- 第一个成员在与结构体变量偏移量(offset)为0的地址处。
- 其他成员变量要对齐到对齐数的整数倍的地址处。
- 对齐数 = 对齐系数 与 该成员大小的较小值。
- #pragma pack(n);中的n就是对齐系数。
- VS中默认的值为8;linux中的默认值为4。
- 结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
举例说明对齐原则
1.下面的结构体大小分别是多大(假设32位机器)?
struct A {
char a; //1
char b; //1
char c; //1
};
//进行整体对齐,最大类型为1<对齐系数4,按1整体对齐,所以1+1+1=3
struct B {
int a; //4
char b; //1
short c;//2
};
//进行整体对齐,最大类型为4=对齐系数4,所以按4整体对齐4 1+2=3<4 对齐4 所以4+4=8
struct C {
char b; //1
int a; //4
short c;//2
};
//进行整体对齐,最大类型为4=对齐系数4,所以按4整体对齐 1<4(对齐4) 4=4 2<4(对齐4) 所以4+4+4=12
#pragma pack(2)
struct D {
char b; //1
int a; //4
short c;//2
};
//进行整体对齐,最大类型为4>对齐系数n(2),所以按2整体对齐 1<2(对齐2)4>2(2的倍数) 2=2 所以2+4+2=8
答案及解析:3 8 12 8
2. 有一个如下的结构体:
struct A{
long a1;
short a2;
int a3;
int *a4;
};
请问在64位编译器下用sizeof(struct A)计算出的大小是多少?
24
28
16
18
答案及解析:24
64位编译器下:指针8字节(一定不能忘记),题目不说默认4字节对齐
long a1; //8
short a2; //2 8+2=10(不是4的倍数)对齐到4的倍数12
int a3; //4 4+12=16(4的倍数)
int *a4; //8 8+16=24(4的倍数)
3.在32位cpu上选择缺省对齐的情况下,有如下结构体定义:
struct A{
unsigned a : 19;
unsigned b : 11;
unsigned c : 4;
unsigned d : 29;
char index;
};
则sizeof(struct A)的值为()
9
12
16
20
答案及解析16
题目不说,默认4字节对齐
19+11=30<32bit 4
4+29=33>32bit 4+4
1byte=8bit 1 对齐到 4
4+4+4+4=16
C++中的结构体struct—类
- 在C++中struct是对于C语言中struct的扩展,因为C++是面向对象的语言,可以描述对象的行为,所以在C++中struct内可以放函数,从而就具有类的功能,但又和class有一定的区别。
class和struct的区别
- class默认成员是私有的private,struct默认成员是公有的public
- class继承默认是私有的private,struct继承默认是公有的public
- C++中结构体可以不要struct关键字,C语言中除了typedef定义别名之外是必须要struct关键字。
参考:https://www.cnblogs.com/wsq-888/p/jie-gou-ti-dui-qi-gui-ze-ji-ju-li.html