代碼“va_start(ap,fmt)”是什么意思?

代碼“va_start(ap,fmt)”是什么意思?

VA_LIST 是在C語言中解決變參問題的一組宏,在<stdarg.h>頭文件下。

VA_LIST的用法:
首先在函數(shù)里定義一具VA_LIST型的變量,這個(gè)變量是指向參數(shù)的指針,然后用VA_START宏初始化變量剛定義的VA_LIST變量,這個(gè)宏的第二個(gè)參數(shù)是**個(gè)可變參數(shù)的前一個(gè)參數(shù),是一個(gè)固定的參數(shù)。

然后用VA_ARG返回可變的參數(shù),VA_ARG的第二個(gè)參數(shù)是你要返回的參數(shù)的類型。

**用VA_END宏結(jié)束可變參數(shù)的獲取。然后你就可以在函數(shù)里使用第二個(gè)參數(shù)了。如果函數(shù)有多個(gè)可變參數(shù)的,依次調(diào)用VA_ARG獲取各個(gè)參數(shù)。
VA_LIST在編譯器中的處理:
在運(yùn)行VA_START(ap,v)以后,ap指向**個(gè)可變參數(shù)在堆棧的地址。

VA_ARG()取得類型t的可變參數(shù)值,在這步操作中首先apt = sizeof(t類型),讓ap指向下一個(gè)參數(shù)的地址。然后返回ap-sizeof(t類型)的t類型*指針,這正是 **個(gè)可變參數(shù)在堆棧里的地址。然后用*取得這個(gè)地址的內(nèi)容。

VA_END(),X86平臺(tái)定義為ap = ((char*)0),使ap不再指向堆棧,而是跟NULL一樣,有些直接定義為((void*)0),這樣編譯器不會(huì)為VA_END產(chǎn)生代碼,例如gcc在Linux的X86平臺(tái)就是這樣定義的。
要注意的是:由于參數(shù)的地址用于VA_START宏,所以參數(shù)不能聲明為寄存器變量,或作為函數(shù)或數(shù)組類型。

【求解釋va_list、va_start、va_arg、va_end】

這些都C定義的一些宏獲取省略號(hào)指定的參數(shù): 在函數(shù)體中聲明一個(gè)va_list,然后用va_start函數(shù)來獲取參數(shù)列表中的參數(shù),使用完畢后調(diào)用va_end()結(jié)束。va_start使arg_ptr指向**個(gè)可選參數(shù)百科。

va_arg返回參數(shù)列表中的當(dāng)前參數(shù)并使arg_ptr指向參數(shù)列表中的下一個(gè)參數(shù)。

va_end把a(bǔ)rg_ptr指針清為NULL。函數(shù)體內(nèi)可以多次遍歷這些參數(shù),但是都必須以va_start開始,并以va_end結(jié)尾。

C語言中可變參數(shù)宏的va_start(ap, v)

我把你的提問分為3個(gè)問題:1、為什么printf(\”%s\”, ap);輸出不了?2、va_start(ap, v)的定義中為什么使用二級(jí)指針?3、va_arg(ap,t) 的定義中為什么用*(t *),它的作用是?在解釋之前,先確認(rèn)一個(gè)小問題:在C語言中,指針這種類型的大小實(shí)際上一樣的,我的意思是說無論是char *a,還是int *a,或者是char **a,a這個(gè)指針變量所占用的內(nèi)存空間是一樣的(都是sizeof(a),究竟是等于4,還是8取決于CPU的位數(shù))先回答**個(gè)問題:你應(yīng)該知道va_list的定義:typedef char * va_list;也就是說ap可以理解為一個(gè)char *類型的變量,va_start(ap,c)這個(gè)執(zhí)行之后,ap確實(shí)指向了可變參數(shù)列表中的**個(gè)參數(shù),注意【是ap這個(gè)指針指向了**個(gè)參數(shù)】,而如果你的**個(gè)參數(shù)是一個(gè)字符串(C語言中也就意味著是一個(gè)char*的變量),這樣的話,ap這個(gè)指針就指向了一個(gè)char*類型的指針變量,【指向指針的指針變量是二級(jí)指針變量】這個(gè)我就不用多說了吧,所以printf(\”%s\”, ap);是無法輸出的,而修改為printf(\”%s\”, *(char **)ap);應(yīng)該就可以輸出了!然后是第二個(gè)問題:這里先說一下函數(shù)調(diào)用過程中參數(shù)傳遞的問題: 【 函數(shù)參數(shù)是以數(shù)據(jù)結(jié)構(gòu):棧的形式存取,從右至左入棧。首先是參數(shù)的內(nèi)存存放格式:參數(shù)存放在內(nèi)存的堆棧段中,在執(zhí)行函數(shù)的時(shí)候,從**一個(gè)開始入棧。

因此棧底高地址,棧頂?shù)偷刂?,舉個(gè)例子如下:void func(int x, char *y, char z);那么,調(diào)用函數(shù)的時(shí)候,實(shí)參 char z 先進(jìn)棧,然后是 char *y,**是 int x,因此在內(nèi)存中變量的存放次序是 x->y->z,因此,從理論上說,我們只要探測(cè)到任意一個(gè)變量的地址,并且知道其他變量的類型,通過指針移位運(yùn)算,則總可以順藤摸瓜找到其他的輸入變量。

