C語言中文網 目錄

條件編譯指令(符號),C語言條件編譯指令完全攻略

 在 C 語言中,條件編譯指令可以實現源代碼的部分編譯功能,可以根據表達式的值或者某個特定的宏來確定編譯條件,以決定編譯哪些代碼,不編譯哪些。

使用“#ifndef/#define/#endif”防止頭文件被重復引用

在 C 語言中,一個文件中可以包含多個頭文件,而頭文件之間又是可以相互引用的,這將引起一個文件中可能間接多次包含某個頭文件,從而導致了某些頭文件被重復引用多次。

例如,有 3 個文件 a.h、b.h 和 c.h,其中 b 文件中包含了 a.h,而 c 文件中又分別包含了 a.h 和 b.h 兩個文件。于是問題出來了,由于嵌套包含文件的原因,頭文件 a.h 被兩次包含在源文件 c 中。在這里,如果頭文件中沒有防止多次編譯的語句,有可能會引起如下兩種后果:
  • 某些頭文件重復引用只是增加了編譯器編譯的工作量,導致編譯效率降低,不會引起太大的問題。但是,這里還需要說明的是,對比較大的工程而言,編譯效率低下也將是一件非常痛苦的事情。
  • 某些頭文件重復引用,有可能會引起意想不到的嚴重錯誤。比如,在頭文件中定義了全局變量(雖然這種方式不被推薦,但有時候確實需要這么做),將會導致全局變量被重復定義。

在 C 語言中,避免同一個頭文件被多次包含、重復引用,最常用也是最簡單的方法就是利用“#ifndef/#define/#endif”結構產生預處理塊來防止頭文件被重復引用。如下面的示例代碼所示:
#ifndef __HEADERNAME_H__
#define __HEADERNAME_H__
/*聲明、定義語句*/
#endif
在上面的預處理塊中,當第一次引用(include)頭文件時,由于“__HEADERNAME_H__”還沒有被宏定義(define)過,即滿足“#ifndef__HEADERNAME_H__”,從而執行“#define__HEADERNAME_H__”以及其他內容。

如果因為編碼者的不小心或者嵌套包含等原因造成了這個頭文件被多次引用(include),那么“#ifndef__HEADERNAME_H__”判斷條件將在第二次引用(include)頭文件時得不到滿足,因此不執行后面的內容,直接跳到“#endif”。

通過“#ifndef/#define/#endif”結構產生預處理塊,雖然能夠避免同一個頭文件被多次包含和重復引用,但也存在一個致命的缺點,那就是一旦一不小心在不同頭文件中定義了相同的宏名,問題就比較麻煩了。比如,可能會導致明明看到存在頭文件,而編譯器卻硬說找不到聲明等問題。為了避免這種情況,保證宏名的唯一性,建議按照 Google 公司的建議,頭文件基于其所在項目源代碼樹的全路徑進行命名。命名格式為:

<PROJECT>_<PATH>_<FILE>_H_

其中,PROJECT 表示項目名稱,PATH 表示頭文件相對路徑,FILE 表示文件名,再以“_H_”作為后綴。比如,在項目 CashRegister 中,現在該項目所在目錄下的一個名為 xml 的子文件夾下的一個 parser 頭文件,則宏定義如下:
#ifndef CASHREGISTER_XML_PARSER_H_
#define CASHREGISTER_XML_PARSER_H_
/*聲明、定義語句*/
#endif
當然,基于命名習慣原因,也可以這樣來寫:
#ifndef _CASHREGISTER_XML_PARSER_H_
#define _CASHREGISTER_XML_PARSER_H_
/*聲明、定義語句*/
#endif
這里需要注意的是,由于編譯器在每次編譯時都需要打開頭文件才能判定是否有重復定義,因此在編譯大型項目時,“#ifndef”會使編譯時間相對較長。

除此之外,你還可以使用“#pragma once”方式來防止頭文件被重復引用,該方式一般由編譯器提供,可以保證同一個文件不會被包含多次。但這里需要特別說明的是,該方式受編譯器的限制,有些編譯器并不支持該指令,因此在兼容性方面表現得不是很好。這里建議為了代碼的兼容性,寧肯降低一些編譯性能,還是使用“#ifndef/#define/#endif”結構。

使用條件編譯指令實現源代碼的部分編譯

前面已經說過,條件編譯指令可以使編譯器按不同的條件編譯不同的程序部分,因而產生不同的目標代碼文件。這對于程序的移植和調試是很有用的,尤其是針對于跨平臺程序移植的時候。在 C 語言中,主要有如下條件編譯指令。

1、#if指令

該指令檢測表達式值是否為真。如果表達式的值為真,則編譯后面的代碼直到出現 #else、#elif 或 #endif 為止,否則不編譯。

