深入理解C语言(一)——字节对齐

字节对齐的概念

伴随计算机技术的发展,CPU的数据位宽经历了16bit,32bit,64bit,以32bit CPU访问4字节内存为例,4字节对齐的存取速度是最快的。在非对齐的情况下,CPU需要分解成两次32bit操作(大部分平台下依然只是一条指令)

不同体系结构的CPU对非对齐访问操作的支持情况也不相同。

字节对齐的问题

Q:sizeof(struct x)是多少

struct X{int a; long b; char c;};

A:这个问题的答案首先取决于目标程序是32bit还是64bit的系统;其次还取决于编译选项是否有字节对齐选项,-fpack-struct;

例如,在32bit,无对齐选项下的结果是12;

32bit,-fpack-struct=1选项的情况下,结果是9;

64bit LP64模式下,无对齐选项,结果是24

64bit LP64模式下,-fpack-struct=1情况下,结果是13

64bit LLP64模式下,无选项,结果是12

64bit LLP模式下,含-fpack-struct=1选项,结果是9

LP64和LLP64:

LP64和LLP64是不同的编译器数据模型。LP64表示long long,long 和pointer是64位,int是32位,LLP64表示long long和pointer是64位,int和long是32位。

各种数据类型的汇总如下:

Data typeILP32LP32ILP64LP64LLP64
宏定义---__LP64____LLP64__
平台win32/unix和类unixwin16unix和类unixwin64 
char88888
short1616161616
int3232643232
long3232646432
long long6464646464
pointer3232646464

字节对齐规则

a. 在C语言默认规则中,基本数据类型的对齐值与自身大小一致。比如对于int,float类型,其自身对齐值为4字节,对于double类型,其自身对齐值为8字节。

b. C语言默认规则中,聚合数据类型中每个成员都需要满足自身的对齐要求,且聚合类型整体的对齐值取决于成员中自身对齐值最大的值,聚合类型整体大小为对齐值的整数倍。聚合类型包括结构体/联合体/类等。

c.C的对齐规则与硬件平台,32位还是64位系统无关,属于语言规范。其目的是实现对每个元素(包括聚合类型内部的元素)访问都按照对齐的高效率方式。

理解C语言字节对齐规则的关键:

规则的目的是让出现在代码中的每一个基本类型数据都能够自对齐,从而提高访问效率,无论这个基本类型数据是出现在数组中,结构中,联合体中或者结构嵌套定义中等等。

比如为了结构数组中的元素自对齐,就导致了结构大小必须也是该对齐值的倍数,否则会产生矛盾。

编译器中的字节对齐规则:

gcc中编译选项-fpack-struct[=n]或预编译指令#pragma pack(n),可以限定对齐值的上限;

gcc中变量数据__attribute__((aligned(n))) 可以约定对齐值的下限。

C11 对齐关键字:_Alignas 、_Alignof

_Alignas含义和在C99中使用GUN attribute ((aligned(n))) 或者 #program pack(n)一样,可以用于指定对齐。使用_Alignas 说明符指定变量或类型的对齐值,必须为2的非负整数幂,且不小于其自身对齐值。

Alignof用于获取获取类型或变量的对齐值。

例如:

size_t typedef struct { int a; double b; } S; // alignof(S) == 8

_Alignas的使用举例:

_Alignas(2) char data;    //_Alignas指定的对齐值2大于char自身对其值,是正确的声明方式。

_Alignas(3) char data;    //3不是2的非负整数次幂,错误

字节对齐举例

以下举例都是32位ARM平台

1. 

typedef struct  {
    char  c1;

    short s2;

    char c3;

}S;

由于结构体中对齐要求最大的值是short s2,2字节对齐,因此c1后补1个字节,保证S2是2字节对齐,C3后补1个字节,保证结构体整体是2字节对齐。

2. 

typedef struct {

    char c1;

    long l2;

    char c3;

} S;

由于结构体中对齐要求最大的值是long l2,4字节对齐,因此c1后补3个字节,保证l2是4字节对齐,C3后补3个字节,保证结构体整体是4字节对齐。

typedef struct {

    char s[2];

    U64 type;

} S;

S g_var;  /*变量起点8字节对齐,大小16字节*/

#pragma pack(1)

typedef struct {

    char s[2];

    U64 type;

} S;

S g_var; /*变量起点无对齐要求,大小10字节*/

#pragma pack(4)

typedef struct {

char s[2];

} S;

#pragma pack()

/*-fpack-struct = 4选项*/

typedef struct {

    char s[2];

} S;

上面两个结构对齐值都是1,大小为2字节。

注意:#pragma pack(n),-fpack-struct = n控制对齐值上限为n,即比较自对齐值与n值,取较小值。

字节对齐的平台差异

普通内存的读取与存储访问下,不同平台的对齐要求如下

a. 不同体系结构下对非对齐访问的支持情况不同;“不支持”意味着相关操作会异常

b. power PC,intel系列CPU均支持非对齐访问,即同样一条指令既能用于对齐内存访问,也支持非对齐。

c. ARM平台支持有限的非对齐访问

1) ARM下需要设置特别的控制寄存器才能支持非对齐访问

2)ARMv7下4字节对齐访问指令支持非对齐,8字节访问指令不支持;多寄存器存取指令不支持

3) ARM V8下各种宽度内存访问指令均支持非对齐

d. mips平台通过一条指令完成对齐内存的访问,通过两条指令完成非对齐内存访问,编译器可自动选择配套指令。

非普通内存的读取与存储访问下,对齐要求比较特殊

a. 用于原子操作/锁等互斥内存读写的地址,不同平台下均要求对齐。X86下出于兼容性考虑可能例外,但非对齐的原子操作非常影响性能

b. 对于嵌入式系统外设寄存器空间的内存访问,也有对齐要求,比如ARM外设空间地址的MMU都是device类型,ARM 4字节的store/load指令对device空间访问时都要求4字节对齐。

一些建议

C语言的默认对齐规则下不会产生非对齐访问

a. 静态数据定义时编译器分配满足对齐条件的地址

b. 每一层函数对栈的使用都是8字节的倍数,所以栈中的结构也都可以满足8字节以内的对齐要求。

c. C库中的malloc也至少按照8字节对齐返回内存地址

但是开发人员在实现代码时,例如通过强转某片内存为结构体使用,就需要特别留意对齐的要求。

在编写代码时,关于字节对齐,我们需要注意以下几点:

a. 非对齐访问会影响性能(在部分内存敏感的场景下,由于增加了一条指令会导致codesize增加),甚至部分硬件平台下会产生异常

b. 定义结构时需要利用对齐知识留意元素排序,减少内存浪费

下一篇:

深入理解C语言(二)——比特序和字节序​​​​​​​

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bluetangos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值