】注意,x,y,z這幾個(gè)變量是存放到堆棧中的,所以我們需要獲得的不是y這個(gè)變量本身,而是它在堆棧中的地址,而ap這個(gè)指針變量就是保存著堆棧中函數(shù)入?yún)⒌牡刂返模栽趘a_start(ap, v)的定義中要使用&v,而不管v變量本身是什么類型的(哪怕v是一個(gè)指針變量,甚至是二級(jí)指針)&v都表示一個(gè)地址,所以可以強(qiáng)制轉(zhuǎn)換為va_list類型(也就是char *)。第三個(gè)問題:要睡覺了,先自己想吧,如果還不明白,就留言追問吧。

C語言的變參技術(shù),va_start,va_arg,va_end這幾個(gè)函數(shù)怎么用?

#include <stdarg.h> // 必須包含的頭文件int Add(int start,…) // …是作為占位符{ va_list arg_ptr; // 定義變參起始指針 int sum=0; // 定義變參的和 int nArgValue =start; // va_start(arg_ptr,start); // arg_ptr指向**個(gè)變參 do { sum+=nArgValue; // 求和 nArgValue = va_arg(arg_ptr,int); // arg_ptr指向下一個(gè)變參 } while(nArgValue != 0); // 判斷結(jié)束條件;結(jié)束條件是自定義為=0時(shí)結(jié)束 va_end(arg_ptr); // 復(fù)位指針 return sum; }函數(shù)的調(diào)用方法為Add(1,2,3,0);這樣,必須以0結(jié)尾,因?yàn)樽儏⒑瘮?shù)結(jié)束的判斷條件就是讀到0停止。解釋:所使用到的宏: void va_start( va_list arg_ptr, prev_param ); type va_arg( va_list arg_ptr, type ); void va_end( va_list arg_ptr ); typedef char * va_list; #define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1) ) #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) – _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 )1、首先把va_list被定義成char*,這是因?yàn)樵谖覀兡壳八玫腜C機(jī)上,字符指針類型可以用來存儲(chǔ)內(nèi)存單元地址。

而在有的機(jī)器上va_list是被定義成void*的 2、定義_INTSIZEOF(n)主要是為了某些需要內(nèi)存的對(duì)齊的系統(tǒng).這個(gè)宏的目的是為了得到**一個(gè)固定參數(shù)的實(shí)際內(nèi)存大小。

在我的機(jī)器上直接用sizeof運(yùn)算符來代替,對(duì)程序的運(yùn)行結(jié)構(gòu)也沒有影響。(后文將看到我自己的實(shí)現(xiàn))。 3、va_start的定義為 &v+_INTSIZEOF(v) ,這里&v是**一個(gè)固定參數(shù)的起始地址,再加上其實(shí)際占用大小后,就得到了**個(gè)可變參數(shù)的起始內(nèi)存地址。所以我們運(yùn)行va_start(ap, v)以后,ap指向**個(gè)可變參數(shù)在的內(nèi)存地址,有了這個(gè)地址,以后的事情就簡(jiǎn)單了。

這里要知道兩個(gè)事情: ⑴在intel+windows的機(jī)器上,函數(shù)棧的方向是向下的,棧頂指針的內(nèi)存地址低于棧底指針,所以先進(jìn)棧的數(shù)據(jù)是存放在內(nèi)存的高地址處。 (2)在VC等絕大多數(shù)C編譯器中,默認(rèn)情況下,參數(shù)進(jìn)棧的順序是由右向左的,因此,參數(shù)進(jìn)棧以后的內(nèi)存模型如下圖所示:**一個(gè)固定參數(shù)的地址位于**個(gè)可變參數(shù)之下,并且是連續(xù)存儲(chǔ)的。 |————————–| | **一個(gè)可變參數(shù) | ->高內(nèi)存地址處 |————————–| |————————–| | 第N個(gè)可變參數(shù) | ->va_arg(arg_ptr,int)后arg_ptr所指的地方, | | 即第N個(gè)可變參數(shù)的地址。

|————— | |————————–| | **個(gè)可變參數(shù) | ->va_start(arg_ptr,start)后arg_ptr所指的地方 | | 即**個(gè)可變參數(shù)的地址 |————— | |———————— –| | | | **一個(gè)固定參數(shù) | -> start的起始地址 |————– -| …………….. |————————– | | | |————— | -> 低內(nèi)存地址處 (4) va_arg():有了va_start的良好基礎(chǔ),我們?nèi)〉昧?*個(gè)可變參數(shù)的地址,在va_arg()里的任務(wù)就是根據(jù)指定的參數(shù)類型取得本參數(shù)的值,并且把指針調(diào)到下一個(gè)參數(shù)的起始地址。 因此,現(xiàn)在再來看va_arg()的實(shí)現(xiàn)就應(yīng)該心中有數(shù)了: #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) – _INTSIZEOF(t)) ) 這個(gè)宏做了兩個(gè)事情, ①用用戶輸入的類型名對(duì)參數(shù)地址進(jìn)行強(qiáng)制類型轉(zhuǎn)換,得到用戶所需要的值 ②計(jì)算出本參數(shù)的實(shí)際大小,將指針調(diào)到本參數(shù)的結(jié)尾,也就是下一個(gè)參數(shù)的首地址,以便后續(xù)處理。