预处理器指示字 #pragma 简介
- #pragma 用于指示编译器完成一些特定的动作
- #pragma 所定义的很多指示字是编译器特有的
- #pragma 在不同的编译器间是不可移植的
— 预处理器将忽略它不认识的 #pragma 指令
— 不同的编译器可能以不同的方式解释同一条 #pragma 指令
例如(gcc:#pragma message:Compile Android SDK 2.0…,而vc2015: Compile Android SDK 2.0…,所以确实是以不同的方式解释同一条 #pragma 指令
一般用法: #pragma parameter
注:不同的 parameter 参数语法和意义各不相同
1、#pragma message
- message 参数在大多数的编译器中都有相似的实现
- message 参数在编译时输出信息到编译输出窗口中
- message 用于条件编译中可提示代码的的版本信息
#if defined (ANDROID20)
#pragma message("Compile Android SDK 2.0...")
#define VERSION "ANDROID2.0"
#endif
#include <stdio.h>
#define ANDROID20 1
#if defined (ANDROID20)
#pragma message("Compile Android SDK 2.0...")
#define VERSION "ANDROID2.0"
#endif
int main()
{
printf("%s\n", VERSION);
return 0;
}
输出:
#pragma message 和 #error、#warning 的区别:#error、#warning定义的消息一旦被打印,我们的代码可能是出错了,而 #pragma message 定义的消息仅仅是代表一条消息,并没代表代码有何问题
2、#pragma once
一看到这个,我就想起了在visual studio 2015里面创建头文件时,就会出现这个预处理器指示字,以前一直不知道这个是啥意思
- #pragma once用于保证头文件只被编译一次
- #pragma once 是编译器相关的,不一定被支持。
#pragma once 和下面的代码 都是保证头文件只被编译一次
#ifndef _HEADER_FILE_H
#define _HEADER_FILE_H
...//source code
#endif
这两种方式的区别:#ifndef 这样的方式是被C语言所支持的,它其实并不是只包含一次头文件,而是包含多次,然而在包含多次以后,我们通过一个宏来控制它是否被嵌入到源代码里面去,通过宏的方式保证头文件只被嵌入一次,你包含多次,预处理器还是处理多次,所以从效率上来说它是要大打折扣。而我们的 #pragma once 直接告诉预处理器,当前的头文件只编译一次,只要被include一次后,后面的要include不被处理,所以#pragma once 效率更高,但是 #pragma once 很多编译器不支持,所以用#ifndef 的多。
既保持移植性,又保持效率的做法如下:
#ifndef _HEADER_FILE_H
#define _HEADER_FILE_H
#pragma once
...//source code
#endif
3、#pragma pack
- 什么是内存对齐?
— 不同类型的数据在内存中按照一定的规律排列
— 而不一定是顺序的一个接一个的排列
比如下面两个小程序 Test1 和Test2 所占的内存空间是否相同?
#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("%d\n", sizeof(struct Test1)); //4+4+4 = 12
printf("%d\n", sizeof(struct Test2)); //4+4 = 8
return 0;
}
分析:一般都是按四字节对齐,gcc编译器不支持8字节对齐
- 什么是内存对齐?
— 不同类型的数据在内存中按照一定的规则排列
— 而不一定是顺序的一个接一个的排列
- 为什么需要内存对齐?
— CPU对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16…字节
— 当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣 - #pragma pack 用于指定内存对齐方式
- #pragma pack 能够改变编译器的默认对齐方式
#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("%d\n", sizeof(struct Test1));
printf("%d\n", sizeof(struct Test2));
return 0;
}
解析:#pragma pack(1) 括号里面的 1 代表1字节对齐,所以两个值为8,如果#pragma pack(4) 就代表4字节对齐,结果就为sizeof(struct Test1) = 12,sizeof(struct Test2) = 8 和不写#pragma pack(4) 是一个结果。
-
struct占用的内存大小
— 第一个成员始于 0偏移处
— 每个成员按其类型大小和pack参数中较小的一个进行对齐
1、偏移地址必须能被对齐参数整除
2、结构体类型的变量的对齐参数取其内部长度最大的数据成员作为其对齐参数
3、结构体总长度必须为所有对齐参数的整数倍 -
编译器在默认情况下按照4字节对齐
格式:
struct Test1
{ 对齐参数 偏移地址 大小
char c1; 1 0 1
short s; 2 2 2
char c2; 1 4 1
int i; 4 8 4
};
大小:8+4 = 12
struct Test1
{ 对齐参数 偏移地址 大小
char c1; 1 0 1
char c2; 1 1 1
short s; 2 2 2
int i; 4 4 4
};
大小:4+4 = 8
这种偏移地址是我们给他设定的,但不是随意设定的,所以很重要的一点就是要能被对齐参数整除,这样形成的偏移地址就代表了存储的方式。
这张图,再看一遍,对应着偏移地址,就能很容易理解内存对齐的方式。
刷题:
例1:
struct Test1
{
int a;
double b;
char c;
};
8+8+8 = 24
例2:
struct Test2
{
int a;
double b;
char c[6];
};
8+8+8 = 24
例3:
struct Test
{
int a;
double b;
char c;
};
//24
struct Test3
{
int a;
Test d;
double b;
char c;
};
//8+24+8+8 = 48
例4:
typedef struct
{
int a;
double b;
char c;
}Test;
//24
struct Test3
{
int a;
Test d;
char c;
};
8+24+8 = 40
例5:
struct Test
{
char a[20];
int b;
float c;
};
20+4+4 = 28
例6:
struct Test
{
char a[20];
int b;
float c;
double d;
};
20 + 4 + 4 = 28 要为 8 的整数倍
32 + 8 = 40
例7:
union Test
{
char a[20];
int b;
float c;
};
20
例8:
union Test
{
char a[20];
int b;
float c;
double d;
};
24