天天看點

【C語言進階剖析】22、條件編譯使用分析

1 基本概念

2 條件編譯的本質

3 #include 的本質

4 條件編譯的意義

5 小結

開始之前我們先說一個問題,很多軟體都有高端版本和低端版本,這是如何做到的呢,如果讓兩撥人分别開發高端版本和低端版本,這是可以的,但是顯然很不現實,如何隻做一款産品就有高端版本和低端版本呢,先别急着找答案,我們繼續向下看。

條件編譯的行為類似于 c 語言中的 if…else…

條件編譯是預編譯訓示指令,用于控制是否編譯某段代碼

先來看段代碼,對條件編譯有個直覺的概念

代碼很簡單,就是一個 if…else…語句,看列印哪一行。編譯,運作結果如下:

可以看到和我們預想的結果完全一樣,因為定義了宏 c 為 1,if 為真,是以列印 this is first printf…。這和 c 語言中的 if…else…類似

下面有個問題:條件編譯真的和 c 語言中的 if…else…一樣嗎?

想知道是不是和 if…else 一樣,下面進行單獨編譯,為了避免不必要的資訊,将上面代碼中的第 2 行注釋,編譯如下:

打開檔案 22-1.i 如下:

可以看到 if…else 沒有了,我們定義的宏 c 也沒有了。 其實條件編譯是用來訓示預處理器的,根據條件編譯保留什麼代碼,删除什麼代碼,用于控制後續編譯器編譯哪段代碼。

預編譯器根據條件編譯指令有選擇的删除代碼

編譯器不知道代碼分支的存在

if…else…語句在運作期進行分支判斷

條件編譯指令在預編譯期進行分支判斷

可以通過指令行定義宏,不用在代碼中 #define

通過指令行定義宏的文法如下:

gcc -dmacro = value file.c

gcc -dmacro file.c

前者用來定義宏的值,後者用來定義宏是否存在

下面我們就來嘗試一下通過指令行定義宏

編譯運作結果如下:

列印了第 9 行代碼,我們我們想列印第 7 行代碼,可以通過指令行定義宏,編譯運作結果如下:

這裡在指令行定義了宏的值,列印了this is first printf…也就是說第 7 行被保留了,第 9 行被删除了。

我們來看看指令行定義宏的另外一種方式,定義宏是否存在,宏和變量不一樣,宏既可以表示一個值,也可以表示一個标示符。需要修改一下代碼,修改後的如下:

編譯運作如下:

為了更透徹的了解,我們将第 2 行注釋,隻進行預編譯,看一下預編譯之後的結果:

打開檔案 22-2.i 如下:

可以看到使用指令行定義了宏 c,預編譯器保留了第 7 行代碼,删除了第 9 行代碼 反過來,如果在指令行不定義宏 c,預編譯器将删除第 7 行代碼,保留第 9 行代碼

#include 的本質是将已經存在的檔案内容嵌入到目前檔案中

#include 的間接包含同樣會産生嵌入檔案内容的操作

在預處理時,頭檔案中的内容會被複制過來,有人就說了,那 #include 還有什麼好學的,不就是直接複制過來嗎?

下面就來考慮一個問題:間接包含同一個頭檔案是否會産生編譯錯誤?具體如下:檔案 test.c 中包含頭檔案 test.h 和 global.h,test.h 中也包含了檔案 global.h,關系如下圖所示,也就是說頭檔案 global.h 被包含了兩次,那麼編譯能通過嗎?

【C語言進階剖析】22、條件編譯使用分析

我們來嘗試一下

對檔案進行編譯,結果如下:

【C語言進階剖析】22、條件編譯使用分析

編譯器提示我們變量 global 重複定義了,為了一探究竟,我們單步編譯一下看看。将 #include <stdio.h> 注釋,單步編譯

打開檔案 22-3.i,内容如下:

