对宏定义的作用域的一点思考

一、前言

    在C语言代码或C++代码中宏定义#ifndef……#define……#endif主要是为了避免头文件重复引用,那它是怎么一个避免机制呢?这就与宏定义的作用域有关了。


二、宏定义作用域

    首先,C语言标准中宏定义的作用域是,从定义位置开始,到其当前所在作用域结束,当前所在作用域只有两个,即块作用域(一对{}大括号的范围)和整个文件结尾;其次,宏定义的变量只属于当前这个文件,其它文件在没有包含拥有宏定义的这个文件的情况下是无法访问这个宏定义变量的,注意,就算是两个对应的头文件和源文件他们还是两个单独不同的文件(如a.h和a.cpp)

    当然,有些编译器会对宏定义的作用域作了扩展,即不管宏定义在哪里开始,其作用域都是整个文件,但是这是编译器的实现原理,并不是C语言的语言标准,关于语言标准与编译器实现原理不同可以查看博文关于对编程语言的一点理解。

    怎么理解宏定义作用域只属于当前文件?第一个例子非常简单,一个头文件a.h,和一个源文件a.cpp,其每个文件如下:

//a.h  头文件
#define b 1;


//a.cpp  源文件,并没有包含a.h头文件
void fun()
{
	int a = 1 + b;     //未定义标识符b
}

    第二个例子以避免头文件重复引用的宏定义#ifndef……#define……#endif为例,首先要理解避免头文件重复引用是什么意思?其实“被重复引用”是指一个头文件在同一个cpp文件中被include了多次,这种错误常常是由于include嵌套造成的。比如:存在a.h文件#include "c.h"而此时b.cpp文件导入了#include "a.h" 和#include "c.h"此时就会造成c.h重复引用,这个时候,在重复引用的头文件中使用#ifndef……#define……#endif是非常有必要的。怎么理解呢?

    首先,源代码在编译之前需要进行预处理(也就是预编译),预编译干什么呢?我所知道的就是进行头文件展开、宏定义替换等操作,对于上面的例子中,b.cpp文件导入了#include "a.h" 和#include "c.h"两个头文件,如果在头文件c.h中使用了#ifndef……#define……#endif宏定义格式(一般是在头文件开头),那在预处理b.cpp文件的时候,首先对a.h头文件进行展开,由于a.h里面又包含了c.h头文件,所以又一次展开,这个时候c.h头文件里面的宏定义就相当于是在b.cpp文件里面定义的,其作用域就是整个b.cpp文件,在接下来的b.cpp部分都可以使用或判断该宏定义。展开了a.h头文件之后,由于b.cpp也显示的包含了c.h头文件,所以需要再一次对其进行展开,在这一次的展开过程中,由于b.cpp文件内已经有了c.h头文件的宏定义,所以在展开的开头部分进行判断为假,所以编译器就避免了再一次对c,h头文件进行展开。c.h、a.h头文件和b.cpp源文件具体如下:

//c.h头文件
#ifndef C_H
#define C_H

//c.h文件需要提供的接口声明
//记住头文件就是声明部分

void fun_c();

#endif

//a.h头文件
#ifndef A_H
#define A_H

#include "c.h"

//a.h文件需要提供的接口声明

void fun_a();

#endif


//b.cpp文件
#include "a.h"
#include "c.h"

//调用a.h和c.h中函数

int mian()
{
	fun_c();
	fun_a();
}
上述代码在VS2010里面编辑并没有错误,而且运行也是没有问题的,只是在运行直线需要先实现fun_c()和fun_a()两个函数。文件b.cpp预处理之后可能变为(伪代码,并不是预处理器真正的输出代码)

//b.cpp预处理后文件
/*********************************
     #include "a.h"替换开始
**********************************/
#define A_H

//#include "c.h"      //这里包含c.h文件,继续替换
#define C_H
void fun_c();

void fun_a();
/*********************************
     #include "a.h"替换完成
**********************************/

/*********************************
     又一次#include "c.h"替换开始
**********************************/
//#include "c.h"
#ifndef C_H     //这一个判断为假,因为前面已经定义了宏A_H
/***********以下两行代码并不替换进来*******************/
//#define C_H
//void fun_c();
#endif
/*********************************
     又一次#include "c.h"替换结束,可以看到,并没有再一次添加fun_c()函数的声明代码
**********************************/

//调用a.h和c.h中函数

int mian()
{
	fun_c();
	fun_a();
}
    如果以上代码,还不足以让你理解宏定义的作用域只在当前文件范围内,那下面对这个例子进行反面论证,上面第二个例子中#ifndef……#define……#endif的使用是为了避免在同一个文件(不管是cpp还是h文件)中多次包含某一头文件,也就是说第一次的头文件包含会展开,后面再有包含的话首先要进行宏判断,判断成功才包含,但是宏判断的前提是这个宏已经在当前文件中了,比如上面例子中的b.cpp文件,通过#include “a.h”,其中a.h又#include“c.h”, 所以在b.cpp包含a.h头文件的时候,就已经把A_H和C_H这两个宏添加到自己本文件的作用域内了,这难道不能说明宏的作用范围只属于它的本身文件吗?如果不是这样的,那b.cpp文件为什么不能直接就对A_H和C_H宏进行判断,而是必须要先#include两个头文件(见第一个例子)。

三、总结

     C语言标准中宏定义的作用域是,从定义位置开始,到其当前所在作用域结束,即宏定义只属于当前这个文件,其他文件如果没有通过#include包含这个文件,那就不能使用这个宏定义。


你可能感兴趣的:(C)