1. 程式人生 > >C之條件編譯(二十)

C之條件編譯(二十)

C語言 條件編譯

我們在平時的項目中,難免會遇到這樣的問題:一個產品需要好幾個版本(如低、中、高版本)。那麽問題來了,我們需要對這一個產品進行幾個版本的人馬的同時開發嗎?當然是不用啦,企業是講究效益的,當然是希望一個產品一份代碼就搞定啦。在這時我們就可以使用 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之條件編譯(二十)