预处理,它有时很神奇----小话c语言(24)

来源:互联网 发布:做淘宝客怎么拉人 编辑:程序博客网 时间:2024/05/02 04:19

作者:陈曦

日期:2012-7-28  18:19:55

环境:[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2]  

转载请注明出处


Q1: 宏这个东西真是很奇怪,为什么我想将一句#include代码用宏来替换,却不行?

#define INCLUDE_STDIO   #include<stdio.h>INCLUDE_STDIOint main(){    return 0;}

保存为preprocess_header.c


A: 如果是预处理出了问题,我们可以使用-E查看预处理后的结果,来分析到底哪里出了问题。


Q2: 由上面看,好像没有什么问题。

A: 看起来是没有什么问题,问题在于上面的结果是预处理后的结果。编译器编译预处理后的源代码还出现了#include, 编译器是不识别的,所以会报错。换句话说,预处理做预处理的事情,编译器做编译源代码的事情(不过编译器常常被看做包括预处理的功能).预处理器可以处理#include, 而编译器根本无法识别#include.


Q3: 预处理当发现INCLUDE_STDIO符号是#include<stdio.h>时为什么没有继续预处理此头文件的内容?

A: 这就在于c语言标准规定#define定义的符号是进行重新预处理的,但是仅限于#define的符号,遇到#include是不做处理的。 


Q4: 有时,需要测试数据,写了好多相同名称开头的变量,最后进行赋值,有什么好方法?

A: 这当然需要用到##符号了,它可以正确生成你需要的变量。如下代码:

#include <stdio.h>#include <string.h>#include <stdlib.h>#include <stdarg.h>#define PRINT_D(longValue)       printf(#longValue" is %ld\n", ((long)longValue));#define PRINT_STR(str)              printf(#str" is %s\n", (str));#define ADD_TO_NUM(sum_name, sum_number, value) \(sum_name ## sum_number) += (value);int main(int argc, char **argv){    int sum1 = 10, sum2 = 20;    int i = 10;    ADD_TO_NUM(sum, 1, i);    ADD_TO_NUM(sum, 2, i);        PRINT_D(sum1)    PRINT_D(sum2)            return 0;}

编译运行:
sum1 is 20sum2 is 30

同理,如果需要对c语言的结构,比如堆栈、队列等写不同类型数据的操作函数,可以将类型用作参数做出类似的宏定义。


Q5: 对于do, while循环,我喜欢反着用,用until的模式,有什么方法吗?

A: 如下代码:

#define repeat  do#define until(x)    while(!(x))

测试代码:
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <stdarg.h>#define PRINT_D(longValue)       printf(#longValue" is %ld\n", ((long)longValue));#define PRINT_STR(str)              printf(#str" is %s\n", (str));     #define repeat  do#define until(x)    while(!(x))int main(int argc, char **argv){    int i = 1;    repeat    {        PRINT_D(i)        ++i;    }until(i > 10);        return 0;}

运行结果:
i is 1i is 2i is 3i is 4i is 5i is 6i is 7i is 8i is 9i is 10

Q6: 很多时候,写代码的时候,发现一个关键字或者代码组合老是写,用宏做替换是否是个好的选择?

A: 只要风险可控就是好选择。比如,register关键字感觉好长,可以用REG代替;无限循环for(;;)也很长,可以用FOREVER代替;switch语句内部的break和case语句连写也可以用宏CASE代替。

#define REG     register#define FOREVER for(;;)#define CASE    break; case

测试代码:
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <stdarg.h>#define PRINT_D(longValue)       printf(#longValue" is %ld\n", ((long)longValue));#define PRINT_STR(str)              printf(#str" is %s\n", (str));     #define REG     register#define FOREVER for(;;)#define CASE    break; caseint main(int argc, char **argv){    REG int i = 1;    FOREVER        PRINT_STR("hello")    return 0;}


这里还有关于文件读写的例子,发现老是要写fopen,fclose函数,写了好多遍,不如封装在一个通用文件中,用宏是个好选择:

#define FOPEN_COMMON(file_name)    \FILE    *fp = fopen((file_name), "r+");  \if(!fp) \{   \perror("fopen error");  \return -1;  \}#define FCLOSE_COMMON   \fclose(fp);

总之,有什么代码觉得多的地方,用宏做替换是个好选择,不过要清楚这里面的风险。


Q7: 既然宏就是字符串的替换,那么是否也可以将一种基本类型定义成另一种基本类型?

A: c语言标准并没有规定宏字符串不能是关键字,所以上面的情况是可以的。不过,这样很可能打乱以前的逻辑,可以在某些场合做测试。

#include <stdio.h>#include <string.h>#include <stdlib.h>#include <stdarg.h>#define PRINT_D(longValue)       printf(#longValue" is %ld\n", ((long)longValue));#define PRINT_STR(str)              printf(#str" is %s\n", (str));#define double  intint main(int argc, char **argv){    double i = 1.5;    PRINT_D(i)    return 0;}

可以看到,上面将double字符串定义成int字符串,main函数中的定义i的代码也就变成了int  i = 1.5; 最后的输出结果:

i is 1


Q8: 有时,为了方便输出不同类型的变量,写了如下两个宏:

#define PRINT(longValue)       printf(#longValue" is %ld\n", ((long)longValue));#define PRINT(str)              printf(#str" is %s\n", (str));
为什么,再使用 PRINT(1) 时会崩溃?


A: 这在于宏并不像函数一样支持重载,它就是个名称,不会因为仅仅宏参数不一样而导致编译器认为宏不一样。所以,上面两个PRINT宏其实是被编译器看做是同一个宏,还会出现重定义的警告:

warning: "PRINT" redefined
所以,PRINT(1)其实使用的是第二个宏。可以使用预处理命令得到预处理后的结果,首先是源代码,保存为preprocess_macro.c : 

#include <stdio.h>#define PRINT(longValue)        printf(#longValue"is %ld\n", (longValue));#define PRINT(str)              printf(#str" is %s\n", (str));int main(){    PRINT(1)    return 0;}

使用预处理得到如下:

由上图可以看出,确实使用了PRINT(str)这个宏来替换,所以导致崩溃。



Q9: 有时,希望可以根据sizeof(int)的数值来做一些提示,为什么下面的代码会出现编译错误?

#if sizeof(int) == 4#elif sizeof(int) == 8#warning    "sizeof(int) == 8"#endif


A: 这个原因在于c语言标准规定预处理根本不能计算sizeof的值,且#if后面的条件表达式必须是整形常量,所以会提示错误。 



Q10: 如果我希望可以控制int类型是4字节或者8字节大小,该怎么办?

A: 其实,这个不是程序员可以控制的,这是平台确定的。在mac下,gcc4.2.1, i386模式int是4字节,x86_64模式int也是4字节; 但是,long类型在i386是4字节,x86_64模式是8字节,知道上面这些信息后,最好自定义类型在恰当模式使用自己需要大小的类型。下面是对long类型的测试:

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

保存为preprocess_if.c .


gcc -o preprocess_if preprocess_if.c -arch i386编译后执行,结果为4.

gcc -o preprocess_if preprocess_if.c -arch x86_64编译后执行,结果为8.




作者:陈曦

日期:2012-7-28  18:19:55

环境:[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2]  

转载请注明出处



原创粉丝点击