C之條件編譯(二十)
條件編譯的行為類似於 C 語言中的 if ... else ... ,條件編譯是預編譯指示命令用於控制是否編譯某段代碼。我們通過下面這個示例代碼進行分析,代碼如下
#include <stdio.h> #define C 1 int main() { const char* s; #if ( C == 1 ) s = "This is first printf...\n"; #else s = "This is second printf...\n"; #endif printf("%s", s); return 0; }
我們來分析下,如果我們直接編譯的話,就會打印 This is first printf... 這句話。編譯後看看結果
結果如我們分析的那樣,下來我們註釋掉第3行的宏定義,那麽就會打印 This is second printf... 這句話了,這個就不做實驗了,大家可以自己做下看看結果是否如我們分析的那樣。我們在上面說到 條件編譯的行為類似於 C 語言中的看代碼的話確實比較像,我們再來單步編譯下,看看代碼是如何被處理的,為了避免出現不相幹的代碼 if ... else ... ,我們註釋掉頭文件和打印語句,結果如下
大家可以看到它直接就被替換了。下來我們就講下如何在宏定義被註釋掉的情況下還打印出第一句話,那麽這就是我們所謂的條件編譯了。
預編譯器根據條件編譯指令有選擇的刪除代碼,編譯器不知道代碼分支的存在。 if ... else ... 語句在運行期進行分支判斷,而條件編譯指令在預編譯期進行分支判斷。我們可以通過命令行宏定義來指定條件編譯,命令如:gcc -Dmacro=value file.c 或 gcc -Dmacro file.c。ps:因為宏定義在 C 語言中還有個起到標識符的作用,因此我們可以直接命令行定義它,使條件為真即可。
我們將上面的代碼中的第3行的宏定義去掉,再將第9行的 #if ( C == 1 ) 變成 #ifden C,再來編譯下,看看結果
我們再次加上 -Dmacro
我們看到通過在命令行定義宏來達到條件編譯的結果。下面我們來講講 #include 的本質,它的本質是將已經存在的文件內容嵌入到當前文件中,#include 的間接包含同樣會產生嵌入文件內容的操作。我們看看間接包含同一個頭文件是否會產生編譯錯誤,結構如下
我們來創建 global.h 和 test.h。
global.h 代碼
// global.h int global = 10;
test.h 代碼
// test.h #include "global.h" const char* NAME = "test.h"; char* hello_world() { return "Hello world!\n"; }
test.c 代碼
#include <stdio.h> #include "test.h" #include "global.h" int main() { const char* s = hello_world(); int g = global; printf("%s\n", NAME); printf("%d\n", g); return 0; }
我們來看看編譯結果
它說 global 重復定義了,我們再來仔細看看三個文件,發現只是在 global.h 文件中定義了 global = 10,那麽這是怎麽回事呢?我們來單步編譯下,看看代碼是什麽樣的(同樣註釋掉頭文件和打印語句),由於代碼過長,所以就直接上代碼了,代碼如下
# 1 "test.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 2 # 1 "test.c" # 1 "test.h" 1 # 1 "global.h" 1 int global = 10; # 4 "test.h" 2 const char* NAME = "test.h"; char* hello_world() { return "Hello world!\n"; } # 3 "test.c" 2 # 1 "global.h" 1 int global = 10; # 4 "test.c" 2 int main() { const char* s = hello_world(); int g = global; return 0; }
我們發現在第 14 行定義了 global,在後面包含 global.h 時又重新定義了 global 變量。這樣看來編譯器報的錯誤就很正常了,那麽我們經常會使用到包含多個頭文件,也沒見會發生重復定義啊。這時利用條件編譯便可以解決頭文件重復包含的編譯錯誤,格式如下
#ifndef _HEADER_FILE_H_ #define _HEADER_FILE_H_ // source code #endif
我們再次在兩個頭文件中分別加上條件編譯,我們再次編譯,結果如下
沒有報錯,而是直接成功執行,我們再來單步編譯下,看看代碼是怎樣的
# 1 "test.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 2 # 1 "test.c" # 1 "test.h" 1 # 1 "global.h" 1 int global = 10; # 6 "test.h" 2 const char* NAME = "test.h"; char* hello_world() { return "Hello world!\n"; } # 3 "test.c" 2 int main() { const char* s = hello_world(); int g = global; return 0; }
我們發現 global 變量只定義了一次。這便是我們條件編譯的一種應用啦。那麽條件編譯的意義還不止這些,還有如下幾點:a> 條件編譯使得我們可以按不同的條件編譯出不同的代碼段,因而可以產生不同的目標代碼;b> #if ... #else .. #endif 被預編譯器處理,而 if ... else... 語句被編譯器處理,必然被編譯進目標代碼。在實際的工程中條件編譯主要用於以下兩種情況:1. 不同的產品線共用一份代碼;2. 區分編譯產品的調試版和發布版。我們看看下面的示例代碼
#include <stdio.h> #include "product.h" #if DEBUG #define LOG(s) printf("[%s:%d] %s\n", __FILE__, __LINE__, s) #else #define LOG(s) NULL #endif #if HIGH void f() { printf("This is the high level product!\n"); } #else void f() { } #endif int main() { LOG("Enter main() ..."); f(); printf("1. Query Information.\n"); printf("2. Record Information.\n"); printf("3. Delete Information.\n"); #if HIGH printf("4. High Level Query.\n"); printf("5. Mannul Service.\n"); printf("6. Exit.\n"); #else printf("4. Exit.\n"); #endif LOG("Exit main() ..."); return 0; }
product.h 代碼如下
#define DEBUG 1 #define HIGH 1
我們分析下,如果定義 DEBUG 的話,我們便定義 LOG 日誌宏,以便來打印一些信息。如果定義 HIGH 的話,我們便是高版本的了,在 main 函數中除了打印 1 2 3,還將會打印出 4 5 6。如果沒定義 HIGH,便打印 4 就完了。因為我麽在 product.h 中分別定義它們為真,所以應該打印出的是高版本的調試版的。我們看看編譯結果
結果如我們分析的那樣,我們如果需要一個低版本的發布版,這時只需要在 product.h 中定義 DEBUG 和 HIGH 分別為 0,我們看看結果
我們如果需要一個高版本的發布版,這時只需要在 product.h 中定義 DEBUG 為 0 和 HIGH 為 1,結果如下
那麽我們這時就可以根據我們的需求來編譯各種版本的啦。通過對條件編譯的學習,總結如下:1、通過編譯器命令行能夠定義預處理器使用的宏;2、條件編譯可以避免重復包含同一個頭文件;3、條件編譯是在工程開發中可以區別不同產品線的代碼;4、條件編譯可以定義產品的發布版和調試版。
歡迎大家一起來學習 C 語言,可以加我QQ:243343083。
C之條件編譯(二十)