C语言结构体字节对齐与gcc手动设置对齐__attribute__((aligned(n)))和__attribute__((packed))

C语言结构体字节对齐与gcc手动设置对齐__attribute__((aligned(n)))和__attribute__((packed))
一、字节对齐规则
【规则一】数据成员对齐规则:变量只能从他的长度的整数倍地址开始存储

第一个数据成员放在 offset 为 0的地方,以后每个数据成员的对齐按照操作系统的基本字节单位(32位操作系统为4,64位操作系统为8)和这个数据成员自身长度中,比较小的那个进行。
即以后每个数据成员放在 offset=
m
i
n
(











,








)
×


【规则二】整体对齐规则:跟最大数据成员长度的整数倍对齐

在数据成员完成各自对齐之后,结构体(或联合体)本身也要进行对齐。
所有结构体成员的字节长度 没有超出(<=) 操作系统的基本字节单位(32位操作系统为4,64位操作系统为8),按照结构体中字节数最大的变量长度来对齐
结构体中某个成员的字节长度 超出(>) 操作系统基本字节单位,按照系统基本字节单位来对齐

【规则三】结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素长度的整数倍地址开始存储。

从操作系统的基本字节单位(32位操作系统为4,64位操作系统为8)和其内部最大元素长度两者中取较小值,从该值的整数倍地址开始存储。
即 offset=
m
i
n
(











,















)
×


特别地,如果结构体成员指定了__attribute__((aligned(n))),那么从n的整数倍地址开始存储。(见“七、嵌套结构体”的例子)

二、attribute((aligned(n)))
attribute((aligned(n)))中,n的有效参数为2的幂值,32位最大为
2
32
,64位为
2
64
,这个时候编译器会将让n与默认的对齐字节数进行比较,取较大值为对齐字节数,与#pragma pack(n)恰好相反。

它的作用是让整个结构体变量整体进行n字节对齐(注意是结构体变量整体n字节对齐,而不是结构体内各数据成员也要n字节对齐)

三、attribute((packed)) 取消编译时对齐优化
attribute((packed)) 为取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,也就是采用1字节对齐。

四、attribute()在结构体类型中的使用方法
attribute() 的位置比较灵活

定义结构体时不对类型重命名,即不使用 typedef 时:

struct mystruct
{
/成员变量定义/
}attribute() /(可同时在这定义变量)/;

struct attribute() mystruct
{
/成员变量定义/
}/(可同时在这定义变量)/;
定义结构体同时对类型进行重命名,即使用 typedef 时:

typedef struct mystruct
{
/成员变量定义/
}attribute() mystruct_t;

typedef struct attribute() mystruct
{
/成员变量定义/
} mystruct_t;
五、测试代码
#include <stdio.h>

typedef struct A {
int v1;
double v2;
char v3;
} A_t;

typedef struct B {
int v1;
double v2;
char v3;
}attribute((aligned(4))) B_t;

typedef struct attribute((aligned(16))) C {
int v1;
double v2;
char v3;
} C_t;

typedef struct attribute((packed)) D {
int v1;
double v2;
char v3;
} D_t;

int main() {
printf(“%d\n”, (int)sizeof(A_t));
printf(“%d\n”, (int)sizeof(B_t));
printf(“%d\n”, (int)sizeof(C_t));
printf(“%d\n”, (int)sizeof(D_t));

A_t a;
a.v1 = 0x11;
a.v2 = 0x1;
a.v3 = 0xa;

B_t b;
b.v1 = 0x22;
b.v2 = 0x2;
b.v3 = 0xb;

C_t c;
c.v1 = 0x33;
c.v2 = 0x3;
c.v3 = 0xc;

D_t d;
d.v1 = 0x44;
d.v2 = 0x4;
d.v3 = 0xd;
return 0;
}
执行编译指令:

geekziyu@geekziyu-ubuntu-1:~/CLionProjects/c-helloworld$ gcc -m32 main.c -o main.32.o
geekziyu@geekziyu-ubuntu-1:~/CLionProjects/c-helloworld$ gcc main.c -o main.o

执行结果如下:

geekziyu@geekziyu-ubuntu-1:~/CLionProjects/c-helloworld$ ./main.o
24
24
32
13
geekziyu@geekziyu-ubuntu-1:~/CLionProjects/c-helloworld$ ./main.32.o
16
16
16
13

5.1 64位程序
结构体 A_t a 对象结构如下图所示:

a.v1 从偏移地址0开始存储,int占4个字节 【规则一】
a.v2 是double类型,长度为8个字节,64位程序默认对齐数也是8个字节,因此从8的整数倍偏移地址开始存储,即 offset=8【规则一】
a.v3 是char类型,长度为1个字节,64位程序默认对齐数是8个字节,因此可以从 offset=9 开始存储 【规则一】
接着,当前结构体 A_t a 整体 应该按照64位默认对齐数8B来对齐,即整体大小是8的整数倍
现在已经有17个字节了,因此,通过对齐填充扩展到size=24 【规则二】
0x3ff0000000000000 等于double型的1

结构体 B_t b 对象结构如下图所示:

attribute((aligned(4))) 并 不 要求结构体数据成员对齐;
0x4000000000000000 等于double型的2

结构体 C_t c 对象结构如下图所示:

0x4008000000000000 等于double型的3

结构体 D_t d 对象结构如下图所示:

按一个字节对齐是最简单,结构也是最紧凑的,没有对齐填充的部分。
0x4010000000000000 等于double型的4

5.2 32位程序
结构体 A_t a 对象结构如下图所示:

