c語言可變參數宏
① c語言中可變參數宏的va_start(ap, v)
我把你的提問分為3個問題:
1、為什麼printf("%s", ap);輸出不了?
2、va_start(ap, v)的定義中為什麼使用二級指針?
3、va_arg(ap,t) 的定義中為什麼用*(t *),它的作用是?
在解釋之前,先確認一個小問題:
在C語言中,指針這種類型的大小實際上一樣的,我的意思是說無論是char *a,還是int *a,或者是char **a,a這個指針變數所佔用的內存空間是一樣的(都是sizeof(a),究竟是等於4,還是8取決於CPU的位數)
先回答第一個問題:
你應該知道va_list的定義:typedef char * va_list;
也就是說ap可以理解為一個char *類型的變數,va_start(ap,c)這個執行之後,ap確實指向了可變參數列表中的第一個參數,注意【是ap這個指針指向了第一個參數】,而如果你的第一個參數是一個字元串(C語言中也就意味著是一個char*的變數),這樣的話,ap這個指針就指向了一個char*類型的指針變數,【指向指針的指針變數是二級指針變數】這個我就不用多說了吧,所以printf("%s", ap);是無法輸出的,而修改為printf("%s", *(char **)ap);應該就可以輸出了!
然後是第二個問題:
這里先說一下函數調用過程中參數傳遞的問題:
【 函數參數是以數據結構:棧的形式存取,從右至左入棧。
首先是參數的內存存放格式:參數存放在內存的堆棧段中,在執行函數的時候,從最後一個開始入棧。因此棧底高地址,棧頂低地址,舉個例子如下:
void func(int x, char *y, char z);
那麼,調用函數的時候,實參 char z 先進棧,然後是 char *y,最後是 int x,因此在內存中變數的存放次序是 x->y->z,因此,從理論上說,我們只要探測到任意一個變數的地址,並且知道其他變數的類型,通過指針移位運算,則總可以順藤摸瓜找到其他的輸入變數。】
注意,x,y,z這幾個變數是存放到堆棧中的,所以我們需要獲得的不是y這個變數本身,而是它在堆棧中的地址,而ap這個指針變數就是保存著堆棧中函數入參的地址的,所以在va_start(ap, v)的定義中要使用&v,而不管v變數本身是什麼類型的(哪怕v是一個指針變數,甚至是二級指針)&v都表示一個地址,所以可以強制轉換為va_list類型(也就是char *)。
第三個問題:
要睡覺了,先自己想吧,如果還不明白,就留言追問吧。
② c語言如何封裝一個帶有可變參數的方法
需要借用C語言的VA_LIST宏定義,及相關操作來實現可變參數。
VA_LIST所在頭文件:#include <stdarg.h>,用法如下:
(1)首先在函數里定義一具VA_LIST型的變數,這個變數是指向參數的指針;
(2)然後用VA_START宏初始化剛定義的VA_LIST變數;
(3)然後用VA_ARG返回可變的參數,VA_ARG的第二個參數是你要返回的參數的類型(如果函數有多個可變參數的,依次調用VA_ARG獲取各個參數);
(4)最後用VA_END宏結束可變參數的獲取。
以下是一個自定義列印介面的實現:
intmy_printf(constchar*fmt,...)//...表示參數可變
{
va_listargs;//定義va_list
staticchargc_PrintfOutBuff[1000];
va_start(args,fmt);//初始化
vsnprintf((char*)gc_PrintfOutBuff,1000,(char*)fmt,args);//這里沒有使用VA_ARG取回單個變數,而是借用vsnprinf一次性讀取。
va_end(args);//結束獲取
puts("%s",(constchar*)gc_PrintfOutBuff);//使用。
return0;
}
③ C語言頭文件ansidecl.h中定義的宏VA_OPEN和VA_FIXEDARG和VA_CLOSE表示什麼
這個是用於處理可變參數的,其實C標准只定義了
va_start
,
va_end
,
va_
,
va_arg
這幾個宏,而
va_list
是一個存儲可變參數信息的對象。
va_start
用於初始化可變參數列表
va_
將參數列表拷貝一份,而不直接使用源參數列表,當然,這個拷貝參數列表中的參數信息和源列表是一樣的。
var_arg
抽取參數列表中的下一個參數
var_end
用於結束參數處理(如果函數調用了va_start,在函數返回之前應該調用va_end結束本次處理)。
egg.
void
printInt(int
num,
...)
{
va_list
ap;//用於存儲可變參數的信息的列表
var_start(ap,num);//初始化參數列表,你要告訴var_start,最後一個已命名的參數是哪一個(其實就是
...
前面的那個),這里就是num,因為va_start內部要找到可變參數的首地址,所以要知道從哪個地址開始是可變參數的地址,這里傳遞num,也就是從地址&num+1開始作為可變參數的地址。可變參數信息會存在ap這個list中
for(int
i=0;i<num;i++)
{
int
val=var_arg(ap,int);//使用va_arg宏從參數列表ap中取出一個參數,由於宏並不知道我們傳遞的參數的類型,所以它無法返回,所以你要告訴它參數是什麼類型的,然後它就返回一個這種類型的參數值給你,參數列表內部維護一個指針,用於指示當前處理到哪個地址,調用va_arg後指針會移動到下一個參數的位置,那麼它怎麼知道下一個參數在哪裡?你告訴它參數類型是什麼,它就會向後移動這個參數類型所佔的位元組數,例如你從裡面拿了一個int,那麼它就+4,又從裡面拿了一個char,它就+1。每次調用完va_arg後,指針都是指向下一個待處理的參數的地址。
printf("The
value
is:
%d
",val);
}
va_end(ap);//函數返回前,記得調用va_end這個宏來結束參數的處理,這個很重要,不要忘記。
}
printInt(3,24,36,71);//
調用printInt函數
④ C語言 可變參數宏的問題
這個問題可以這樣考慮:
你在write_log()函數里調用了vfprintf()函數,其實這個vfprintf()就是一個可以接受你從上層函數傳下來的可變參數串的函數。
你現在要在
log_info()
函數下調用
write_log()
函數,並想把可變參數串傳給它,你只要參考
vfprintf()
的函數定義來定義
write_log()
函數就可以。
c語言中
vfprintf()
函數的定義是:
int
vfprintf(file
*stream,
const
char
*format,
va_list
ap);
不知你是否能受到啟發。
⑤ C語言可變參數傳遞的問題
方案是有的,但是需要用到匯編代碼。而且,不同的CPU架構,代碼寫起來還不一樣。
大概的方法,通過解析fmt的內容,找出其中的%d、%u等格式控制符,根據格式控制符,提取出後面的各個參數。參數如何提取,需要用到匯編代碼,而且不同的CPU架構,代碼實現是有差異的。
不過,在下覺得,您可能並不需要真正實現這樣的函數。或許將fun1定義為類似如下的一個宏,就能解決你的問題了吧。
#define fun1(a,b,fmt, args...) \
do \
{ \
if (a>b) \
{ \
fun2(fmt, ##args); \
} \
else \
{ \
fun2(fmt, ##args); \
} \
} while (0)