【C進階】21、巨集定義與使用分析

bryson發表於2021-12-23

Summary

1)#define前處理器處理的單元實體之一,在預編譯期進行文字替換

2)#define定義的巨集可以出現在程式的任意地方,定義之後的程式碼都可以使用

3)#define可以定義巨集常量,本質上是字面量

4)define可以定義表示式,使用上類似函式;功能可能更強大(型別可以作為引數、求陣列大小);更容易出錯(當和其他運算混合在一起時)

5)巨集由於是直接文字替換,所以沒有任何的呼叫開銷;巨集表示式裡不能出現遞迴;巨集被前處理器處理,所以編譯器不知道巨集的存在,自然巨集也不會有作用域的概念作用域是針對變數和函式的

6)常用的預定義巨集

巨集含義示例
__FILE__被編譯的檔名file1.c
__LINE__當前行號25
__DATE__編譯時的日期Jan 31 2012
__TIME__編譯時的時間17:01:01
__STDC__編譯器是否遵循標準C規範1

巨集定義與使用分析

#define前處理器處理的單元實體之一
#define定義的巨集可以出現在程式的任意位置
#define定義之後的程式碼都可以使用這個巨集

1、#define定義的巨集常量

#define定義的巨集常量可以直接使用,本質為字面量(不會佔用記憶體,字串字面量會存在只讀儲存區)

// test.c中 下面的巨集定義正確麼?
#define ERROR -1
#define PATH1 "D:\test\test.c"
#define PATH2 D:\test\test.c
#define PATH3 D:\test\
test.c

int main()
{
    int i = ERROR;
    char* p1 = PATH1;
    char* p2 = PATH2;
    char* p3 = PATH3;
    return 0;
}
單步編譯
gcc -E test.c -o test.i    ==> 預編譯,生成中間檔案.i
gcc -S test.i -o test.s    ==> 編譯,生成彙編檔案.s

分析:在第一步預編譯階段,處理define並生成.i檔案,這時候不會出錯;下一步編譯階段,將中間檔案轉換為彙編檔案時,就會報錯。
image.png
原因在於:在預編譯的階段,僅僅是做文字替換,沒有語法檢查;到了編譯階段,會對替換後的.i檔案進行語法檢查,再生成彙編檔案,這時候語法檢查就出錯了。
image.png

2、#define定義的表示式

#define表示式的使用類似函式呼叫
#define表示式可以比函式更強大
#define表示式比函式更易出錯

#define SUM(a, b) (a) + (b)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define DIM(a) sizeof(a) / sizeof(*a)

int main()
{
    int a = 1;
    int b = 3;
    int c[2] = {0};

    // expected: 4, 1, 2
    printf("SUM(a, b) = %d\n", SUM(a, b));
    printf("MIN(a, b) = %d\n", MIN(a, b));
    printf("size of array = %d\n", DIM(c));

    // unexpected: 3
    printf("unexpected: %d", MIN(++a, b));   // 期望得到++a和b中的較小的值:2
    return 0;
}

分析:

  • define表示式類似函式呼叫,像上面的例子中,printf中巨集的定義就很像是函式呼叫。
  • define表示式可能比函式更強大,如上,DIM巨集可以求陣列的大小但是在C語言中無法通過函式求一個陣列的大小,因為當陣列作為函式的引數時,會退化成指標;再比如,C語言中不能把型別作為引數,但是巨集就可以。
  • define表示式更容易出錯,如上,MIN(++a, b)期望得到的值為2,實際得到的值卻是3。由單步編譯得到的結果可以看出,替換後的表示式為((++a) < (b) ? (++b) : (b)),前置的++被執行了2次,所以得到了3。預編譯器就像是一個傳話筒,在傳話的過程中,就發生了歧義

巨集表示式被前處理器處理,編譯器並不知道巨集的存在;
巨集表示式用“實參”完全替代形參,不進行任何運算
巨集表示式沒有任何的呼叫開銷:(因為直接進行文字替換,不像函式需要引數入棧、返回等開銷)
巨集表示式中不能出現遞迴定義:(因為巨集只在預處理期進行一次文字替換,後續的符號編譯器就不認識了)

#define SUM(n) ((n>0) ? (SUM(n-1) + n) : 0)
int s = SUM(10);    // 編譯的時候就會報錯,undefined reference SUM

3、巨集定義的常量或表示式是否有作用域限制?

下面的程式合法麼?
void def()
{
    #define PI 3.1415926
    #define AREA(r) r*r*PI
}
double area(double r)
{
    retrun AREA(r);
}

int main()
{
    double r = area(10);
    return 0;
}

分析:編譯、執行都不會出錯。說明巨集是沒有作用域限制的。只要定義完,後面的程式碼都可以用。在定義之前使用就會報錯undefined reference。作用域只是針對變數和函式的,巨集在預處理期被展開,在編譯期已經沒有巨集這個東西了,所以也就沒法給巨集限定作用域了。

4、 常用的預定義巨集

巨集含義示例
__FILE__被編譯的檔名file1.c
__LINE__當前行號25
__DATE__編譯時的日期Jan 31 2012
__TIME__編譯時的時間17:01:01
__STDC__編譯器是否遵循標準C規範1

本文總結自“狄泰軟體學院”唐佐林老師《C語言進階課程》。
如有錯漏之處,懇請指正。

相關文章