一、宏定义
1. 宏定义的作用域
宏定义开始,到文件结束(其他的文件包含宏定义的文件也可引用)。
2. 宏定义可增加代码的可读性
#define ERROR_POWEROFF -1
若不采用宏定义的方式,代码中出现-1 时,程序的可读性变差,代码中出现有具体的含义的单独的数字(比如上面-1) 称为魔鬼数,别人阅读代码的时候会抓狂,恐怕自己阅读的时候,也不知具体的含义
3. 宏预编译阶段宏定义替换代码中具体的定义
这点代码中定义比较容易出错,比如
(1)#define SQR (x) x * x
当表达式 x = 10+1, SQR(x) * SQR(x) 替换为 10+1*10+1,显然这不是我们想要的结果,导致出错
(2)#define ADD (x) (x)+(x)
当表达式 x=5, ADD(x)*ADD(x) 替换为 (5)+(5)*(5)+5,显然这不是我们想要的结果,导致出错
解析:避免这种错误,当宏定义的时候,每部分都加上括号:(1))#define SQR (x) ((x) *( x)) (2)#define ADD (x) ((x)+(x))
4. 当宏出现在字符串中的时候,宏不会被替换
比如 printf("ADD(x)"); 打印的结果为 ADD(x) 而不是 (x)+(x)
5. 宏定义函数的时候,函数标识符和参数之间不能有空格
比如 #define SQR (x) x * x , 宏将变成代码中用(x) x * x 替换代码中的SQR ;
但引用宏的时候可以有空格,比如 #define ADD (x) ((x)+(x)), 应用的时候 ADD (3) 和 ADD(3) 都是正确的
6. 取消宏定义的符号 #undef,此符号之后的宏的定义将不再起作用
二、条件编译
条件编译的形式之一:
(1) #ifdef 标识符
程序段1
#else
程序段2
#endif
(2) #if 常量表达式
程序段1
#else
程序段2
#endif
条件编译应用于场景之一,比如当在windows平台下编写代码调试,而程序又是可以运行在linux或者aix平台下,这样可以进行条件编译;
条件编译和 if--else if---else if .....else 语句一样可以有不同的引申
三、文件包含#include
1. 文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。C语言提供#include 命令来实现文件包含的操作,它实际是宏定义的延伸;
(1)#include
C 编译系统所提供的并存放在指定的子目录下的头文件。找到文件后,用文件内容替换该语句;
(2)#include “filename”
预处理应在当前目录中查找文件名为filename 的文件,若没有找到,则按系统指定的路径信息,搜索其他目录。找到文件后,用文件内容替换该语句。
#include 是将已存在文件的内容嵌入到当前文件中
2. #include 支持相对路径
比如,#include "./filename" (当前目录下的文件filename) #include "./icp/filename"(上层路径文件icp目录下的文件filename)
四、#pragma comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中。常用的lib 关键字,可以帮我们连入一个库文件。比如:
#pragma comment(lib, "user32.lib")
该指令用来将user32.lib 库文件加入到本工程中。
linker:将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或 者在开发环境中设置的链接选项,你可以指定/include 选项来强制包含某个对象,
例如:#pragma comment(linker, "/include:__mySymbol")
五、#pragma warning
#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于:
#pragma warning(disable:4507 34) // 不显示4507 和34 号警告信息
#pragma warning(once:4385) // 4385 号警告信息仅报告一次
#pragma warning(error:164) // 把164 号警告信息作为一个错误。
不过程序设计的时候,尽量少用disable,尽量在编码的时候,将warning问题解决掉,有的时候warning 也是潜在的bug
六、#pragma once
在头文件的最开始加入这条指令就能够保证头文件被编译一次
另外保证头文件只编译一次的方法:
#ifndef _FILENAME_H
#define _FILENAME_H
#endif
七、#pragma code_seg
另一个使用得比较多的pragma 参数是code_seg。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它
八、#pragma message
能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#pragma message(“消息文本")
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 这对于我们进行源码控制,代码调试有帮助。
九、内存对齐 #pragma pack
1. 内存对齐的原理:
字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4 整除的地址,和可以被8 整除的地址)无论如
何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存
访问仅需要一次访问。减少内存地址总线访问未对齐的地址的周期。
2. 内存对齐例子
(1)struct TestStruct1
{
char c1;
short s;
char c2;
int i;
};
解析:此结构体在内存中的布局为 1*,11,1*******,1111 (1 代表占用内存,* 代表为内存对齐补的内存空间)
所以 sizeof(TestStruct1) 为12
3. 内存对齐的避免
可以在程序设计的时候,尽量避免内存对齐的情况,尽量自然对齐节省占用内存空间,比如上例
struct TestStruct2
{
char c1;
char c2;
short s;
int i;
};
sizeof(TestStruct2) 为 8
4. #pragma pack
#pragma pack (n),编译器将按照n 个字节对齐
#pragma pack (),编译器将取消自定义字节对齐方式
例如
#pragma pack(8)
struct TestStruct4
{
char a;
long b;
};
struct TestStruct5
{
char c;
TestStruct4 d;
long long e;
};
#pragma pack()
解析:
TestStruct4 内存布局: 1***,1111
TestStruct5 内存布局: 1***,1***,1111****,11111111
所以 sizeof(TestStruct4) 为 8,sizeof(TestStruct5)为 24
5. 内存对齐的规则
(1)每个成员分别按自己的方式对齐,并能最小化长度
(2)复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
(3)对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐
(4)对于数组,比如:char a[3];它的对齐方式和分别写3 个char 是一样的.也就是说它还是按1 个字节对齐,即数组按照数组中的每个成员的类型对齐
如果写: typedef char Array3[3];Array3 这种类型的对齐方式还是按1 个字节对齐,而不是按它的长度
(5)不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个
十、#预算符
1. # 符号的用处之一: 将宏中字符串中的变量,以变量值的形式打印
(1)比如:
#define SQR(x) printf("The square of x is %d.\n", ((x)*(x)));
如果这样使用宏:SQR(8);
则输出为:
The square of x is 64.
(2) 修改上面的宏为:
#define SQR(x) printf("The square of "#x" is %d.\n", ((x)*(x)));
再使用:
SQR(8);
则输出的是:
The square of 8 is 64.
十一、##预算符
##预算符作用:粘合剂
#define DISPLAY(n) x ## n
如果这样使用宏:
DISPLAY(8)
则会被展开成这样:
x8