代碼第 10 行表示這裡是頭檔案 test.h,如何處理呢,就是直接将頭檔案 test.h 的内容複制過來,由于 test.h 中又包含了 global.h 是以将 global.h 的内容複制過來,第 12 行表示這是複制過來的 global.h 檔案;處理完 #include “test.h” 後,再處理頭檔案

#include “global.h”,如第 21 行所示,同樣是将 global.h 中的頭檔案直接複制過來。

這就出現問題了,global.h 中的内容被複制了兩遍,是以出現了在 14 行和第 23 行兩次定義變量 global 的情況,編譯器提示我們變量 global 重複定義了。

是以頭檔案的重複包含在軟體産品中是不正确的,會使得編譯器看到多段相同的代碼,那麼問題來了 ,現在的軟體産品,有成千上萬的檔案,怎麼能保證不重複包含呢,這顯然是不可能的,那麼怎麼保證重複包含也不出現錯誤呢?

下面我們将代碼修改一下,22-3.c 的代碼不改動,修改檔案 test.h 和檔案 global.h,如下所示:

以 global.h 為例,#ifndef _global_h_ 意思是如果沒有定義宏 _global_h_ ,那麼将執行第 3 行代碼,#define _global_h_,定義宏 _global_h_,如果已經定義了宏 _global_h_,下面的代碼将直接被删除,是以隻有第一次引用頭檔案時代碼會被複制過來,同時也定義了宏,後面再次引用該頭檔案由于已經定義了宏,頭檔案中的代碼将直接删除,不存在将一個檔案拷貝多份的情況,避免了重複定義。test.h 也是一樣的。

下面我們來編譯、運作一下,結果如下:

我們再次單步編譯一下,隻進行預處理,看看結果如何?

打開檔案 22-3.i

可以看到 global.h 檔案隻被複制了一次。是以使用條件編譯可以避免頭檔案被複制多份的情況,多次引用也不會出現問題,隻有第一次的引用會被複制過來,後面的都不會再複制了

下面我們總結一下:

條件編譯可以解決頭檔案重複包含的編譯錯誤

文法如下:

【C語言進階剖析】22、條件編譯使用分析

條件編譯使得我們可以按不同的條件編譯不同的代碼段,因而産生不同的目标代碼

#if…#else…#endif 被預編譯器處理,而 if…else…語句被編譯器處理,必然被編譯進目标代碼

實際工程中條件編譯主要用于以下兩種情況

不同的産品線共用一份代碼

區分編譯産品的調試版和釋出版

利用條件編譯我們就可以有選擇的删除或保留代碼,這樣就可以編譯出不同的産品,也就有了高版本和低版本。具體是怎麼做的呢,我們來實際操作一下。

上面代碼,第 4 行到第 8 行,如果宏 debug 為真,定義一個宏 log(s) 列印日志,否則定義的 log(s) 為空,啥也不幹。 第 10 行到第 19 行,表示如果定義的宏 high 為真,定義函數 f() ,該函數列印一條版本資訊,否則,定義函數 f() ,該函數為空,啥也不幹。 23 行列印日志,24 行執行函數 f() 第 29 行到 第 35 行表示如果宏 high 為真,執行下面三條列印語句(軟體進階功能),否則執行一條列印語句 是以宏 debug 用來表示這是一個調試版程式還是一個釋出版程式,宏 high 用來表示是一個高端版軟體還是一個低端版軟體

下面我們就來執行一下程式,執行結果如下:

上面的執行結構表示,這是一個調試版的高端版本,如果想要低端版本的釋出産品,應該怎麼做呢,很簡單,修改頭檔案 product.h 即可,修改如下:

再次編譯運作,結果如下:

結果顯示。高端功能沒有運作,日志也沒有列印出來,是以這是一個低端的釋出産品。

1、通過編譯器指令行能夠定義預處理器使用的宏

2、條件編譯可以避免重複包含同一個頭檔案

3、條件編譯在工程開發中可以差別不同的産品線代碼

4、條件編譯可以定義産品的釋出版和調試版