我们今天来介绍下 C 语言中的 #pragma,#pragma 用于指示编译器完成一些特定的动作。#pragma 所定义的很多指示字是编译器特有的,在不同的编译器间是不可移植的。
预处理期将忽略它不认识的
#pragma 指令,不同的编译器可能以不同的方式解释同一条 #pragma 指令。一般用法:#pragma parameter。注意:不同的 parameter 参数语法和意义各不相同!
#pragma message:a> message 参数在大多数的编译器中都有相似的实现;b> message 参数在编译时输出消息到编译输出窗口中;c> message 用于条件编译中可提示代码的版本信息。它与#error 和#warning 不同,#pragma message仅仅代表一条编译消息,不代表程序错误。
下来我们来分析个示例代码,代码如下#include
#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 message 定义一条输出信息,我们来看看在 gcc 编译器中输出什么
我们可以看出第一次没有定义 ANDROID 参数,编译直接报我们提示的错误。那么它在输出信息的时候,也同样将 #pragma message 输出了。我们再在 BCC 编译器中编译下,看看输出是什么
那么在 BCC 编译器中我们将参数写在后面它还不识别,它编译之后的结果是没有 #pragma message,直接显示后面的信息。由此我们可以看到在不同的编译器中,对 message 的处理结果不一样。
下来我们再讲讲 #pragma once,它是用于保证头文件只被编译一次。那么问题来了,我们之前讲讲 #ifndef xxx_h_ #define xxx_h_ #endif 这种用法。它们两个有什么区别呢?因为后一种的实现是基于宏参数实现的,所以它每次包含到头文件时便会进去检查,所以效率不高。但是因为是宏参数,所以这是 C 语言支持的,在每个编译器中都会识别。#pragma once 是编译器相关的,它不一定被支持。但是因为编译器一看见它就不去包含了,所以它的效率及其高,因此两种方式各有利弊。我们下来做个试验分析下
test.c#include
#include "global.h"
#include "global.h"
int main()
{
printf("g_value = %d\n", g_value);
return 0;
}
global.h#pragma once
int g_value = 1;
我们在 gcc 编译器中编译下,看看结果如何
我们看到在 gcc 中没报错误,直接运行。我们下来在 BCC 中再来编译下
我们看到在 BCC 中直接报错,显然在 BCC 编译器中就不支持这种写法。
那么下来我们再来看看 C 语言中的内存对齐,什么是内存对齐呢?不同类型的数据在内存中按照一定规则排列,但是不一定是按照顺序的一个接一个的排列。那么为什么需要内存对齐呢?原因有这么几点:1、CPU 对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16 ... 字节;2、当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣;3、某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则会产生异常。#pragma pack 用于指定内存对齐方式。
下面我们来看个示例代码,代码如下#include
struct Test1
{
char c1;
short s;
char c2;
int i;
};
struct Test2
{
char c1;
char c2;
short s;
int i;
};
int main()
{
printf("sizeof(Test1) = %d\n", sizeof(struct Test1));
printf("sizeof(Test2) = %d\n", sizeof(struct Test2));
return 0;
}
我们看到两个结构体中的成员变量都一样,但是位置不同。那么问题来了,它们所占的内存大小相同吗?我们来看看编译结果
很明显结果不一样,那么为什么呢?这就是内存对齐了,在计算机内部,默认的是4字节对齐,因为这样效率最高。我们可以指定它们都是1字节对齐,这样结果就一样了。#pragma pack 能够改变编译器的默认对齐方式,下面给个示例#pragma pack(1)
struct Test1
{
char c1;
short s;
char c2;
int i;
};
#pragma pack()
分别在两个结构的前后都这样设置按照 1 字节对齐的方式,我们再来看看结果如何
这下它们占的内存大小就一样了。之前的那种内存分布如下图所示
那么 struct 占用的内存大小是怎样计算的呢?第一个成员起始于 0 偏移处,每个成员按其类型大小和 pack 参数中较小的一个进行对齐;偏移地址必须能被对齐参数整除,结构体成员的大小取其内部长度最大的数据成员作为其大小;结构体总长度必须为所有对齐参数的整数倍。编译器在默认情况下按照 4 字节对齐。
下来我们来分析下之前的程序中结构体默认的按照 4 字节怎样对齐的#pragma pack(4)
struct Test1
{ // 内存对齐 起始距离 内存大小
char c1; // 1 0 1
short s; // 2 2 2
char c2; // 1 4 1
int i; // 4 8 4
};
#pragma pack()
#pragma pack(4)
struct Test2
{ // 内存对齐 起始距离 内存大小
char c1; // 1 0 1
char c2; // 1 1 1
short s; // 2 2 2
int i; // 4 4 4
};
#pragma pack()
那么大家和上面的表对照下,是不是一样呢。下来我们再做个实验,按照8字节对齐试试,代码如下#include
#pragma pack(8)
struct S1
{ // 内存对齐 起始距离 内存大小
short a; // 2 0 2
long b; // 4 4 4
};
struct S2
{ // 内存对齐 起始距离 内存大小
char c; // 1 0 1
struct S1 d;// 4 4 8
double e; // 8 16 8
};
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
按照我们的分析,上面应该分别打印出 8 和 24。我们来看看 gcc 编译器的结果
我们看到打印出的是 8 和 20,那么我们是不是分析出错了?别着急哈,再看看 BCC 编译器的结果
我们看到 BCC 编译器和我们分析的一致。那么 gcc 为什么会打印 20 呢?原来在 gcc 编译器中不支持 8 字节对齐的方式,因此它是按照 4 字节对齐方式进行打印的。那么最后一个 double 的起始距离就是 12 了,因此结果就是 20 啦。
通过对 #pragma 的学习,总结如下:1、#pragma 用于指示编译器完成一些特定的动作;2、它所定义的很多指示字是编译器特有的;3、#pragma message 用于 自定义编译消息、#pragma once 用于保证头文件只被编译一次、#pragma pack 用于指示内存对齐方式。
欢迎大家一起来学习C 语言,可以加我QQ:243343083。