咱们今天来介绍下 C 语言中的 #pragma,#pragma 用于指示编译器完成一些特定的动做。#pragma 所定义的不少指示字是编译器特有的,在不一样的编译器间是不可移植的。ide
预处理期将忽略它不认识的 #pragma 指令,不一样的编译器可能以不一样的方式解释同一条 #pragma 指令。通常用法:#pragma parameter。 注意:不一样的 parameter 参数语法和意义各不相同!性能
#pragma message:a> message 参数在大多数的编译器中都有类似的实现;b> message 参数在编译时输出消息到编译输出窗口中;c> message 用于条件编译中可提示代码的版本信息。它与 #error 和 #warning 不一样,#pragma message 仅仅表明一条编译消息,不表明程序错误。学习
下来咱们来分析个示例代码,代码以下spa
#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 message 定义一条输出信息,咱们来看看在 gcc 编译器中输出什么3d
咱们能够看出第一次没有定义 ANDROID 参数,编译直接报咱们提示的错误。那么它在输出信息的时候,也一样将 #pragma message 输出了。咱们再在 BCC 编译器中编译下,看看输出是什么blog
那么在 BCC 编译器中咱们将参数写在后面它还不识别,它编译以后的结果是没有 #pragma message,直接显示后面的信息。由此咱们能够看到在不一样的编译器中,对 message 的处理结果不同。图片
下来咱们再讲讲 #pragma once,它是用于保证头文件只被编译一次。那么问题来了,咱们以前讲讲 #ifndef xxx_h_ #define xxx_h_ #endif 这种用法。它们两个有什么区别呢?由于后一种的实现是基于宏参数实现的,因此它每次包含到头文件时便会进去检查,因此效率不高。可是由于是宏参数,因此这是 C 语言支持的,在每一个编译器中都会识别。#pragma once 是编译器相关的,它不必定被支持。可是由于编译器一看见它就不去包含了,因此它的效率及其高,所以两种方式各有利弊。咱们下来作个试验分析下内存
test.c
编译器
#include <stdio.h> #include "global.h" #include "global.h" int main() { printf("g_value = %d\n", g_value); return 0; }
global.hit
#pragma once int g_value = 1;
咱们在 gcc 编译器中编译下,看看结果如何
咱们看到在 gcc 中没报错误,直接运行。咱们下来在 BCC 中再来编译下
咱们看到在 BCC 中直接报错,显然在 BCC 编译器中就不支持这种写法。
那么下来咱们再来看看 C 语言中的内存对齐,什么是内存对齐呢?不一样类型的数据在内存中按照必定规则排列,可是不必定是按照顺序的一个接一个的排列。那么为何须要内存对齐呢?缘由有这么几点:一、CPU 对内存的读取不是连续的,而是分红块读取的,块的大小只能是一、二、四、八、16 ... 字节;二、当读取操做的数据未对齐,则须要两次总线周期来访问内存,所以性能会大打折扣;三、某些硬件平台只能从规定的相对地址处读取特定类型的数据,不然会产生异常。#pragma pack 用于指定内存对齐方式。
下面咱们来看个示例代码,代码以下
#include <stdio.h> 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 <stdio.h> #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 的学习,总结以下:一、#pragma 用于指示编译器完成一些特定的动做;二、它所定义的不少指示字是编译器特有的;三、#pragma message 用于 自定义编译消息、#pragma once 用于保证头文件只被编译一次、#pragma pack 用于指示内存对齐方式。
欢迎你们一块儿来学习 C 语言,能够加我QQ:243343083。