#ifdef DEBUG
#define DBG(...) fprintf(stderr, " DBG(%s, %s(), %d): ", __FILE__, __FUNCTION__, __LINE__); fprintf(stderr, __VA_ARGS__)
#else
#define DBG(...)
#endif
int main()
{
DBG("hello\n");
}
/*C語言預處理器定義的一些宏
__LINE__ 當時行號(預處理器正在執行的那一時刻),十進制數
__FUNCTION__ 當時函數,字符串
__FILE__當時文件,字符串
__DATE__當時日期,字符串
__TIME__當時時間,字符串
等
*/
如果在gcc ,use -D define DEBUG micro
[root@localhost test]# gcc -o test1 test1.c -DDEBUG
[root@localhost test]# ./test1
DBG(test1.c, main(), 11): hello // 這行爲打印的內容,後面的hello爲程序DBG("hello\n");中的hello。
#、##、 …和_ VA_ARGS _
1.#
假如希望在字符串中包含宏參數,ANSI C允許這樣作,在類函數宏的替換部分,#符號用作一個預處理運算符,它可以把語言符號轉化程字符串。例如,如果x是一個宏參量,那麼#x可以把參數名轉化成相應的字符串。該過程稱爲字符串化(stringizing).簡單理解就是:#n把一個宏參數n變成字符串n
#incldue <stdio.h>
#define PSQR(x) printf("the square of" #x "is %d.\n",(x)*(x))
int main(void)
{
int y =4;
PSQR(y);
PSQR(2+4);
return 0;
}
輸出結果:
the square of y is 16.
the square of 2+4 is 36.
第一次調用宏時使用“y”代替#x;第二次調用把2+4轉成字符串“2+4”。
2.##
##運算符可以用於類函數宏的替換部分。另外,##還可以用於類對象宏的替換部分。這個運算符把兩個語言符號組合成單個語言符號。簡單理解就是a##b轉化爲ab,把a和b連在一起了
例如:
#define XNAME(n) x##n
這樣宏調用:
XNAME(4)
展開後:
x4
程序:
#include <stdio.h>
#define XNAME(n) x##n
#define PXN(n) printf("x"#n" = %d\n",x##n)
int main(void)
{
int XNAME(1)=12;//int x1=12;
PXN(1);//printf("x1 = %d\n", x1);
return 0;
}
輸出結果:
x1=12
3.可變參數宏 …和_ VA_ARGS _
VA_ARGS 是一個可變參數的宏,很少人知道這個宏,這個可變參數的宏是新的C99規範中新增的,目前似乎只有gcc支持(VC6.0的編譯器不支持)。
實現思想就是宏定義中參數列表的最後一個參數爲省略號(也就是三個點)。這樣預定義宏_ VA_ARGS _就可以被用在替換部分中,替換省略號所代表的字符串。比如:
#define PR(...) printf(__VA_ARGS__)
int main()
{
int wt=1,sp=2;
PR("hello\n");
PR("weight = %d, shipping = %d",wt,sp);
return 0;
}
輸出結果:
hello
weight = 1, shipping = 2
省略號只能代替最後面的宏參數。
#define W(x,…,y)錯誤!
freertos實例:
#define osThreadDef(name, thread, priority, instances, stacksz) \
const osThreadDef_t os_thread_def_##name = \
{ #name, (thread), (priority), (instances), (stacksz)}
上訴宏定義中出現了##和# 符號,其中
##是一個連接符號,用於把參數連在一起;
#是“字符串化”的意思。出現在宏定義中的#是把跟在後面的參數轉換成一個字符串。
例如:
#define paster( n ) printf( "token" #n" = %d\n ", token##n )
所以paster(9);就是相當於 printf("token 9 = %d\n",token9);
那麼osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128);等價於下面的定義
#defineosThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128) \
const osThreadDef_t os_thread_def_Task_LED1 = { Task_LED1,Func_LED1,osPriorityNormal, 0, 128}
linux驅動實例:
module_platform_driver(axi_i2s_driver);
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
那麼對module_platform_driver(axi_i2s_driver);進行第一步展開,即:
module_driver(axi_i2s_driver, platform_driver_register,platform_driver_unregister)
繼續展開下一層,…並沒有實際參數,對應的需要刪除##VA_ARGS即:
#define module_driver(axi_i2s_driver, platform_driver_register,platform_driver_unregister) \
static int __init axi_i2s_driver_init(void) \
{ \
return platform_driver_register(&(axi_i2s_driver) ); \
} \
module_init(axi_i2s_driver_init); \
static void __exit axi_i2s_driver_exit(void) \
{ \
platform_driver_unregister(&(axi_i2s_driver) ); \
} \
module_exit(axi_i2s_driver_exit);
module_platform_driver()宏的作用就是定義指定名稱的平臺設備驅動註冊函數和平臺設備驅動註銷函數,並且在函數體內分別通過platform_driver_register()函數和platform_driver_unregister()函數註冊和註銷該平臺設備驅動。
VA_LIST 是在C語言中解決變參問題的一組宏,變參問題是指參數的個數不定,可以是傳入一個參數也可以是多個;可變參數中的每個參數的類型可以不同,也可以相同;可變參數的每個參數並沒有實際的名稱與之相對應,用起來是很靈活。
va_list 用法示例
#include <stdarg.h>
int AveInt(int,...);
void main()
{
printf("%d/t",AveInt(2,2,3));
printf("%d/t",AveInt(4,2,4,6,8));
return;
}
int AveInt(int v,...)
{
int ReturnValue=0;
int i=v;
va_list ap ;
va_start(ap,v);
while(i>0)
{
ReturnValue+=va_arg(ap,int) ;
i--;
}
va_end(ap);
return ReturnValue/=v;
}
VA_LIST的用法:
(1)首先在函數裏定義一具VA_LIST型的變量,這個變量是指向參數的指針;
(2)然後用VA_START宏初始化變量剛定義的VA_LIST變量;
(3)然後用VA_ARG返回可變的參數,VA_ARG的第二個參數是你要返回的參數的類型(如果函數有多個可變參數的,依次調用VA_ARG獲取各個參數);
(4)最後用VA_END宏結束可變參數的獲取。
上面是va_list的具體用法,下面講解一下va_list各個語句含義(如上示例黑體部分)和va_list的實現。
可變參數是由宏實現的,但是由於硬件平臺的不同,編譯器的不同,宏的定義也不相同,下面是VC6.0中x86平臺的定義 :
typedef char * va_list; // TC中定義爲void*
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //爲了滿足需要內存對齊的系統
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //ap指向第一個變參的位置,即將第一個變參的地址賦予ap
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) /*獲取變參的具體內容,t爲變參的類型,如有多個參數,則通過移動ap的指針來獲得變參的地址,從而獲得內容*/
#define va_end(ap) ( ap = (va_list)0 ) //清空va_list,即結束變參的獲取
va_list ap ; 定義一個va_list變量ap
va_start(ap,v) ;執行ap = (va_list)&v + _INTSIZEOF(v),ap指向參數v之後的那個參數的地址,即 ap指向第一個可變參數在堆棧的地址。
va_arg(ap,t) , ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )取出當前ap指針所指的值,並使ap指向下一個參數。 ap+= sizeof(t類型),讓ap指向下一個參數的地址。然後返回ap-sizeof(t類型)的t類型指針,這正是第一個可變參數在堆棧裏的地址。然後 用取得這個地址的內容。
va_end(ap) ; 清空va_list ap。
使用VA_LIST應該注意的問題:
(1)因爲va_start, va_arg, va_end等定義成宏,所以它顯得很愚蠢,可變參數的類型和個數完全在該函數中由程序代碼控制,它並不能智能地識別不同參數的個數和類型. 也就是說,你想實現智能識別可變參數的話是要通過在自己的程序裏作判斷來實現的.
(2)另外有一個問題,因爲編譯器對可變參數的函數的原型檢查不夠嚴格,對編程查錯不利.不利於我們寫出高質量的代碼。
(3)由於參數的地址用於VA_START宏,所以參數不能聲明爲寄存器變量,或作爲函數或數組類型。