一、前言
在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_Hvoid 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包含這個檔案,那就不能使用這個宏定義。