前文介绍了结构体内存的对齐规则。有趣的是,这些规则并非是绝对不可改变的。
C语言的实现相比于其他高级语言显得更底层,给了程序员很大的灵活性。比如,我们可以通过一些方式来设置C语言里面的对齐规则。但C语言本身并没有规定这些使用方法,是由编译器定义的。(可以理解为,这篇介绍的,其实都是GCC在C基础上的实现,至于Clang为什么也支持,是因为他为了兼容性,模仿GCC,其他编译器也是,但已经说不清是谁在模仿谁了。)
方法一 预编译 #pragma pack(n)
参考:百度百科
写一段最简的代码,说明#pragma pack(n)的使用方法:
#pragma pack(1)
struct test_st{
char hate;
int like;
};
#pragma pack(4)
struct test_stt{
char hate;
int like;
};
struct test_st test1;
struct test_stt test2;
int main(void)
{
printf("Size: Char[%d], Short[%d], int[%d], long[%d], longlong[%d]\r\n", sizeof(char), sizeof(short), sizeof(int), sizeof(long), sizeof(long long));
printf("Sizeof struct Test-1 = %d\r\n", sizeof(test1));
printf("Sizeof struct Test-2 = %d\r\n", sizeof(test2));
return 0;
}
运行后得到:
Size: Char[1], Short[2], int[4], long[8], longlong[8]
Sizeof struct Test-1 = 5
Sizeof struct Test-2 = 8
也就是通过pack(n)指定了结构体中的成员以n字节对齐,这样可以保证padding member的宽度最大是n,即一次性最多填充n个padding,可以节省空间。
但以上使用并不正确,以上使用方式类似于malloc()没有free(),是不安全的。正确的应该将环境暂存,当pack功能使用完成后,再恢复。有两种方式:
一种是使用完成后来一句 #pragma pack(),恢复为默认对齐方式。
#pragma pack(1)
struct test_st{
char hate;
int like;
};
#pragma pack(4)
struct test_stt{
char hate;
int like;
};
#pragma pack() //Restore to default pack value
struct test_st test1;
struct test_stt test2;
另一种是使用前 pack(push) 暂存 pack 设置,使用后 pack(pop) 恢复 pack 设置。
#pragma pack(push) //Before changing pack value, push the current one
#pragma pack(1)
struct test_st{
char hate;
int like;
};
#pragma pack(4)
struct test_stt{
char hate;
int like;
};
#pragma pack(pop) //After using user pack value, restore the original pack value by pop.
struct test_st test1;
struct test_stt test2;
网上还查到一些其他用法:#pragma pack(show)可以用于在编译阶段显示当前的pack设置,正常会显示这样的warning:
但是在我的环境GCC下,这一句会出现这样的warning:
在网上找到了相对权威的解答:GCC 不支持 #pragma pack(show) 这个用法。
以及GCC权威的参考,其中确实没有提到show这种用法。
这里给出MicroSoft Visual C中对于这部分的说明。
也给出GCC中的参考实现,里面确实没有 show 这一 op code 对应的处理逻辑:
另外,在其实现中注意到 pack(n) 中的 n 必须是 2 的幂,如1,2,4,8,16等。
实际结构体的pack有效值为 min(所有成员中最宽的基本数据类型宽度,n)
。例如
#pragma pack(4)
struct test_stt{
short like;
};
test_stt 相当于被 pack(2)。
方法二 __ attribute__ 参数
上面的#pragma pack(n)的方式,对于整个文件或者直到下一个#pragma pack(…)的地方起作用。这里要介绍的__attribute__参数方式仅对修饰的结构体起作用。
参考一篇CSDN博主的文章:
- 可以用“__attribute__((aligned(n)))”后缀来设置对齐属性,n即代表结构体的对齐设置,当结构体中有成员的宽度大于n时,则按最大成员长度来对齐,这是GNU C的用法。
- __attribute__((packed))是“紧凑对齐”的意思,这是GCC特有的语法,相当于在结构体前使用#pragma pack(1)。
给出一个示例程序:
#include "stdio.h"
char ph; //Add a char placeholder to make struct start from an odd pointer value.
struct test_st{
char hate;
int like;
}__attribute__((packed)) test1;
struct test_stt{
char Good;
short boy;
char like;
short bad;
int girl;
}__attribute__((aligned(8))) test2;
struct test_st{
char hate;
int like;
}__attribute__((aligned(1))) test3;
int main(void)
{
printf("Size: Char[%d], Short[%d], int[%d], long[%d], longlong[%d]\r\n", sizeof(char), sizeof(short), sizeof(int), sizeof(long), sizeof(long long));
printf("Sizeof struct Test-1 = %d\r\n", sizeof(test1));
printf("Sizeof struct Test-2 = %d\r\n", sizeof(test2));
printf("Sizeof struct Test-3 = %d\r\n", sizeof(test3));
return 0;
}
结果是:
Size: Char[1], Short[2], int[4], long[8], longlong[8]
Sizeof struct Test-1 = 5
Sizeof struct Test-2 = 16
Sizeof struct Test-3 = 8
请读者自己总结下用法,并尝试写写吧。
方法三 编译选项 -fpack-struct
我在Telink TLSR8258这个MCU上写嵌入式程序的时候,注意到了自动生成的makefile文件的编译选项中有-fpack-struct,这个是gcc的编译选项,被集成到了Telink IDE中,作为优化的可选项,默认勾选:
到GCC中实测一下,代码:
struct test_st{
char meat;
short a;
int t;
}test;
int main(void)
{
printf("Size of Test = %d\r\n", sizeof(test));
return 0;
}
如果用gcc test.c -o test
编译,运行后,得到
Size of Test = 8
如果用gcc test.c -o test -fpack-struct
编译,运行后,得到
Size of Test = 7
可见,这个编译选项,是将被编译的代码中的结构体都pack起来。
下一篇:【内存对齐】第四篇·Array、Union内存对齐的规律与原则
连载中… by 2024/05/28