编译预处理
专题三:编译预处理。包括以下章节:
- 编译过程简介
- 宏定义与宏使用分析
- 条件编译使用分析
- #error和#line
- #pragma预处理分析
- #和##运算符使用解析
#pragma
- #pragma是编译器指示字,用于指示编译器完成一些特定的动作
- #pragma所定义的很多指示字是编译器和操作系统特有的
- #pragma在不同的编译器间是不可移植的
- 预处理器将忽略它不认识的#pragma指令
- 两个不同的编译器可能以两种不同的方式解释同一条#pragma指令
#pragma message
- message参数在大多数的编译器中都有相似的实现
- message参数在编译时输出消息到编译输出窗口中
- message可用于代码的版本控制
- message是VC特有的编译 器指示字,GCC中将其忽略。(低版本的gcc忽略,但是高版本的gcc会打印附注信息)
实例分析5-1:#pragma message在不同编译器下不同实现
5-1.c
#include <stdio.h>
#if defined(ANDROID20)
#pragma message("Compile Android SDK 2.0...")
#define VERSION "Android 2.0"
#elif defined(ANDROID23)
#pragma message("Compile Android SDK 2.3...")
#define VERSION "Android 2.3"
#elif defined(ANDROID40)
#pragma message("Compile Android SDK 4.0...")
#define VERSION "Android 4.0"
#else
#error Compile Version is not provided!
#endif
int main()
{
printf("%s\n", VERSION);
return 0;
}
结果:
#pragma pack
不同类型的数据在内存中按照一定的规则排列;而不是顺序的一个接一个的排放,这就是对齐。
为什么需要内存对齐?
- CPU对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16字节
- 当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣
- 某些硬件平台只能从规定的地址处取某些特定类型的数据,否则抛出硬件异常(比如某些硬件只能读取偶数内存地址的值)
#pragma pack能够改变编译器的默认对齐方式
struct占用的内存大小
- 第一个成员起始于0偏移处
- 每个成员按其类型大小和指定对齐参数n中较小的一个进行对齐
– 偏移地址和成员占用大小均需对齐
– 结构体成员的对齐参数为其所有成员使用的对齐参数的最大值 - 结构体总长度必须为所有对齐参数的整数倍
5-2.c
#include<stdio.h>
#pragma pack(2)
struct Test1
{
//假如首地址是0x0000
char c1;//大小1Byte,1和2比较1小,所以起始地址:0,内存地址:0x0000
short s;//大小2Byte,2和2比较等同,所以起始地址:2,内存地址:0x0002 0x0003
char c2;//大小1Byte,1和2比较1小,所以起始地址:4,内存地址:0x0004
int i; //大小4Byte,4和2比较2小,所以起始地址:6,内存地址:0x0006 0x0007 0x0008 0x0009
};
#pragma pack()
#pragma pack(4)
struct Test2
{
//假如首地址是0x0000
char c1;//大小1Byte,1和4比较1小,所以起始地址:0,内存地址:0x0000
char c2;//大小1Byte,1和4比较1小,所以起始地址:1,内存地址:0x0001
short s;//大小2Byte,2和4比较2小,所以起始地址:2,内存地址:0x0002 0x0003
int i; //大小4Byte,4和4比较等同,所以起始地址:4,内存地址:0x0004 0x0005 0x0006 0x0007
};
#pragma pack()
#pragma pack(4)
struct Test3
{
//假如首地址是0x0000
char c1;//大小1Byte,1和4比较1小,所以起始地址:0,内存地址:0x0000
short s;//大小2Byte,2和4比较2小,所以起始地址:2,内存地址:0x0002 0x0003
char c2;//大小1Byte,1和4比较1小,所以起始地址:4,内存地址:0x0004
int i; //大小4Byte,4和4比较等同,所以起始地址:8,内存地址:0x0008 0x0009 0x000A 0x000B
};
#pragma pack()
int main()
{
printf("%lu\n", sizeof(struct Test1));
printf("%lu\n", sizeof(struct Test2));
printf("%lu\n", sizeof(struct Test3));
return 0;
}
结果:
实例分析5-2:Intel和微软面试题
5-3.c
#include<stdio.h>
//结构体中的成员大小与对齐参数进行比较,小的那个数是对齐数,不一定必须是8
#pragma pack(8)
struct S1
{
//假如首地址是0x0000
short a;//大小2Byte,2和8比较2小,所以起始地址:0
long b; //大小8Byte,8和8比较等同,所以起始地址:8
//所以,struct S1内存中占16字节(0x0000~0x000F)
};
struct S2
{
//假如首地址是0x0000
char c; //大小1Byte,1和8比较1小,所以起始地址:0
struct S1 d;//大小16Byte,16和8比较8小,所以起始地址:8
double e; //大小8Byte,8和8比较等同,所以起始地址:(24)0x0018
//所以,struct S2内存中占32字节(0x0000~0x001F)
};
#pragma pack()
int main()
{
struct S2 s2;
printf("%lu\n", sizeof(struct S1));
printf("%lu\n", sizeof(struct S2));
//s2.c的内存地址和s2.d的内存地址相差8字节
//s2.c占1字节,而剩余7字节(8字节对齐)无法装下s2.d(16字节)。所以剩余7字节为空,s2.c地址偏移8个字节开始装s2.d。
printf("%d\n", (int)&(s2.d) - (int)&(s2.c));
return 0;
}
结果:
然而在VC上编译结果却不同
5-3.c
#include<stdio.h>
#pragma pack(8)
struct S1
{
//假如首地址是0x0000
short a;//大小2Byte,2和8比较2小,所以起始地址:0
long b; //大小4Byte,4和8比较4小,所以起始地址:4
//所以,struct S1内存中占8字节(0x0000~0x0007)
};
struct S2
{
//假如首地址是0x0000
char c; //大小1Byte,1和8比较1小,所以起始地址:0
struct S1 d;//大小8Byte,(结构体成员的对齐参数为其所有成员使用的对齐参数的最大值,是4)4和8比较4小,所以起始地址:4
double e; //大小8Byte,8和8比较等同,所以起始地址:(16)0x0010
//所以,struct S2内存中占24字节(0x0000~0x0017)
};
#pragma pack()
int main()
{
struct S2 s2;
printf("%lu\n", sizeof(struct S1));
printf("%lu\n", sizeof(struct S2));
printf("%d\n", (int)&(s2.d) - (int)&(s2.c));
return 0;
}
结果: