--事物的难度远远低于对事物的恐惧!
直接进主题吧,今天来分析下#pragma指示字,该指示字是C语言留给编译器处理的。
-#pragma用于指示编译器完成一些特定的动作
-#pragma所定义的很多指示字是编译器特有的
-#pragma在不同的编译器间是不可移植的
-预处理器将忽略它不认识的#pragma指令
-不同的编译器可能以不同的方式解释同一条#pragma指令
一般用法为:#pragma parameter (其中 不同的parameter参数语法和意义各不相同)
#pragma message
-message参数在大多数的编译器中都有相似的实现
-message参数在编译时输出消息到编译输出窗口中
-message用于条件编译中可提示代码的版本信息
注意:与#error和#warning不同,#pragma message仅仅代表一条编译消息,不代表程序错误。
来看看下边的代码:
#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; }
VC编译器下编译结果如下,我在命令行中定义了宏ANDROID23,编译过程编译器将符合条件编译的#pragma message信息给打印出来,这样我们就能看到我们所编译的版本信息,gcc编译器下输出格式可能会有所不同,但是功能是类似的。
#pragma once
-#pragma once 用于保证头文件只被编译一次
-#pragma once是编译器相关的,不一定支持
想想如下图片中的两种头文件处理方式,有什么不同?
#ifndef...方式属于C语言中条件编译,任何C语言编译环境,都支持该种方式,但是同一个头文件重复包含,最后是通过预处理器去处理重复包含的部分,编译效率比较低下;而#pragma once就直接保证了该头文件只编译一次,效率相对来说会高一些,又由于#pragma once是与编译器相关的,所以工程中的做法一般如下:
#ifndef __FILENAME_H__ #define __FILENAME_H__ #pragma once //头文件加上这句 //即可保证支持#pragma once的编译器进行高效编译 //而对于不支持#pragma once的编译器,则使用#findef...的方式进行编译 //your code ... #endif
#pragma pack
在说#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; }
编译执行结果如下:
从输出结果来看,虽然两个结构体的元素一样,但是所占的内存空间大小不一样,这就涉及内存对齐的情况。
下边是两个结构体在内存中的排布情况
为了分析上图,我们首先来看下为什么需要内存对齐?
-CPU对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16...字节
-当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣
-某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则产生硬件异常
-#pragma pack用于指定内存对齐方式,使用方式为:#pragma pack(number) number为对齐字节数
下边我们将上一段代码修改成下边式样,使用#pragma pack(1)使得两个结构体都按1个字节对齐
#include <stdio.h> #pragma pack(1) struct Test1 { char c1; short s; char c2; int i; }; #pragma pack() #pragma pack(1) struct Test2 { char c1; char c2; short s; int i; }; #pragma pack() int main() { printf("sizeof(Test1) = %d\n", sizeof(struct Test1)); printf("sizeof(Test2) = %d\n", sizeof(struct Test2)); return 0; }
编译执行:
现在可以看到,两个结构体在内存中所占用的内存大小一致了,现在来重点分析下struct中的内存分配问题
-struct第一个成员起始于 0偏移处
-每个成员按其类型大小和pack参数中较小的一个进行对齐
-偏移地址必须能被对齐参数整除
-结构体成员的大小取其内部长度最长的数据成员作为其大小
-结构体总长度必须为所有对齐参数的整数倍
编译器在默认情况下按照4字节对齐
有了以上struct结构体内存分布原则,我们在来分析下上边最开始的两个结构体(默认4字节对齐),为什么一个占12字节,一个占8字节。
struct Test1 { // 对齐参数 偏移地址 占用大小 char c1; // 1 0 1 short s; // 2 2 2 char c2; // 1 4 1 int i; // 4 8 4 }; //Test1最后一个成员偏移地址为8,又往后占用了4字节,所以结构体大小为12字节 struct Test2 { // 对齐参数 偏移地址 占用大小 char c1; // 1 0 1 char c2; // 1 1 1 short s; // 2 2 2 int i; // 4 4 4 }; //Test1最后一个成员偏移地址为4,又往后占用了4字节,所以结构体大小为8字节
相信进过以上的分析,对于内存对齐,你已经有了比较清晰的认识,下边来看看一个微软的面试题:
#include <stdio.h> #pragma pack(8) struct S1 { short a; long b; }; struct S2 { char c; struct S1 d; //注:结构体以最大成员的字节数参与选取字节对齐的参数,所以这个的对齐参数为4 double e; }; #pragma pack() int main() { printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
这道题有两个地方需要注意:
1、结构体作为成员时,其参与内存对齐参数的数值为结构体内最大成员的字节数
2、gcc编译器暂时不支持8字节对齐,所以在gcc环境下,输出分别为8和20,而在vc环境下输出为8和24(大家可自行验证)
总结:
-#pragma 用于指示编译器完成一些特定的动作
-#pragma所定义的很多指示字是编译器特有的
-#pragma message 用于自定义编译消息
-#pragma once用于保证头文件只被编译一次
-#pragma pack用于指定内存对齐方式