C 和 C++ 宏 詳解
android
宏 替換 發生的時機
ios
爲了可以真正理解#define的做用,須要瞭解下C語言源程序的處理過程。當在一個集成的開發環境如Turbo C中將編寫好的源程序進行編譯時,實際通過了預處理、編譯、彙編和鏈接幾個過程。其中預處理器產生編譯器的輸出,它實現如下的功能:c++
文件包含。
能夠把源程序中的#include 擴展爲文件正文,即把包含的.h文件找到並展開到#include 所在處。
條件編譯。
預處理器根據#if和#ifdef等編譯命令及其後的條件,將源程序中的某部分包含進來或排除在外,一般把排除在外的語句轉換成空行。
宏展開。
預處理器將源程序文件中出現的對宏的引用展開成相應的宏 定義,即本文所說的#define的功能,由預處理器來完成。通過預處理器處理的源程序與以前的源程序有全部不一樣,在這個階段所進行的工做只是純粹的替換與展開,沒有任何計算功能,因此在學習#define命令時只要能真正理解這一點,這樣纔不會對此命令引發誤解並誤用。
程序員
1. #define的基本用法
編程
#define 是 C語言中提供的宏定義命令,其主要目的是爲程序員在編程時提供必定的方便,並能在必定程度上提升程序的運行效率,但學生在學習時每每不能 理解該命令的本質,老是在此處產生一些困惑,在編程時誤用該命令,使得程序的運行與預期的目的不一致,或者在讀別人寫的程序時,把運行結果理解錯誤,這對 C語言的學習很不利。windows
1.1 #define命令剖析
數組
#define命令是C語言中的一個宏定義命令,它用來將一個標識符定義爲一個字符串,該標識符被稱爲宏名,被定義的字符串稱爲替換文本。該命令有兩種格式:一種是簡單的宏定義,另外一種是帶參數的宏定義。app
一個標識符被宏定義後,該標識符即是一個宏名。這時,在程序中出現的是宏名,在該程序被編譯前,先將宏名用被定義的字符串替換,這稱爲宏替換,替換後才進行編譯,宏替換隻是簡單的替換,即 簡單的純文本替換,C預處理器不對宏體作任何語法檢查,像缺個括號、少個分號什麼的預處理器是無論的。模塊化
宏體換行須要在行末加反斜槓 \
宏名以後帶括號的宏 被認爲是 宏函數。用法和普通函數同樣,只不過在預處理階段,宏函數會被展開。優勢是沒有普通函數保存寄存器和參數傳遞的開銷,展開後的代碼有利於CPU cache的利用和指令預測,速度快。缺點是可執行代碼體積大。
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
y = min(1, 2);會被擴展成y = ((1) < (2) ? (1) : (2));
分號吞噬 問題:
#define MAX(x,y) \
{ \
return (x) > (y) ? (x):(y); \
}
if(1)
MAX(20, 10); //這個分號致使這一部分代碼塊結束,導致else找不到對應的if分支而報錯
else
;;
上面 宏 展開後 if else 代碼以下
if(1)
{ return (20) > (10) ? (20):(10); };//後面多了一個分號,致使 if 代碼塊結束,導致else找不到對應的if分支而報錯
else
;;
示例代碼(test.c):函數
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX(x,y) \
{ \
return (x) > (y) ? (x):(y); \
}
void main()
{
if(1)
MAX(20, 10); //這個分號致使這一部分代碼塊結束,導致else找不到對應的if分支而報錯
else
;;
}
gcc -E test.c -o test.e 會生成 test.e 的預處理文件
gcc -E test.c 會直接把預處理後內容輸出到屏幕上。直接輸出到屏幕上截圖:
能夠看到後面多了一個分號。如今執行編譯
能夠看到 else 分支缺乏 對應的 if 。
預處理並不分析整個源代碼文件, 它只是將源代碼分割成一些標記(token), 識別語句中哪些是C語句, 哪些是預處理語句. 預處理器可以識別C標記, 文件名, 空白符, 文件結尾標誌.
預處理語句格式: #command name(...) token(s)
1, command預處理命令的名稱, 它以前以#開頭, #以後緊隨預處理命令, 標準C容許#兩邊能夠有空白符, 但比較老的編譯器可能不容許這樣. 若某行中只包含#(以及空白符), 那麼在標準C中該行被理解爲空白. 整個預處理語句以後只能有空白符或者註釋, 不能有其它內容.
2, name表明宏名稱, 它可帶參數. 參數能夠是可變參數列表(C99).
3, 語句中能夠利用"\"來換行.
e.g.
# define ONE 1 /* ONE == 1 */
等價於: #define ONE 1
#define err(flag, msg) if(flag) \
printf(msg)
等價於: #define err(flag, msg) if(flag) printf(msg)
簡單的宏定義
#define <宏名> <字符串>
例: #define PI 3.1415926
#define FALSE 0
帶參數的宏定義
#define <宏名>(<形式參數表>) <宏體>
例: #define A(x) x
#define MAX(a,b) ( (a) > (b) ) ? (a) : (b)
取消宏定義:#undef 宏名
可變參數 的 宏
C/C++宏定義的可變參數詳細解析_C 語言:https://yq.aliyun.com/ziliao/134584
#define LOG( format, ... ) printf( format, __VA_ARGS__ )
LOG( "%s %d", str, count );
__VA_ARGS__是系統預約義宏,被自動替換爲參數列表。
#define debug(format, args...) fprintf (stderr, format, args)
#define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)
或者
#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)
前二者存在多餘逗號問題,第三個宏使用##去掉可能多餘的逗號。
便可變參數被忽略或爲空,’##’操做將使預處理器(preprocessor)去除掉它前面的那個逗號
當一個宏本身調用本身時,會發生什麼?
例如:#define TEST( x ) ( x + TEST( x ) )
TEST( 1 ); 會發生什麼?爲了防止無限制遞歸展開,語法規定,當一個宏遇到本身時,就中止展開,也就是說,當對TEST( 1 )進行展開時,展開過程當中又發現了一個TEST,那麼就將這個TEST看成通常的符號。TEST(1) 最終被展開爲:1 + TEST( 1) 。
宏參數的prescan(預掃描)
當一個宏參數被放進宏體時,這個宏參數會首先被所有展開(有例外,見下文)。當展開後的宏參數被放進宏體時,預處理器對新展開的宏體進行第二次掃描,並繼續展開。例如:
#define PARAM( x ) x
#define ADDPARAM( x ) INT_##x
PARAM( ADDPARAM( 1 ) );
由於ADDPARAM( 1 ) 是做爲PARAM的宏參數,因此先將ADDPARAM( 1 )展開爲INT_1,而後再將INT_1放進PARAM。
例外狀況:若是PARAM宏裏對宏參數使用了#或##,那麼宏參數不會被展開:
#define PARAM( x ) #x
#define ADDPARAM( x ) INT_##x
PARAM( ADDPARAM( 1 ) ); //將被展開爲"ADDPARAM( 1 )"。
使用這麼一個規則,能夠建立一個頗有趣的技術:打印出一個宏被展開後的樣子,這樣能夠方便你分析代碼:
#define TO_STRING( x ) TO_STRING1( x )
#define TO_STRING1( x ) #x
TO_STRING首先會將x所有展開(若是x也是一個宏的話),而後再傳給TO_STRING1轉換爲字符串。
如今你能夠這樣:
const char *str = TO_STRING( PARAM( ADDPARAM( 1 ) ) );
去一探PARAM展開後的樣子。
一個很重要的補充:
若是一個像函數的宏在使用時沒有出現括號,那麼預處理器只是將這個宏做爲通常的符號處理(即 就是不處理)。
函數宏對參數類型是不敏感的, 你沒必要考慮將何種數據類型傳遞給宏. 那麼, 如何構建對參數類型敏感的宏呢? 參考關於"##"的介紹.
對象宏
不帶參數的宏被稱爲"對象宏(objectlike macro)"
#define 常常用來定義常量, 此時的宏名稱通常爲大寫的字符串. 這樣利於修改這些常量.
e.g.
#define MAX 100
int a[MAX];
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
#endif
#define __FILE_H__ 中的宏就不帶任何參數, 也不擴展爲任何標記. 這常常用於包含頭文件.
要調用該宏, 只需在代碼中指定宏名稱, 該宏將被替代爲它被定義的內容.
函數宏
帶參數的宏也被稱爲"函數宏". 利用宏能夠提升代碼的運行效率: 子程序的調用須要壓棧出棧, 這一過程若是過於頻繁會耗費掉大量的CPU運算資源. 因此一些代碼量小但運行頻繁的代碼若是採用帶參數宏來實現會提升代碼的運行效率.
函數宏的參數是固定的狀況
函數宏的定義採用這樣的方式: #define name( args ) tokens
其中的args和tokens都是可選的. 它和對象宏定義上的區別在於宏名稱以後不帶括號.
注意, name以後的左括號(必須緊跟name, 之間不能有空格, 不然這就定義了一個對象宏, 它將被替換爲 以(開始的字符串. 但在調用函數宏時, name與(之間能夠有空格.
e.g.
#define mul(x,y) ((x)*(y))
注意, 函數宏以後的參數要用括號括起來, 看看這個例子:
e.g.
#define mul(x,y) x*y
"mul(1, 2+2);" 將被擴展爲: 1*2 + 2
一樣, 整個標記串也應該用括號引用起來:
e.g.
#define mul(x,y) (x)*(y)
sizeof mul(1,2.0) 將被擴展爲 sizeof 1 * 2.0
調用函數宏時候, 傳遞給它的參數能夠是函數的返回值, 也能夠是任何有意義的語句:
e.g.
mul (f(a,b), g(c,d));
e.g.
#define insert(stmt) stmt
insert ( a=1; b=2;) 至關於在代碼中加入 a=1; b=2 .
insert ( a=1, b=2;) 就有問題了: 預處理器會提示出錯: 函數宏的參數個數不匹配. 預處理器把","視爲參數間的分隔符.
insert ((a=1, b=2;)) 可解決上述問題.
在定義和調用函數宏時候, 要注意一些問題:
1, 咱們常常用{}來引用函數宏被定義的內容, 這就要注意調用這個函數宏時的";"問題.
example_3.7:
#define swap(x,y) { unsigned long _temp=x; x=y; y=_tmp}
若是這樣調用它: "swap(1,2);" 將被擴展爲: { unsigned long _temp=1; 1=2; 2=_tmp};
明顯後面的;是多餘的, 咱們應該這樣調用: swap(1,2)
雖然這樣的調用是正確的, 但它和C語法相悖, 可採用下面的方法來處理被{}括起來的內容:
#define swap(x,y) \
do { unsigned long _temp=x; x=y; y=_tmp} while (0)
swap(1,2); 將被替換爲:
do { unsigned long _temp=1; 1=2; 2=_tmp} while (0);
在Linux內核源代碼中對這種do-while(0)語句有這普遍的應用.
2, 有的函數宏是沒法用do-while(0)來實現的, 因此在調用時不能帶上";", 最好在調用後添加註釋說明.
eg_3.8:
#define incr(v, low, high) \
for ((v) = (low),; (v) <= (high); (v)++)
只能以這樣的形式被調用: incr(a, 1, 10) /* increase a form 1 to 10 */
函數宏中的參數包括可變參數列表的狀況
C99標準中新增了可變參數列表的內容. 不光是函數, 函數宏中也可使用可變參數列表.
#define name(args, ...) tokens
#define name(...) tokens
"..."表明可變參數列表, 若是它不是僅有的參數, 那麼它只能出如今參數列表的最後. 調用這樣的函數宏時, 傳遞給它的參數個數要很多於參數列表中參數的個數(多餘的參數被丟棄).
經過__VA_ARGS__來替換函數宏中的可變參數列表. 注意__VA_ARGS__只能用於函數宏中參數中包含有"..."的狀況.
e.g.
#ifdef DEBUG
#define my_printf(...) fprintf(stderr, __VA_ARGS__)
#else
#define my_printf(...) printf(__VA_ARGS__)
#endif
tokens中的__VA_ARGS__被替換爲函數宏定義中的"..."可變參數列表.
注意在使用#define時候的一些常見錯誤:
#define MAX = 100
#define MAX 100;
=, ; 的使用要值得注意. 再就是調用函數宏是要注意, 不要多給出";".
1. 關於定義宏的另一些問題
(1)宏能夠被屢次定義, 前提是這些定義必須是相同的。
這裏的"相同"要求前後定義中空白符出現的位置相同, 但具體的空白符類型或數量可不一樣, 好比原先的空格可替換爲多個其餘類型的空白符: 可爲tab, 註釋...
e.g.
#define NULL 0
#define NULL /* null pointer */ 0
上面的重定義是相同的, 但下面的重定義不一樣:
#define fun(x) x+1
#define fun(x) x + 1 或: #define fun(y) y+1
若是屢次定義時, 再次定義的宏內容是不一樣的, gcc會給出"NAME redefined"警告信息.
應該避免從新定義函數宏, 無論是在預處理命令中仍是C語句中, 最好對某個對象只有單一的定義. 在gcc中, 若宏出現了重定義, gcc會給出警告.
(2) 在gcc中, 可在命令行中指定對象宏的定義:
e.g.
$ gcc -Wall -DMAX=100 -o tmp tmp.c
至關於在tmp.c中添加" #define MAX 100".
那麼, 若是原先tmp.c中含有MAX宏的定義, 那麼再在gcc調用命令中使用-DMAX, 會出現什麼狀況呢?
---若-DMAX=1, 則正確編譯.
---若-DMAX的值被指定爲不爲1的值, 那麼gcc會給出MAX宏被重定義的警告, MAX的值仍爲1.
注意: 若在調用gcc的命令行中不顯示地給出對象宏的值, 那麼gcc賦予該宏默認值(1), 如: -DVAL == -DVAL=1
(3) #define所定義的宏的做用域
宏在定義以後才生效, 若宏定義被#undef取消, 則#undef以後該宏無效. 而且字符串中的宏不會被識別
e.g.
#define ONE 1
sum = ONE + TWO /* sum = 1 + TWO */
#define TWO 2
sum = ONE + TWO /* sum = 1 + 2 */
#undef ONE
sum = ONE + TWO /* sum = ONE + 2 */
char c[] = "TWO" /* c[] = "TWO", NOT "2"! */
(4) 宏的替換能夠是遞歸的, 因此能夠嵌套定義宏.
e.g.
# define ONE NUMBER_1
# define NUMBER_1 1
int a = ONE /* a = 1 */
2. #undef
#undef用來取消宏定義, 它與#define對立:
#undef name
如夠被取消的宏實際上沒有被#define所定義, 針對它的#undef並不會產生錯誤.
當一個宏定義被取消後, 能夠再度定義它.
3. #if, #elif, #else, #endif
#if, #elif, #else, #endif用於條件編譯:
#if 常量表達式1
語句...
#elif 常量表達式2
語句...
#elif 常量表達式3
語句...
...
#else
語句...
#endif
#if和#else分別至關於C語句中的if, else. 它們根據常量表達式的值來判別是否執行後面的語句. #elif至關於C中的else-if. 使用這些條件編譯命令能夠方便地實現對源代碼內容的控制.
else以後不帶常量表達式, 但若包含了常量表達式, gcc只是給出警告信息.
使用它們能夠提高代碼的可移植性---針對不一樣的平臺使用執行不一樣的語句. 也常常用於大段代碼註釋.
e.g.
#if 0
{
一大段代碼;
}
#endif
常量表達式能夠是包含宏, 算術運算, 邏輯運算等等的合法C常量表達式, 若是常量表達式爲一個未定義的宏, 那麼它的值被視爲0.
#if MACRO_NON_DEFINED == #if 0
在判斷某個宏是否被定義時, 應當避免使用#if, 由於該宏的值可能就是被定義爲0. 而應當使用下面介紹的#ifdef或#ifndef.
注意: #if, #elif, #else以後的宏只能是對象宏. 若是name爲名的宏未定義, 或者該宏是函數宏. 那麼在gcc中使用"-Wundef"選項會顯示宏未定義的警告信息.
4. #ifdef, #ifndef, defined.
#ifdef, #ifndef, defined用來測試某個宏是否被定義
#ifdef name 或 #ifndef name
它們常常用於避免頭文件的重複引用:
#ifndef __FILE_H__
#define __FILE_H__
#include "file.h"
#endif
defined(name): 若宏被定義,則返回1, 不然返回0.
它與#if, #elif, #else結合使用來判斷宏是否被定義, 乍一看好像它顯得多餘, 由於已經有了#ifdef和#ifndef. defined用於在一條判斷語句中聲明多個判別條件:
#if defined(VAX) && defined(UNIX) && !defined(DEBUG)
和#if, #elif, #else不一樣, #indef, #ifndef, defined測試的宏能夠是對象宏, 也能夠是函數宏. 在gcc中使用"-Wundef"選項不會顯示宏未定義的警告信息.
5. #include , #include_next
#include用於文件包含. 在#include 命令所在的行不能含有除註釋和空白符以外的其餘任何內容.
#include "headfile"
#include <headfile>
#include 預處理標記
前面兩種形式你們都很熟悉, "#include 預處理標記"中, 預處理標記會被預處理器進行替換, 替換的結果必須符合前兩種形式中的某一種.
實際上, 真正被添加的頭文件並不必定就是#include中所指定的文件. #include"headfile"包含的頭文件固然是同一個文件, 但#include <headfile>包包含的"系統頭文件"多是另外的文件. 但這不值得被注意. 感興趣的話能夠查看宏擴展後到底引入了哪些系統頭文件.
關於#include "headfile"和#include <headfile>的區別以及如何在gcc中包含頭文件的詳細信息, 參考本blog的GCC筆記.
相對於#include, 咱們對#include_next不太熟悉. #include_next僅用於特殊的場合. 它被用於頭文件中(#include既可用於頭文件中, 又可用於.c文件中)來包含其餘的頭文件. 並且包含頭文件的路徑比較特殊: 從當前頭文件所在目錄以後的目錄來搜索頭文件.
好比: 頭文件的搜索路徑一次爲A,B,C,D,E. #include_next所在的當前頭文件位於B目錄, 那麼#include_next使得預處理器從C,D,E目錄來搜索#include_next所指定的頭文件.
可參考cpp手冊進一步瞭解#include_next
6. 預約義 的 宏
標準C中定義了一些對象宏, 這些宏的名稱以"__"開頭和結尾, 而且都是大寫字符. 這些預約義宏能夠被#undef, 也能夠被重定義.
下面列出一些標準C中常見的預約義對象宏(其中也包含gcc本身定義的一些預約義宏:
__LINE__ 當前語句所在的行號, 以10進制整數標註.
__FILE__ 當前源文件的文件名, 以字符串常量標註.
__DATE__ 程序被編譯的日期, 以"Mmm dd yyyy"格式的字符串標註.
__TIME__ 程序被編譯的時間, 以"hh:mm:ss"格式的字符串標註, 該時間由asctime返回.
__STDC__ 若是當前編譯器符合ISO標準, 那麼該宏的值爲1
__STDC_VERSION__ 若是當前編譯器符合C89, 那麼它被定義爲199409L, 若是符合C99, 那麼被定義爲199901L.
我用gcc, 若是不指定-std=c99, 其餘狀況都給出__STDC_VERSION__未定義的錯誤信息, 咋回事呢?
__STDC_HOSTED__ 若是當前系統是"本地系統(hosted)", 那麼它被定義爲1. 本地系統表示當前系統擁有完整的標準C庫.
ANSI標準說明了五個預約義的宏名。它們是:
_LINE_ /* (兩個下劃線),對應%d*/
_FILE_ /* 對應%s */
__FUNCTION__ /* 對應%s */
_DATE_ /* 對應%s */
_TIME_ /* 對應%s */
gcc定義的預約義宏:
__OPTMIZE__ 若是編譯過程當中使用了優化, 那麼該宏被定義爲1.
__OPTMIZE_SIZE__ 同上, 但僅在優化是針對代碼大小而非速度時才被定義爲1.
__VERSION__ 顯示所用gcc的版本號.
可參考"GCC the complete reference".
要想看到gcc所定義的全部預約義宏, 能夠運行: $ cpp -dM /dev/null
7. #line
#line用來修改__LINE__和__FILE__.
e.g.
printf("line: %d, file: %s\n", __LINE__, __FILE__);
#line 100 "haha"
printf("line: %d, file: %s\n", __LINE__, __FILE__);
printf("line: %d, file: %s\n", __LINE__, __FILE__);
顯示:
line: 34, file: 1.c
line: 100, file: haha
line: 101, file: haha
8. #pragma 和 _Pragma
#pragma用編譯器用來添加新的預處理功能或者顯示一些編譯信息. #pragma的格式是各編譯器特定的, gcc的以下:
#pragma GCC name token(s)
#pragma以後有兩個部分: GCC和特定的pragma name. 下面分別介紹gcc中經常使用的.
(1) #pragma GCC dependency
dependency測試當前文件(既該語句所在的程序代碼)與指定文件(既#pragma語句最後列出的文件)的時間戳. 若是指定文件比當前文件新, 則給出警告信息.
e.g.
在demo.c中給出這樣一句:
#pragma GCC dependency "temp-file"
而後在demo.c所在的目錄新建一個更新的文件: $ touch temp-file, 編譯: $ gcc demo.c 會給出這樣的警告信息: warning: current file is older than temp-file
若是當前文件比指定的文件新, 則不給出任何警告信息.
還能夠在在#pragma中給添加自定義的警告信息.
e.g.
#pragma GCC dependency "temp-file" "demo.c needs to be updated!"
1.c:27:38: warning: extra tokens at end of #pragma directive
1.c:27:38: warning: current file is older than temp-file
注意: 後面新增的警告信息要用""引用起來, 不然gcc將給出警告信息.
(2) #pragma GCC poison token(s)
若源代碼中出現了#pragma中給出的token(s), 則編譯時顯示警告信息. 它通常用於在調用你不想使用的函數時候給出出錯信息.
e.g.
#pragma GCC poison scanf
scanf("%d", &a);
warning: extra tokens at end of #pragma directive
error: attempt to use poisoned "scanf"
注意, 若是調用了poison中給出的標記, 那麼編譯器會給出的是出錯信息. 關於第一條警告, 我還不知道怎麼避免, 用""將token(s)引用起來也不行.
(3) #pragma GCC system_header
從#pragma GCC system_header直到文件結束之間的代碼會被編譯器視爲系統頭文件之中的代碼. 系統頭文件中的代碼每每不能徹底遵循C標準, 因此頭文件之中的警告信息每每不顯示. (除非用 #warning顯式指明).
(這條#pragma語句還沒發現用什麼大的用處)
因爲#pragma不能用於宏擴展, 因此gcc還提供了_Pragma:
e.g.
#define PRAGMA_DEP #pragma GCC dependency "temp-file"
因爲預處理之進行一次宏擴展, 採用上面的方法會在編譯時引起錯誤, 要將#pragma語句定義成一個宏擴展, 應該使用下面的_Pragma語句:
#define PRAGMA_DEP _Pragma("GCC dependency \"temp-file\"")
注意, ()中包含的""引用以前引該加上\轉義字符.
9. #warning, #error
#warning, #error分別用於在編譯時顯示警告和錯誤信息, 格式以下:
#warning tokens
#error tokens
e.g.
#warning "some warning"
注意, #error 和 #warning 後的 token 要用""引用起來!
(在gcc中, 若是給出了warning, 編譯繼續進行, 但若給出了error, 則編譯中止. 若在命令行中指定了 -Werror, 即便只有警告信息, 也不編譯.
10. 經常使用的預處理命令
預處理命令由#(hash字符)開頭, 它獨佔一行, #以前只能是空白符. 以#開頭的語句就是預處理命令,不以#開頭的語句爲C中的代碼行。
經常使用的預處理命令以下:
#define 定義一個預處理宏
#undef 取消宏的定義
#include 包含文件命令
#include_next 與#include類似, 但它有着特殊的用途
#if 編譯預處理中的條件命令, 至關於C語法中的if語句
#ifdef 判斷某個宏是否被定義, 若已定義, 執行隨後的語句
#ifndef 與#ifdef相反, 判斷某個宏是否未被定義
#elif 若#if, #ifdef, #ifndef或前面的#elif條件不知足, 則執行#elif以後的語句, 至關於C語法中的else-if
#else 與#if, #ifdef, #ifndef對應, 若這些條件不知足, 則執行#else以後的語句, 至關於C語法中的else
#endif #if, #ifdef, #ifndef這些條件命令的結束標誌.
defined 與#if, #elif配合使用, 判斷某個宏是否被定義
#line 標誌該語句所在的行號
# 將宏參數替代爲以參數值爲內容的字符竄常量
## 將兩個相鄰的標記(token)鏈接爲一個單獨的標記
#pragma 說明編譯器信息
#warning 顯示編譯警告信息
#error 顯示編譯錯誤信息
2. #define使用中的常見問題解析
2.1 簡單宏定義使用中出現的問題
在簡單宏定義的使用中,當替換文本所表示的字符串爲一個表達式時,容易引發誤解和誤用。以下例:
#define N 2+2
void main()
{
int a=N*N;
printf(「%d」,a);
}
出現問題:
在此程序中存在着宏定義命令,宏N表明的字符串是2+2,在程序中有對宏N的使用,通常同窗在讀該程序時,容易產生的問題是先求解N爲2+2=4,而後在程序中計算a時使用乘法,即N*N=4*4=16,其實該題的結果爲8,爲何結果有這麼大的誤差?
問題解析:
宏展開是在預處理階段完成的,這個階段把替換文本只是看做一個字符串,並不會有任何的計算髮生,在展開時是在宏N出現的地方 只是簡單地使用串2+2來代替N,並不會增添任何的符號,因此對該程序展開後的結果是a=2+2*2+2,計算後=8,這就是宏替換的實質,如何寫程序才能完成結果爲16的運算呢?
解決辦法:
/*將宏定義寫成以下形式*/
#define N (2+2)
/*這樣就可替換成(2+2)*(2+2)=16*/
總結:把 宏體 和 全部的宏變量 都用 括號括起來
2.2 帶參數的宏定義出現的問題
在帶參數的宏定義的使用中,極易引發誤解。例如咱們須要作個宏替換能求任何數的平方,這就須要使用參數,以便在程序中用實際參數來替換宏定義中的參數。通常容易寫成以下形式:
#define area(x) x*x
/*這在使用中是很容易出現問題的,看以下的程序*/
void main()
{
int y = area(2+2);
printf(「%d」,y);
}
按理說給的參數是2+2,所得的結果應該爲4*4=16,可是錯了,由於該程序的實際結果爲8,仍然是沒能遵循純粹的簡單替換的規則,又是先計算再替換 了,在這道程序裏,2+2即爲area宏中的參數,應該由它來替換宏定義中的x,即替換成2+2*2+2=8了。那若是遵循(1)中的解決辦法,把2+2 括起來,即把宏體中的x括起來,是否能夠呢?#define area(x) (x)*(x),對於area(2+2),替換爲(2+2)*(2+2)=16,能夠解決,可是對於area(2+2)/area(2+2)又會怎麼樣呢,有的學生一看到這道題立刻給出結果,由於分子分母同樣,又錯了,仍是忘了遵循先替換再計算的規則了,這道題替換後會變爲 (2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除運算規則,結果爲16/4*4=4*4=16,那應該怎麼呢?解決方法是在整個宏體上再加一個括號,即#define area(x) ((x)*(x)),不要以爲這不必,沒有它,是不行的。
要想可以真正使用好宏定義,那麼在讀別人的程序時,必定要記住先將程序中對宏的使用所有替換成它所表明的字符串,不要自做主張地添加任何其餘符號,徹底展開後再進行相應的計算,就不會寫錯運行結果。
若是是本身編程使用宏替換,則在使用簡單宏定義時,當字符串中不僅一個符號時,加上括號表現出優先級,若是是帶參數的宏定義,則要給宏體中的每一個參數加上括號,並在整個宏體上再加一個括號。看到這裏,不由要問,用宏定義這麼麻煩,這麼容易出錯,可不能夠摒棄它, 那讓咱們來看一下在C語言中用宏定義的好處吧。
以下代碼:
#include <iostream.h>
#define product(x) x*x
int main()
{
int i=3;
int j,k;
j = product(i++);
cout<<"j="<<j<<endl;
cout<<"i="<<i<<endl;
k = product(++i);
cout<<"k="<<k<<endl;
cout<<"i="<<i<<endl;
return 0;
}
依次輸出結果:
j=9;i=5;k=49;i=7
3. 宏定義的優勢
(1) 方便程序的修改
使用簡單宏定義可用宏代替一個在程序中常用的常量,這樣在將該常量改變時,不用對整個程序進行修改,只修改宏定義的字符串便可,並且當常量比較長時, 咱們能夠用較短的有意義的標識符來寫程序,這樣更方便一些。咱們所說的常量改變不是在程序運行期間改變,而是在編程期間的修改,舉一個你們比較熟悉的例子,圓周率π是在數學上經常使用的一個值,有時咱們會用3.14來表示,有時也會用3.1415926等,這要看計算所須要的精度,若是咱們編制的一個程序中 要屢次使用它,那麼須要肯定一個數值,在本次運行中不改變,但也許後來發現程序所表現的精度有變化,須要改變它的值, 這就須要修改程序中全部的相關數值,這會給咱們帶來必定的不便,但若是使用宏定義,使用一個標識符來代替,則在修改時只修改宏定義便可,還能夠減小輸入 3.1415926這樣長的數值屢次的狀況,咱們能夠如此定義 #define pi 3.1415926,既減小了輸入又便於修改,何樂而不爲呢?
(2) 提升程序的運行效率
使用帶參數的宏定義可完成函數調用的功能,又能減小系統開銷,提升運行效率。正如C語言中所講,函數的使用可使程序更加模塊化,便於組織,並且可重複利用,但在發生函數調用時,須要保留調用函數的現場,以便子 函數執行結束後能返回繼續執行,一樣在子函數執行完後要恢復調用函數的現場,這都須要必定的時間,若是子函數執行的操做比較多,這種轉換時間開銷能夠忽 略,但若是子函數完成的功能比較少,甚至於只完成一點操做,如一個乘法語句的操做,則這部分轉換開銷就相對較大了,但使用帶參數的宏定義就不會出現這個問 題,由於它是在預處理階段即進行了宏展開,在執行時不須要轉換,即在當地執行。宏定義可完成簡單的操做,但複雜的操做仍是要由函數調用來完成,並且宏定義所佔用的目標代碼空間相對較大。因此在使用時要依據具體狀況來決定是否使用宏定義。
2. define中的三個特殊符號:#,##,#@ 和 do while
#define Conn(x,y) x##y
#define ToChar(x) #@x
#define ToString(x) #x
(1) x##y 表示什麼?表示x鏈接y。
##符號會鏈接兩個符號,從而產生新的符號(詞法層次),即 「##」是一種分隔鏈接方式,它的做用是先分隔,而後進行強制鏈接。 例如:
#define SIGN( x ) INT_##x
int SIGN( 1 ); 宏被展開後將成爲:int INT_1;
舉例說:
int n = Conn(123,456); /* 結果就是n=123456; */
char* str = Conn("asdf", "adf"); /* 結果就是 str = "asdfadf"; */
#define TYPE1(type,name) type name_##type##_type
#define TYPE2(type,name) type name##_##type##_type
TYPE1(int, c); 轉換爲:int name_int_type ; (由於##號將後面分爲 name_ 、type 、 _type三組,替換後強制鏈接)
TYPE2(int, d);轉換爲: int d_int_type ; (由於##號將後面分爲 name、_、type 、_type四組,替換後強制鏈接)
(2)再來看#@x,其實就是給x加上單引號,結果返回是一個const char。舉例說:
char a = ToChar(1);結果就是a='1';
作個越界試驗char a = ToChar(123);結果就錯了;
可是若是你的參數超過四個字符,編譯器就給給你報錯了!
error C2015: too many characters in constant :P
(3)最後看看#x,估計你也明白了,他是給x加雙引號。即 #符號把一個符號直接轉換爲字符串。
也就是 #是「字符串化」的意思,出如今宏定義中的#是把跟在後面的參數轉換成一個字符串
char* str = ToString(123132);就成了str="123132";
#define ERROR_LOG(module) fprintf(stderr,"error: "#module"\n")
ERROR_LOG("add"); 轉換爲 fprintf(stderr,"error: "add"\n");
ERROR_LOG(devied =0); 轉換爲 fprintf(stderr,"error: devied=0\n");
(4) 宏定義用 do{ }while(0)
複雜宏定義及do{}while(0)的使用
#define foo() do{}while(0)
採用這種方式是爲了防範在使用宏過程當中出現錯誤,主要有以下幾點:
(1)空的宏定義避免warning:
#define foo() do{}while(0)
(2)存在一個獨立的block,能夠用來進行變量定義,進行比較複雜的實現。
(3)若是出如今判斷語句事後的宏,這樣能夠保證做爲一個總體來是實現:
#define foo(x) \
action1(); \
action2();
在如下狀況下:
if(NULL == pPointer)
foo();
就會出現action1和action2不會同時被執行的狀況,而這顯然不是程序設計的目的。
(4)以上的第3種狀況用單獨的{}也能夠實現,可是爲何必定要一個do{}while(0)呢,看如下代碼:
#define switch(x,y) {int tmp; tmp="x";x=y;y=tmp;}
if(x>y)
switch(x,y);
else //error, parse error before else
otheraction();
在把宏引入代碼中,會多出一個分號,從而會報錯。使用do{….}while(0) 把它包裹起來,成爲一個獨立的語法單元,從而不會與上下文發生混淆。同時由於絕大多數的編譯器都可以識別do{…}while(0)這種無用的循環並進行優化,因此使用這種方法也不會致使程序的性能下降。
爲了看起來更清晰,這裏用一個簡單點的宏來演示:
#define SAFE_DELETE(p) do{ delete p; p = NULL} while(0)
假設這裏去掉do...while(0),
#define SAFE_DELETE(p) delete p; p = NULL;
那麼如下代碼:
if(NULL != p) SAFE_DELETE(p)
else ...do sth...
就有兩個問題:
1) 由於if分支後有兩個語句,else分支沒有對應的if,編譯失敗
2) 假設沒有else, SAFE_DELETE中的第二個語句不管if測試是否經過,會永遠執行。
你可能發現,爲了不這兩個問題,我不必定要用這個使人費解的do...while, 我直接用{}括起來就能夠了
#define SAFE_DELETE(p) { delete p; p = NULL;}
的確,這樣的話上面的問題是不存在了,可是我想對於C++程序員來說,在每一個語句後面加分號是一種約定俗成的習慣,這樣的話,如下代碼:
if(NULL != p) SAFE_DELETE(p);
else ...do sth...
其else分支就沒法經過編譯了(緣由同上),因此採用do...while(0)是作好的選擇了。也許你會說,咱們代碼的習慣是在每一個判斷後面加上{}, 就不會有這種問題了,也就不須要do...while了,如:
if(...)
{
}
else
{
}
現有一個例子:#define PROJECT_LOG(level,arg) \ dosomething();\ if (level <= PROJECT_LOG_get_level()) \ PROJECT_LOG_wrapper_##level(arg);
如今假設有如下應用,現有一個例子:
#define PROJECT_LOG(level,arg) \
dosomething();\
if (level <= PROJECT_LOG_get_level()) \
PROJECT_LOG_wrapper_##level(arg);
如今假設有如下應用:
if(L==1)
PROJECT_LOG(L,"AAA");
宏轉開爲:
if(L==1)
dosomething();
if (1 <= PROJECT_LOG_get_level())
PROJECT_LOG_wrapper_1("AAA"); ;
顯然if(L==1)只管到dosomething();然後面的
if (1 <= PROJECT_LOG_get_level())
PROJECT_LOG_wrapper_1("AAA"); ;
則成了獨立的語句。
假如使用do{}while(0)語句塊,進行宏定義:
#define PROJECT_LOG(level,arg)do{ \
dosomething();\
if (level <= PROJECT_LOG_get_level()) \
PROJECT_LOG_wrapper_##level(arg); \
}while(0)
上述應用轉開後爲:
if(L==1)
do{
dosomething();
if (1<= PROJECT_LOG_get_level())
PROJECT_LOG_wrapper_1("AAA");
}while(0);
這樣避免了意外的麻煩。OK如今明白了不少C程序中奇怪的do{}while(0)宏定義了吧
使用示例代碼:
#include <stdio.h>
#define PRINT1(a,b) \
{ \
printf("print a\n"); \
printf("print b\n"); \
}
#define PRINT2(a, b) \
do{ \
printf("print a\n"); \
printf("print b\n"); \
}while(0)
#define PRINT(a) \
do{\
printf("%s: %d\n",#a,a);\
printf("%d: %d\n",a,a);\
}while(0)
#define TYPE1(type,name) type name_##type##_type
#define TYPE2(type,name) type name##_##type##_type
#define ERROR_LOG(module) fprintf(stderr,"error: "#module"\n")
int main()
{
int a = 20;
int b = 19;
TYPE1(int, c);
ERROR_LOG("add");
name_int_type = a;
TYPE2(int, d);
d_int_type = a;
PRINT(a);
if(a > b)
{
PRINT1(a, b);
}
else
{
PRINT2(a, b);
}
return 0;
}
3. 經常使用的一些宏定義
1 防止一個頭文件被重複包含
#ifndef BODYDEF_H
#define BODYDEF_H
//頭文件內容
#endif
2 獲得指定地址上的一個字節或字
#define MEM_B( x ) ( *( (byte *) (x) ) )
#define MEM_W( x ) ( *( (word *) (x) ) )
用法以下:
#include <iostream>
#include <windows.h>
#define MEM_B(x) (*((byte*)(x)))
#define MEM_W(x) (*((WORD*)(x)))
int main()
{
int bTest = 0x123456;
byte m = MEM_B((&bTest));/*m=0x56*/
int n = MEM_W((&bTest));/*n=0x3456*/
return 0;
}
3 獲得一個field在結構體(struct)中的偏移量
#define OFFSETOF( type, field ) ( (size_t) &(( type *) 0)-> field )
請參考文章:詳解寫宏定義:獲得一個field在結構體(struct type)中的偏移量。
4 獲得一個結構體中field所佔用的字節數
#define FSIZ( type, field ) sizeof( ((type *) 0)->field )
5 獲得一個變量的地址(word寬度)
#define B_PTR( var ) ( (byte *) (void *) &(var) )
#define W_PTR( var ) ( (word *) (void *) &(var) )
6 將一個字母轉換爲大寫
#define UPCASE( c ) ( ((c) >= ''a'' && (c) <= ''z'') ? ((c) - 0x20) : (c) )
7 判斷字符是否是10進值的數字
#define DECCHK( c ) ((c) >= ''0'' && (c) <= ''9'')
8 判斷字符是否是16進值的數字
#define HEXCHK( c ) ( ((c) >= ''0'' && (c) <= ''9'') ||((c) >= ''A'' && (c) <= ''F'') ||((c) >= ''a'' && (c) <= ''f'') )
9 防止溢出的一個方法
#define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val))
10 返回數組元素的個數
#define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
4. 宏的使用場景
1. 打印錯誤信息
若是程序的執行必需要求某個宏被定義,在檢查到宏沒有被定義是可使用#error,#warning打印錯誤(警告)信息,如:
#ifndef __unix__
#error "This section will only work on UNIX systems"
#endif
只有__unix__宏被定義,程序才能被正常編譯。
2. 方便調試
__FILE, __LINE, __FUNCTION是由編譯器預約義的宏,其分別表明當前代碼所在的文件名,行號,以及函數名。能夠在代碼中加入以下語句來跟蹤代碼的執行狀況:
if(err)
{
printf("%s(%d)-%s\n",__FILE__,__LINE__,__FUNCTION__);
}
3. C/C++的混合編程
函數int foo(int a, int b);
在C語言的該函數在編譯器編譯後在庫中的名字爲_foo,而C++中該函數被編譯後在庫中的名字爲_foo_int_int(爲實現函數重載所作的改變)。若是C++中須要使用C編譯後的庫函數,則會提示找不到函數,由於符號名不匹配。C++中使用extern 「C」解決該問題,說明要引用的函數是由C編譯的,應該按照C的命名方式去查找符號。
若是foo是C編譯的庫,若是要在C++中使用foo,須要加以下聲明,其中__cplusplus是c++編譯器預約義的宏,說明該文件是被C++編譯器編譯,此時引用C的庫函數,就須要加extern 「C」。
#ifdef __cplusplus
extern 「C」 {
#endif
extern int foo(int a, int b);
#ifdef __cplusplus
}
#endif
4. 使用宏打印 Log 使用示例
#include <stdio.h>
typedef enum
{
ERROR_ONE, // 0
ERROR_TWO,
ERROR_THREE,
ERROR_END
}E_ERROR_CODE;
unsigned long g_error_statistics[ERROR_END] = {0};
/* LOG 打印, # 直接常亮字符串替換 */
#define LOG_PRINT(ERROR_CODE) \
do { \
g_error_statistics[ERROR_CODE]++; \
printf("[%s : %d], error is %s\n", __FILE__, __LINE__, #ERROR_CODE); \
} while (0)
/* ERROR 公共前綴,傳參時省略的寫法, ## 直接展開拼接 */
#define LOG_PRINT_2(CODE) \
do { \
g_error_statistics[ERROR_ ## CODE]++; \
printf("[%s : %d], error is %s\n", __FILE__, __LINE__, "ERROR_" #CODE); \
} while (0)
int main()
{
LOG_PRINT(ERROR_TWO);
LOG_PRINT_2(ONE);
for (unsigned int i = 0; i < ERROR_END; ++i) {
printf("error %u statistics is %lu \n", i, g_error_statistics[i]);
}
return 0;
}
寫文件記錄log
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
int write_log (FILE* pFile, const char *format, ...) {
va_list arg;
int done;
va_start (arg, format);
//done = vfprintf (stdout, format, arg);
time_t time_log = time(NULL);
struct tm* tm_log = localtime(&time_log);
fprintf(pFile, "%04d-%02d-%02d %02d:%02d:%02d ", tm_log->tm_year + 1900, tm_log->tm_mon + 1, tm_log->tm_mday, tm_log->tm_hour, tm_log->tm_min, tm_log->tm_sec);
done = vfprintf (pFile, format, arg);
va_end (arg);
fflush(pFile);
return done;
}
int main() {
FILE* pFile = fopen("123.txt", "a");
write_log(pFile, "%s %d %f\n", "is running", 10, 55.55);
fclose(pFile);
return 0;
}
/*
編譯運行:
gcc log.c -o log
./log
返回結果:cat 123.txt
2016-12-13 13:10:02 is running 10 55.550000
2016-12-13 13:10:04 is running 10 55.550000
2016-12-13 13:10:04 is running 10 55.550000
*/
使用示例代碼:
#include<stdio.h>
#include <time.h>
#include <windows.h>
#include <string.h>
#include <stdarg.h>
#define DBG_WRITE(fmt,args) DBG_Write_Log(strrchr(__FILE__, '\\')+1, __LINE__, fmt,##args);
void DBG_Write_Log(char* filename, int line, char* fmt, ...)
{
FILE* fp;
va_list argp;
char* para;
char logbuf[512];
char timeStr[20];
time_t tt;
struct tm *local;
tt = time(NULL);
local = localtime(&tt);
strftime(timeStr, 20, "%Y-%m-%d %H:%M:%S", local);
sprintf(logbuf, "[%s] %s[%d]", timeStr, filename, line);
va_start(argp, fmt);
vsprintf(logbuf+strlen(logbuf), fmt, argp);
va_end(argp);
fprintf(fp, logbuf);
fclose(fp);
printf(logbuf);
}
void main()
{
DBG_WRITE("test log [%d]system[%s][%d]\n", 1234,"add by test", 5);
DBG_Write_Log(strrchr(__FILE__,'\\')+1, __LINE__, "%s %d\n", "add by test", 5);
}
幾種 log 打印 printf 函數 的 宏定義 示例代碼
#include <stdio.h>
#define lU_DEBUG_PREFIX "##########"
#define LU_DEBUG_CMD 0x01
#define LU_DEBUG_DATA 0x02
#define LU_DEBUG_ERROR 0x04
#define LU_PRINTF_cmd(msg...) do{if(g_lu_debugs_level & LU_DEBUG_CMD)printf(lU_DEBUG_PREFIX msg);}while(0)
#define LU_PRINTF_data(msg...) do{if(g_lu_debugs_level & LU_DEBUG_DATA)printf(lU_DEBUG_PREFIX msg);}while(0)
#define LU_PRINTF_error(msg...) do{if(g_lu_debugs_level & LU_DEBUG_ERROR)printf(lU_DEBUG_PREFIX msg);}while(0)
#define lu_printf(level, msg...) LU_PRINTF_##level(msg)
#define lu_printf2(...) printf(__VA_ARGS__)
#define lu_printf3(...) lu_printf(__VA_ARGS__)
static int lu_printf4_format(int prio, const char *fmt, ...);
#define lu_printf4(prio, fmt...) lu_printf4_format(prio, fmt)
int g_lu_debugs_level; //控制打印等級的全局開關
//lu_printf 相似內核的分等級打印宏,根據g_lu_debugs_level和輸入的第一個標號名來決定該句打印是否輸出。
//lu_printf3 等同於 lu_printf
//lu_printf2 等同於 printf
//lu_printf4 等同於 lu_printf4_format,做用是把輸入的第一個整型參數用<val>的格式打印出來
int main(int argc, char *argv[])
{
g_lu_debugs_level |= LU_DEBUG_CMD | LU_DEBUG_DATA | LU_DEBUG_ERROR;
printf("g_lu_debugs_level = %p\n", g_lu_debugs_level);
lu_printf(cmd,"this is cmd\n");
lu_printf(data,"this is data\n");
lu_printf(error,"this is error\n");
g_lu_debugs_level &= ~(LU_DEBUG_CMD | LU_DEBUG_DATA);
printf("g_lu_debugs_level = %p\n", g_lu_debugs_level);
lu_printf(cmd,"this is cmd\n");
lu_printf(data,"this is data\n");
lu_printf(error,"this is error\n");
lu_printf2("aa%d,%s,%dbbbbb\n", 20, "eeeeeee", 100);
g_lu_debugs_level |= LU_DEBUG_CMD | LU_DEBUG_DATA | LU_DEBUG_ERROR;
printf("g_lu_debugs_level = %p\n", g_lu_debugs_level);
lu_printf3(cmd,"this is cmd \n");
lu_printf3(data,"this is data\n");
lu_printf3(error,"this is error\n");
lu_printf4(0,"luther %s ,%d ,%d\n", "gliethttp", 1, 2);
return 0;
}
#include <stdarg.h>
static int lu_printf4_format(int prio, const char *fmt, ...)
{
#define LOG_BUF_SIZE (4096)
va_list ap;
char buf[LOG_BUF_SIZE];
va_start(ap, fmt);
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
va_end(ap);
printf("<%d>: %s", prio, buf);
printf("------------------------\n");
printf(buf);
}
#define ENTER() LOGD("enter into %s", __FUNCTION__)
#define LOGD(...) ((void)LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define LOG(priority, tag, ...) \
LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#define LOG_PRI(priority, tag, ...) \
android_printLog(priority, tag, __VA_ARGS__)
#define android_printLog(prio, tag, fmt...) \
__android_log_print(prio, tag, fmt)
使用示例代碼:
#include <stdio.h>
/* Define Log print macro */
#define MyLog(DebugLevel, format, ...) \
do{ \
switch (DebugLevel) \
{ \
case 1: \
printf(format, ##__VA_ARGS__); \
break; \
case 2: \
printf("Function: "__FUNCTION__", Line: %d, ---> "format"", __LINE__, ##__VA_ARGS__); \
break; \
case 3: \
printf("File: "__FILE__", Function: "__FUNCTION__", Line: %d, ---> "format"", __LINE__, ##__VA_ARGS__); \
break; \
default: \
break; \
} \
}while(0)
int main(void)
{
MyLog(1, "Simple Log print!\r\n");
MyLog(2, "Satndard Log display!\r\n");
MyLog(3, "Detail Log view!\r\n");
MyLog(1, "If debug level is not equal 1,2 or 3 that log is invisible, such as next line :\r\n");
MyLog(6, "I am invisible log!\r\n");
MyLog(1, "Now, I think you have understood how to use MyLog macro.\r\n");
return 0;
}
使用示例代碼:
#include <stdio.h>
#define LOG_DEBUG "DEBUG"
#define LOG_TRACE "TRACE"
#define LOG_ERROR "ERROR"
#define LOG_INFO "INFOR"
#define LOG_CRIT "CRTCL"
#define LOG(level, format, ...) \
do { \
fprintf(stderr, "[%s|%s@%s,%d] " format "\n", \
level, __func__, __FILE__, __LINE__, ##__VA_ARGS__ ); \
} while (0)
int main()
{
LOG(LOG_DEBUG, "a=%d", 10);
return 0;
}
// 或者
/*
#define DBG(format, ...) fprintf(stderr, "[%s|%s@%s,%d] " format "\n", APP_NAME, __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__ );
*/
使用示例代碼:
#define LOG(level, format, ...) \
do { \
fprintf(stderr, "[%s|%s@%s,%d] " format "/n", \
level, __func__, __FILE__, __LINE__, ##__VA_ARGS__ ); \
} while (0)
使用示例代碼:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define LOG(fmt, ...) do \ { \ printf("[%s][%s][%s:%d] %s:"fmt"\n", __DATE__, __TIME__, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \ }while(0) int main(void) { char* str = "this is test string"; int num = 100; LOG("test string : %s . test num: %d", str, num); return 0; }