a.v2 是double类型,长度为8个字节,32位程序默认对齐数是4个字节,取较小值4B,因此从4的整数倍偏移地址开始存储,即 offset=4【规则一】
整体是按4B还是8B对齐暂时不好判断,需要增加实验。

结构体 B_t b 对象结构如下图所示:

整体是按4B还是8B对齐暂时不好判断,需要增加实验。

结构体 C_t c 对象结构如下图所示:

整体是按16B还是8B对齐暂时不好判断,需要增加实验。

结构体 D_t d 对象结构取消了对齐,因此结构和64位程序一样。

六、分析与验证
6.1 规则一与操作系统的基本字节单位有关
例6.1-1:把以下程序编译为64位程序并运行:

typedef struct A {
char v1;
int v2;
} A_t;

int main() {
A_t a;
a.v1 = 0x11;
a.v2 = 0x22;
return 0;
}
预期结果分析:
结构体成员变量 i 长度为 4B,编译为64位程序,则基本字节单位为 8B,取较小值 4B,因此它从偏移地址 4 开始存储,即 offset=4

根据 GDB 分析后,该结构体在栈上的存储如下图所示:

例6.1-2:把以下程序编译为32位程序并运行:

#include <stdio.h>

typedef struct A {
char v1;
double v2;
} A_t;

int main() {
printf(“%d\n”, (int) sizeof(A_t)); // 输出结果为12
A_t a;
a.v1 = 0x11;
a.v2 = 0x2;
return 0;
}
预期结果分析:
结构体成员变量 v2 程度是 8B,编译为64位程序,则基本字节单位为 4B,取较小值 4B,因此它从偏移地址 4 开始存储,即 offset=4

根据 GDB 分析后,该结构体在栈上的存储如下图所示:

6.2 规则一与__attribute__((aligned(n)))无关
上一节6.1的结构体都加上 attribute((aligned(n))),第二个成员的起始偏移地址 offset 也不会发生变化

6.3 规则二不是32位系统就按照4字节对齐,64位系统就按照8字节对齐
typedef struct A {
char v1;
char v2;
char v3;
} A_t;
上述程序在32位和64位操作系统下,sizeof(A_t)都等于3。而并非32位系统下等于4,64位系统下等于8

这个例子就说明:如果 所有结构体成员的长度 没有超出(<=) 操作系统的基本字节单位(32位操作系统为4,64位操作系统为8),按照结构体中字节数最大的变量长度来对齐

6.4 规则二并不总是按最大成员长度对齐
typedef struct A {
char v1;
double v2;
} A_t;
上述程序在32位操作系统下,sizeof(A_t) 等于12。正好是32位系统基本字节单位4B的整数倍,而非最大数据成员长度8B的整数倍16。

这个例子就说明:结构体中某个成员的字节长度 超出(>) 操作系统基本字节单位,按照系统基本字节单位来对齐。

6.5 规则二与__attribute__((aligned(n)))的关联
我在结构体内就放一个成员变量,然后在32位和64位上测试 sizeof() 的结果填写到单元格中,以下是汇总表格:

结构体内成员变量类型 attribute((aligned(n))) 32位 64位 备注
char 不设置 1 1 32位和64位的sizeof的结果都可以证明
对齐字节数是从 操作系统基本字节单元 和 最大数据成员长度 中选取较小值
char n=4 4 4
char n=8 8 8
char n=16 16 16
char n=32 32 32
int 不设置 4 4
int n=4 4 4 64位的sizeof的结果相对比较明显地证明
对齐字节数是从 操作系统基本字节单元 和 最大数据成员长度 中选取较小值
int n=8 8 8
int n=16 16 16
int n=32 32 32
当你没有设置 attribute((aligned(n))) 时,对齐字节数是从 操作系统基本字节单元 和 最大数据成员长度 中选取较小值;
但是,当你设置了 attribute((aligned(n))) 之后,整体就得按 n 来对齐。

七、嵌套结构体
在64位操作系统下编译运行以下程序:

#include <stdio.h>

typedef short t;

typedef struct A {
t v1;
char v2;
} A_t;

typedef struct B {
char v1;
A_t v2;
} B_t;

typedef struct attribute((aligned(32)))C {
char v1;
A_t v2;
} C_t;

int main() {
printf(“%d\n”, (int)sizeof(A_t));
printf(“%d\n”, (int)sizeof(B_t));
printf(“%d\n”, (int)sizeof(C_t));

A_t a;
a.v1 = 0x1;
a.v2 = 0x11;

B_t b;
b.v1 = 0x22;
b.v2 = a;

C_t c;
c.v1 = 0x33;
c.v2 = a;

return 0;
}
结构体 A_t a 的内存结构如下图:

结构体 B_t b 的内存结构如下图:

结构体 C_t c 的内存结构如下图:

B_t b 和 C_t c 第二个成员结构体变量,都是从 2B 的整数倍地址开始的,2B 是 操作系统基本字节单元8B 和结构体 A_t 中的最大成员长度2B 中的较小值。
B_t b 和 C_t c 分布基本相同,差别是 C_t c 的对齐填充更多。

如果把结构体 A 改为整体 8B 对齐:

typedef struct attribute((aligned(8))) A {
t v1;
char v2;
} A_t;
结构体 A_t a 的内存结构如下图:

结构体 B_t b 的内存结构如下图:

结构体 C_t c 的内存结构如下图:

B_t b 和 C_t c 第二个成员结构体变量,按照 8B 的整数倍地址开始,显然是因为__attribute__((aligned(8))) 指定的 8B。
为了验证 attribute((aligned(8))) 的“绝对成立性”,我又去编译了32位程序,结果还是从 8B 的整数倍地址开始!
B_t b 和 C_t c 分布基本相同,差别是 C_t c 的对齐填充更多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值