C/C++预处理和宏定义
Sep 1, 2012Introduction
编译器对C/C++文件进行编译时, 第一步就是宏处理, 也即预处理.这步是由预处理器完成.
预处理就是对源文件的宏(macro)进行处理(如宏替换, 条件编译, 包含头文件信息).这个步骤属于文本处理,
还没有真正开始编译.
现在我们一般感受不到这个过程, 因为现在的编译器把预处理, 编译, 汇编, 链接的过程都集成起来了, programmer只需要一键即可生成可执行文件.
C/C++中宏处理命令是以#开头的语句, 不以#开头的则是代码语句.
预定义宏
下面是C标准中已经预定义好的宏:
- __LINE__
- __FILE__
- __DATE__
- __TIME__
- __STDC__
当然可以使用#undef取消或者自己重新定义.
此外编译器也有自己定义的宏, 称为编译器扩展宏.
但是不同的编译器使用的宏是不一样的.典型的是MS的VC++和GCC.
如VC++中的#pragma comment(lib, “xxx.lib”)链接静态链接库的宏, 改用gcc编译就不成功了.
gcc编译器可以通过下面命令查看编译器定义的宏:
|
|
cpp是GNU的预处理器(preprocessor).
而VC++编译器定义的宏, 到MSDN参看.
自定义宏
除了在代码中通过#define自定义宏外, 还可以在编译源文件的时候通过配置编译器实现.
编译器允许在编译源码时,用户自定义宏.
gcc编译器
gcc/g++在编译源代码的时候, 添加命令选项-D添加自定宏.
假设我要定义一个DEBUG的宏, 则可以使用下面命令编译:
|
|
-D参数后面跟的是自定义宏的名字, 需要定义多个宏的话, 使用多个-D.
MS VC++编译器
MS的VS中, 可以通过如下设置自定义宏:
右击项目-> properties -> C/C++ -> preprocessor -> preProcessor Definitions
在相应窗口中添加自己自定义的宏.
应用
在写ACM代码时,可以通过自定义宏实现输入/输出的重定向而不用每次手动把重定向语句给注释掉再提交.
在重定向输入/输出语句如下代码:
|
|
一般来说OJ都会编译都会定义ONLINE_JUDGE宏(不同OJ有可能不同).在我们本机编译代码时, 我们一般的编译是没有指定这个ONLINE_JUDGE宏的,
因此, 在本机编译的时候, 就会包含上面的两个重定向代码.而在OJ编译时, 由于ONLINE_JUDGE定义了, 因此没有包含重定向语句, 而是从标准输入/输出.
gcc生成预处理后文件
gcc/g++可以通过命令参数输出预处理后的文件.
对于C预处理后的文件后缀是.i, c++处理后文件后缀是.ii
命令如下:
|
|
生成的.i/.ii文件会原来的cpp文件大很多, 因为把头文件的信息都完整包含进来了.
并且源代码中的宏定义都被处理了, 在预处理后文件中都不见了.
避免头文件多次被引用
C/C++中的宏最常用的就是通过#ifdef, #endif来避免头文件多次被引用(redefinition).
C++ 03标准中有一条ODR(One Definition Rule)准则, 里面提到:
In any translation unit, a template, type, function, or object can have no more than one definition. Some of these can have any number of declarations. A definition provides an instance.
就是说C++中的模版,类型, 函数, 对象可以有多个声明, 但最多有1个定义.
如果违反ODR原则, 编译器会提示redefinition错误.
如果防止头文件中的定义被多次包含呢? 答案就是宏定义.
对每个头文件都定义一个独特唯一标识的宏,通过判断该宏是否被定义, 而决定是否包含该头文件.
标准模版如下:
|
|
一般来说, 对于头文件定义的宏, 命名规则通常为NAME_H, 其中NAME为头文件名字的大写.
下面是一个具体的例子:
|
|
上面main.cpp文件中多次包含obj.h, 但是obj.h使用#ifndef, 因此不会出现redefinition问题.
我们分别对上面两种情况看看g++预处理后的.i文件内容:
1. obj.h没有使用#ifndef, 预处理后的main.i文件内容如下:
|
|
2. obj.h使用#ifndef, 预处理后的main.i文件内容如下:
|
|
明显可以看出, 使用#ifndef后, obj的定义只包含1次.
除了使用#ifdef做法外, 还可以使用#pragma once宏, 很多编译器都支持这个宏.
但是#pragma once并不是C++标准的.
符号##,#, \
C的宏中有两个符号#和##是用于处理字符串的.
#是可以把传给宏的token转换成字符串类型
##则是把两个taokens拼接成1个, 然后替换.
用例子更好地说明, 先看下面代码
|
|
上面代码的输出的结果是:
abc //输出字符串"abc"
10 //输出a1
第一个输出的是”abc”, #a把a转换字符串类型;
而第二个输出是a1的值, a##b就是拼接ab, combine(a, 1)在编译器预处理后就变成a1了.
我们看看gcc预处理后的.i文件中的main函数:
|
|
可以看到, 和预想情况一样.
行连接符\
当宏定义一行代码无法写完时, 可以使用反斜杠\续行.即在每行宏后面添加\.
预处理器会把以\结束的宏指令的末尾的\和换行符删除掉,把若干宏指令行合并成一行.