2、#endif指令

該指令用于終止 #if 指令。

3、#else指令

該指令用于 #if 指令之后,當前面的 #if 指令的條件不為真時,就編譯 #else 后面的代碼。

4、#elif指令

該指令綜合了 #else 和 #if 指令的作用。下面的示例代碼演示了 #if、#else、#elif 與 #endif 的組合使用情況。
#if OS==1
    printf("V1.0");
#elif OS==2
    printf("V2.0");
#else
    printf("未知");
#endif

5、#ifdef 和 #ifndef 指令

相對于 #if 指令(檢測表達式的值是否為真),#ifdef 和 #ifndef 指令用于檢測指令關鍵字后面的宏名稱是否已經定義。其中,#ifdef 指令表示如果宏已經被定義,那么它的檢測結果為真,否則返回假;而 #ifndef 指令的含義正好與 #ifdef 指令相反,它表示如果宏未被定義,那么它的檢測結果為真,否則為假。

在一般情況下,條件編譯指令組合主要有如下幾種形式。
1) 第一種形式如下:

#ifdef 宏名稱
    /*程序段1*/
#else
    /*程序段2*/
#endif

它表示如果宏名稱已經定義,則對程序段 1 進行編譯;否則對程序段 2 進行編譯。如果沒有程序段 2,則可以省略“#else”,如下所示:

#ifdef 宏名稱
    /*程序段1*/
#endif


2) 第二種形式如下:

#ifndef 宏名稱
    /*程序段1*/
#else
    /*程序段2*/
#endif

#ifndef 指令的含義正好與 #ifdef 指令相反,因此它表示如果宏名稱未被定義,則對程序段 1 進行編譯,否則對程序段 2 進行編譯。

3) 第三種形式如下:

#if 表達式
    /*程序段1*/
#else
    /*程序段2*/
#endif

它表示如果表達式的值為真(非 0),則對程序段 1 進行編譯,否則對程序段 2 進行編譯。如果有多個(兩個以上)條件,則可以用 #elif 指令,如下所示:

#if 表達式
    /*程序段1*/
#elif 表達式
    /*程序段2*/
#elif 表達式
    /*程序段3*/
#else
    /*程序段4*/
#endif

妙用“defined”

在 C 語言中,除了“#ifdef”和“#ifndef”指令之外,還可以使用 defined 判斷標識符是否定義過。實際上,“#if defined”等價于“#ifdef”,而“#if!defined”等價于“#ifndef”。例如,下面的示例代碼就演示了如何使用 defined 來避免重復包含頭文件引起的重復定義問題:
#if !defined(_CASHREGISTER_XML_PARSER_H_)
#define _CASHREGISTER_XML_PARSER_H_
/*聲明、定義語句*/
#endif
它實際上等價于下面的代碼:
#ifndef _CASHREGISTER_XML_PARSER_H_
#define _CASHREGISTER_XML_PARSER_H_
/*聲明、定義語句*/
#endif
當然,從上面的示例來看,或許看不出來使用 defined 有任何的優勢。但是,在處理雙重和多重判斷時,defined 的優勢就顯現出來了,如下面的代碼所示:
#ifndef _CASHREGISTER_XML_PARSER_H_
#ifndef _CASHREGISTER_XML_TRANSFORM_H_
#ifndef _CASHREGISTER_XML_DECODE_H_
/************/
#endif
#endif
#endif
從上面的代碼中可以看出,使用“#ifdef”和“#ifndef”指令一次只能同時檢測一個宏是否定義。如果需要檢測多個宏,則需將“#ifdef”和“#ifndef”指令重復復制多次,這樣看起來很不友好。但是,如果這里使用 defined,那就簡單多了,如下面的代碼所示:
#if !defined(_CASHREGISTER_XML_PARSER_H_)&&!defined(_CASHREGISTER_XML_TRANSFORM_H_)&&!defined(_CASHREGISTER_XML_DECODE_H_)
/************/
#endif
其實,defined 的使用是非常普及的,在一些常見的 C 語言標準庫中也隨處可見,例如:
#if (!defined __STRICT_ANSI__ && !defined _ISOC99_SOURCE && \
     !defined _POSIX_SOURCE && !defined _POSIX_C_SOURCE && \
     !defined _XOPEN_SOURCE && !defined _BSD_SOURCE && \
     !defined _SVID_SOURCE)
# define _BSD_SOURCE    1
# define _SVID_SOURCE    1
#endif
因此,這里建議使用 defined。因為即使當前代碼使用的是簡單的條件編譯,以后在維護或升級時也可能會增加,這樣也可以提高程序的可維護性。

精美而實用的網站,提供C語言、C++、STL、Linux、Shell、Java、Go語言等教程,以及socket、GCC、vi、Swing、設計模式、JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo