條件編輯及宏定義學習小結

Rice_rice發表於2024-05-22

​ 條件編譯(也稱為預處理)是一種在編譯階段控制程式碼是否包含進最終編譯單元的技術。這通常透過使用前處理器指令(如#if, #ifdef, #ifndef, #else, #elif, 和 #endif)來完成。而條件編譯通常是和宏聯絡在一起,因此說宏帶有不用來回切換,宏替換髮生在編譯的預處理階段,省區函式切換的時間花銷,沒有實參形參計算等優點。

​ 因此本次主要記錄條件編譯,並同時記錄宏定義所需要注意的部分細節。

第一種形式

#ifdef MACRO
	some statements
#endif

如果定義了宏 MACRO,則編譯some statements,否則不編譯。


例如下面這個簡單函式:

#include <stdio.h>
int main(int argc, char **argv)
{
    printf("條件編譯的用法演示\n");
#ifdef HEL
    printf("Hello world\n");
#endif
    return 0;
}

當正常編譯時,沒有執行宏定義的選項,會出現下面的情況:

rice@rice:/mnt/f/$ gcc open.c -o open
rice@rice:/mnt/f/$ ./open
條件編譯的用法演示

當編譯時新增-HEL選項,提前增加DEL的宏定義,滿足#ifdef的條件,結果如下:

rice@rice:/mnt/f/$ gcc open.c -DHEL -o open
rice@rice:/mnt/f/$ ./open
條件編譯的用法演示
Hello world

第二種形式

#ifnodef MACRO
	some statements
#endif

​ 如果沒定義宏 MACRO,則編譯some statements,否則不編譯。與第一種形式正好相反,最常用於標頭檔案中,使得標頭檔案的內容不會被重複包含。


#ifndef GET_TIME_H
#define GET_TIME_H

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "font.h" //字型函式

void get_Time(int *lcd_mp);

#endif

​ 在我的get_Time.h標頭檔案中,使用了#ifndef, #define, 和 #endif這三個前處理器指令來確保標頭檔案的內容只被包含一次,即使在多個原始檔中多次包含了此標頭檔案解釋如下:

  1. #ifndef GET_TIME_H:檢查GET_TIME_H這個宏是否已經被定義。如果GET_TIME_H沒有被定義,那麼編譯器會處理它和與之對應的#endif之間的程式碼,即引用了包含的標頭檔案。

  2. #define GET_TIME_H:這是一個宏定義指令,它定義了一個名為GET_TIME_H的宏,但不為其分配任何值(這樣的宏通常被稱為“標記”或“標記宏”)。GET_TIME_H被定義成了空的,這個指令的目的是防止在標頭檔案被再次包含時再次包含標頭檔案的內容。

  3. #endif:與#ifndef對應關係,標誌著#ifndef條件編譯塊的結束。

    另外,作為一個宏,目前接觸到了幾種常見寫法,不外乎幾個小細節:字母全部大寫,符號改成"_",且首尾增加 "_",例如上述標頭檔案條件編譯宏還可以寫為:_GET_TIME_H_

第三種形式

#if expression
	some statements
#endif

​ 如果expression為真(非零),則編譯some statements,否則不編譯。如果expression是一個宏,則必須是一個整數或一個整形表示式。

下面是Linux原始碼部分內容

# if defined __USE_MISC || defined __USE_XOPEN
#  define S_ISVTX	__S_ISVTX
# endif

在Linux原始碼可經常看到條件編譯的內容,在之後除錯BUG,函式封裝的標頭檔案中也會經常用到,需要熟練掌握。

宏的應用

​ 宏分為帶引數和不帶引數的宏。不帶引數的宏非常簡單,是在使用的時候進行簡單的文字替換,不再贅述。需要特別掌握的是帶引數的宏。以下利用一個例子,來總結帶引數宏使用的幾個要點:

#define MUL1(a, b) a *b + a *b           
#define MUL2(a, b) (a) * (b) + (a) * (b) // 每個引數增加括號
#define MUL3(a, b)                   \
    ({                               \
        int aa = a;                  \
        int bb = b;                  \
        ((aa) * (bb) + (aa) * (bb)); \
    })

使用時同樣是文字替換,卻能實現不一樣的結果,例如上述宏定義,引用時,程式碼及得到的結果如下:

int main(int argc, char **argv)
{
    int a=1;
    int b=2;
    int c1 = 0, c2 = 0, c3 = 0;
    c1 = MUL1(a+a, b+b);
    c2 = MUL2(a+a, b+b);
    c3 = MUL3(a+a, b+b);
    printf("c1=%d c2=%d c3=%d", c1, c2, c3);
    return 0;
}

得到的結果:c1=10 c2=16 c3=16

由於宏定義只進行文字替換,很容易看出三者的區別,預處理後,文字替換如下:

    c1 = MUL1(a, b)=1+1*2+2+1+1*2+2;
    c2 = MUL2(a, b)=(1+1)*(2+2)+(1+1)*(2+2);
    c3 = MUL3(a, b)=(1+1)*(2+2)+(1+1)*(2+2); 

再舉個例子,程式碼及得到的結果如下:

int main(int argc, char **argv)
{
    int a = 1;
    int b = 2;
    int a1 = 1;
    int b1 = 2;
    int a2 = 1;
    int b2 = 2;
    int c1 = 0, c2 = 0, c3 = 0;
    c1 = MUL1(a, b++);
    c2 = MUL2(a1, b1++);
    c3 = MUL3(a2, b2++);
    printf("c1=%d c2=%d c3=%d", c1, c2, c3);
    return 0;
}

得到的結果:c1=5 c2=5 c3=4

由於宏定義只進行文字替換,預處理後,文字替換如下:

    c1 = MUL1(a, b)=a*b++ +a*b++=1*2+1*3=5
    c2 = MUL2(a1, b1)=(a1)*(b1++)+(a1)*(b1++)=1*2+1*3=5
    c3 = MUL3(a2, b2)=   
        ({                               \
        int aa = a2;                  \
        int ba = b2++;                  \
        ((a1) * (b1) + (a1) * (b1)); \
    })				=1*2+1*2=4	

因此,對於宏定義可得到如下小結

1.對於帶引數的宏定義,切記要對於每個元素增加括號,以免運算優先順序影響結果。

2.對於在宏定義中會多次出現的引數,應避免。若實在避免不了,可另外定義{(...)}複合語句,利用其他引數代替,且避免使用字首或字尾運算子(例如“++”,“--”)等。

相關文章