C語言#define宏定義,你真的瞭解嗎?

在C語言中,我們使用#define來定義宏。在C程序編譯的預處理階段,預處理器會把宏定義的符號替換成指定的文本。

不帶參數的宏

關於宏最常見的就是用來定義數值常量的名稱,即沒有參數的宏定義,採用如下形式:

#define 宏名稱 替換文本

例如:

#define ARRAY_SIZE  10
int data[ARRAY_SIZE];

當程序需要修改數組長度時,只需要修改宏定義即可,無需對程序中每一處用到數組長度的地方進行修改。

帶參數的宏

你可以定義具有形參的宏,然後預處理器展開這類宏時,會將宏指定的實參替換文本中對應的形參。這有點像函數,故也叫做函數式宏定義、類函數宏,形式如下:

#define 宏名稱([行參列表]) 替換文本
#define 宏名稱([行參列表,]...) 替換文本

當宏被調用時,替換文本中的每個值都與形參列表相對應。另外C99標準允許定義有參略號的宏,省略號必須放在參數列表的後面,以表示可選參數。

當調用有可選參數的宏時,預處理器會將所有可選參數連同分隔它們的逗號打包在一起作爲一個參數。在替換文本中,標識符 VA_ARGS 對應前面打包的可選參數。

//假設有個已經打開的日誌文件,準備採用文件指針fp_log對其進行寫入
#define printLog(...) fprintf(fp_log, __VA_ARGS__)
//使用printLog
printLog("%s: intVar=%d\r\n", __func__, intVar);

預處理器把最後一行的宏調用替換成下面一行代碼:

fprintf(fp_log, "%s: intVar=%d\r\n", __func__, intVar);

帶參宏的一些問題

#include <stdio.h>
#define SQUARE(x)   x * x
int main(int argc, char const *argv[])
{
    int a = 5;
    printf("%d\r\n", SQUARE(5));
    printf("%d\r\n", SQUARE(a+1));
    return 0;
}

​ 程序運行,第一條打印顯而易見爲25,第二條打印爲多少呢?不是36而是11。我們通過"gcc -E a.c >a.txt",將預處理後的文件重定向到a.txt,來觀察被替換的宏文本。

printf("%d\r\n", 5 * 5);
printf("%d\r\n", a+1 * a+1);

可見替換產生的表達式並沒有按照預想的次序進行求值。可能大家會說,加上兩個括號就解決了嘛:

#define SQUARE(x)  (x) * (x)

那麼,爲每個出現在替換文本中的參數加上括號就一定沒問題了嗎?看下面例子:

#include <stdio.h>
#define ADD(x)  (x) + (x)
int main(int argc, char const *argv[])
{
    int a = 5;
    printf("%d\r\n", 10 * ADD(5));
    return 0;
}

看似輸出100,實際輸出55。我們看下替換後的文本:

printf("%d\r\n", 10 * (5) + (5));

乘法運算在加法運算之前執行,所以結果爲55。這個錯誤很容易修正:整個表達式加上括號:

#define ADD(x)  ((x) + (x))

所有對數值表達式進行求值的宏定義,爲避免參數中操作符或鄰近的操作符之間不可預料的互相作用,應對每個參數加括號,整個表達式也要加括號。

宏與函數

儘可能多的加括號就絕對不會有問題了嗎?看下面例子:

#include <stdio.h>
#define MAX(a, b)   ((a) > (b) ? (a) : (b))
int main(int argc, char const *argv[])
{
    int a = 5;
    int b = 5;
    int m = MAX(++a, b);
    printf("%d\r\n", m);
    return 0;
}

m的值是多少?是不是相當於執行MAX(6, 5),然後輸出6呢?答案是7,看下替換的文本:

int m = ((++a) > (b) ? (++a) : (b));

這裏就是C語言中函數式宏定義的陷阱,傳遞參數++a會被展開到替換文本的每一個a處。所以,再多的括號也不可能確保萬無一失。

那麼,函數式宏定義較函數有什麼優點嗎?

  • 相較於函數的分配和釋放棧、傳參、傳返回值等一系列操作,函數式宏定義的代碼執行效率高。
  • 函數的參數必須聲明爲一種特定的類型,而宏是與類型無關的。以比較大小爲例,如果我們需要比較整形、長整型、浮點型數值大小,由於C語言不支持函數重載,我們需要爲每一種數據類型實現一個max()函數,顯然使用宏定義要來的方便了。當然宏定義不檢測參數類型也是把雙刃劍,可能導致程序不安全,需要特別注意。
  • 還有一些函數無法實現的任務,比如說,如何將數據類型作爲參數傳遞給函數?看宏定義怎麼破:
#define MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))
...
p = MALLOC(20, int);

替換後的文本:

p = ((int *)malloc((20) * sizeof(int)));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章