有个粉丝关于条件编译的问题,程序一直编译报错。我整理了关键的部分,下面代码是头文件中的定义。
#ifdef EXTERN
#undef EXTERN
#define EXTERN
#else
#define EXTERN extern
#endif
EXTERN int a;
报错的内容就是error: unknown type name 'EXTERN
,报错的地方是EXTERN int a;
乍一看条件编译好像并没有什么不妥,但编译器确实已经报错了,那就只能说确实没有定义?
。
首先,用户的意图是想用宏EXTERN
代替关键字extern
。
针对此模块,要保证EXTERN
就是extern
,所以需要判断外部是否已经定义了EXTERN
。如果已经定义了,则需要取消宏定义后再重新定义。
那这里又有同学问了,如果外部已经定义了EXTERN
,为什么还要取消宏定义再重新定义呢,直接用不可以吗?
严格来说是不行的,假设外部有个地方定义了
#define EXTERN const
那么此时EXTERN
就不再是extern
了,意义完全变了。这时候又有同学说了,怎么可能会有人把EXTERN
定义成const
呢,我只能说,我也希望不会出现这种情况吧…
如果外部没有定义EXTERN
会进入else分支,则在本模块直接定义。
看起来都是这么合情合理。
上面已经分析了一次,逻辑上好像没问题(指出问题了我还这么继续写下文)。既然分析没有问题,那我们直接去看预处理文件吧,简单写个Demo,头文件不变。
gcc生成预编译命令为gcc -E .\main.c -o main.i
参数E代表预编译处理,o代表输出,后面跟一个文件名。代表将源文件预编译处理将处理的结果输出到main.i文件中。
假设我们在源文件包含头文件之前已经定义了EXTERN,如下:
#include
#define EXTERN extern
#include"main.h"
然后对源文件预编译处理并截取关键信息:
# 10 ".\\main.h"
int a;
# 4 ".\\main.c" 2
源文件部分代码,此时没有定义EXTERN。
#include
#include"main.h"
同样对源文件预编译处理并截取关键信息:
# 10 ".\\main.h"
extern int a;
# 4 ".\\main.c" 2
当外部定义了EXTERN时,头文件预编译的结果没有对变量a做extern声明。
外部定义了EXTERN时,头文件预编译的结果对变量a做了extern声明。
宏的本质是代码替换,上述对比表明,当外部定义了EXTERN
时,在处理
EXTERN int a;
前EXTERN
变成了一个空宏。
那我们再回头看看头文件中的条件编译,如果外部定义了EXTERN
则取消宏定义,并重新定义EXTERN
,但是在重新定义宏的时候,这里仅仅是定义了EXTERN
。并没有指明EXTERN
是extern
。所以编译总是提示unknown type name 'EXTERN
问题已经找出来了,但是粉丝现在又有了新的疑问,同一个宏在多个文件是如何处理的?
其实上面的两种测试代码已经能够表明了,因为预编译也是按照先后顺序依次处理的,所以宏的处理过程也是按照先后顺序。这也是我为什么在包含main头文件之前定义EXTERN
,而不是在包含头文件之后定义EXTERN
。
发现有些同学对于一些宏的判断有些分不清楚,不清楚应该在何种场合使用。在此仍然以EXTERN为例吧。
#define EXTERN
依稀记得当时刚毕业出来工作,当我第一次看到代码有这种定义的时候,也是挺懵逼的。甚至怀疑是不是写错了?因为我当时认为后面少了数字。为什么是少数字呢,因为在我的印象中宏都是下面这种使用方法。
#define VALUE (100)
第一种方式宏定义仅仅代表定义了这个标识符,而不用关心它具体是什么。
常用的就是在调试模式下定义DEBUG,发布程序的时候将DEBUG关闭。主要是用来打印一些信息,方便程序员调试用的,如下:
#ifdef DEBUG
//打印信息
#endif
#if EXTERN
注意这种使用方法,是一个判断语句,跟我们编程用if判断逻辑相同,也就是EXTERN不为0才能成立。
这种用法通常都是在定义宏时直接定义成1,例如:
#define EXTERN (1)
#ifdef EXTERN
这种方式和第二种有区别,这种判断只关注有没有定义,但凡你定义了,不管是什么牛马蛇神,条件都是成立的。
关于宏相关的知识点,之前写过一篇文章。有兴趣的可以看看。
这篇文章主要也是宏相关的,主要是实际编程中的问题。条件编译报错,可以去查看预处理文件,反向分析。如果你对C理解得比较深,可能一眼也就能看出问题了。
每个人都是从小白过来的,需要时间慢慢沉淀,慢慢也就好了,加油~