预处理,果然是预处理----小话c语言(5)

来源:互联网 发布:小米网络短信设置 编辑:程序博客网 时间:2024/05/22 01:53

[Mac-10.7.1 Lion Intel-based]


Q: 预处理到底干了什么事情?

A: 预处理,顾名思义,预先做的处理。源代码中的头文件包含,宏以及条件编译的东西都会在预处理的时候搞定。换句话说,以#开头的语句即为预处理。但是,如果#被包含在引号里面,那就只是单纯的字符或者字符串了。


Q: 怎么证明预处理的存在?

A: 如下代码,保存为macro.c:

#include <stdio.h>#define NUM 100int main(){    printf("%d\n", NUM);    return 0;}

使用gcc  -E  macro.c  -o  macro.i得到预处理的结果(因为篇幅问题,只截取最后数行代码):
# 500 "/usr/include/stdio.h" 2 3 4# 2 "macro.c" 2int main(){ printf("%d\n", 100); return 0;}

可以看到,源代码中的NUM已经被替换成了宏定义的100.

事实上,预处理中的宏同样可以在命令行中指定,如下代码,保存为macro.c:

#include <stdio.h>int main(){    printf("%d\n", NUM);    return 0;}

可以看到代码中的NUM没有被定义,然后使用编译命令加上对NUM的宏定义:

gcc  -DNUM=100  macro.c  -o  macro

编译结束,没有问题,运行亦ok.


同理,对于头文件包含以及条件编译都可以通过预处理命令得到处理之后的代码形式,这样会更好地理解预处理的含义。调试宏也不是一个简单的事情,如果很难确定某个宏到底有没有作用或者宏对应的字符串到底是什么的时候,使用预处理命令得到结果是很好的方式。


Q: 有时看到一个字符串前面带有一个#符号,它表示什么含义?

形如:

#define PRINT_CH(ch)        printf(#ch" is %c\n", (ch));

A:它表示将对应字符组合转换成相应的字符串格式。

使用如下代码,保存为macro.c:

#include <stdio.h>#define PRINT_CH(ch)    printf(#ch" is %c\n", (ch));int main(){    PRINT_CH('a')    return 0;}

使用gcc  -E  -o  macro.i  macro.c编译命令得到预处理后的文件macro.i.

使用如下命令得到预处理文件的最后10行:

可以看到PRINT_CH('a')宏被处理成: printf("'a'"" is %c\n", ('a'));

#ch的作用就是将它变成"ch"的模样。两个字符串字面量放一起相当于字符串拼接的作用。编译和运行结果:


Q: 除了上面的#符号,还会看到##符号,它又是什么含义?

A: 它是代表参数连接,连接过程很单纯,让你看不到一点改变。举个例子:

#include <stdio.h>#define CATENATE(a, b)  a##bint main(){    int ab = 1;    printf("%d\n", CATENATE(a, b));    return 0;}

保存为preprocess.c,预处理后的结果是(仅截取最后数行代码):

# 2 "preprocess.c" 2int main(){ int ab = 1; printf("%d\n", ab); return 0;}

编译运行:

可以发现, a##b得到的结果就是ab这个符号。


Q: 宏定义也是可以续行的,为什么有的时候用的反斜杠来续行,最后编译依然有错?

A: 这有可能续行符被用在了字符串字面量里面,这个不允许的;或者续行符后面跟着非换行字符导致的。续行的含义是将随后紧随的换行字符干掉,当成没发生,如果不是换行字符就可能导致错误。不过gcc 4.2对这个要求也不是很严格了,如下代码,保存为preprocess.c:

#include <stdio.h>#define ADD(a, b)   \     ((a) + (b))int main(){    int ret = ADD(1, 2);    printf("%d\n", ret);    return 0;}

其中ADD宏行末的续行符后面有一个空格,在有的编译器下会编译错误。

为了确认续行符后面是否有空格,使用cat  -e  preprocess.c命令得到行末信息:

可以看到第二行的续行符后面有个空格。编译它,没有什么问题。


Q: 看到printf函数是可变参数的,如果用宏可以定义吗?

A: 是的。__VA_ARGS__便是可变参数的代表。如下代码:

#include <stdio.h>#define printf_ex(...)  printf(__VA_ARGS__)int main (int argc, const char * argv[]){    printf_ex("hello%d\n", 1);    return 0;}

上面的代码宏定义了printf_ex函数,参数为可变参数,它的实现即为printf函数,__VA_ARGS__即为printf_ex的可变参数。

编译运行:


Q: 既然可以宏定义,那么重复的宏定义的结果是什么呢?

A: 这样的话,一般预处理器会按照后者定义的为准,不过一般的预处理器也会发出警告。

如下代码,保存为redefinition.c:

#include <stdio.h>#define A   10#define A   11#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));int main (int argc, const char * argv[]){    PRINT_D(A)    return 0;}

编译:

可以发现出现了A重复宏定义的警告。 

运行:


Q: 有的时候发现需要打印当前运行的位置,比如文件名,行数以及运行所在的函数,这该怎么办?

A: 可以使用__FILE__, __LINE__和__func__这些预定义的符号来处理。这里以__func__的使用为例子:

#include <stdio.h>int add(int a, int b){    printf("func %s execute begin...", __func__);    return a + b;}int main (int argc, const char * argv[]){    add(1, 2);    return 0;}

编译运行:

可以看到add函数里面的__func__被替换成了add.


xichen

2012-5-13 16:30:10