代碼“va_start(ap,fmt)”是什么意思?
代碼“va_start(ap,fmt)”是什么意思?
VA_LIST 是在C語言中解決變參問題的一組宏,在<stdarg.h>頭文件下。
VA_LIST的用法:
首先在函數(shù)里定義一具VA_LIST型的變量,這個變量是指向參數(shù)的指針,然后用VA_START宏初始化變量剛定義的VA_LIST變量,這個宏的第二個參數(shù)是**個可變參數(shù)的前一個參數(shù),是一個固定的參數(shù)。
然后用VA_ARG返回可變的參數(shù),VA_ARG的第二個參數(shù)是你要返回的參數(shù)的類型。
**用VA_END宏結(jié)束可變參數(shù)的獲取。然后你就可以在函數(shù)里使用第二個參數(shù)了。如果函數(shù)有多個可變參數(shù)的,依次調(diào)用VA_ARG獲取各個參數(shù)。
VA_LIST在編譯器中的處理:
在運行VA_START(ap,v)以后,ap指向**個可變參數(shù)在堆棧的地址。
VA_ARG()取得類型t的可變參數(shù)值,在這步操作中首先apt = sizeof(t類型),讓ap指向下一個參數(shù)的地址。然后返回ap-sizeof(t類型)的t類型*指針,這正是 **個可變參數(shù)在堆棧里的地址。然后用*取得這個地址的內(nèi)容。
VA_END(),X86平臺定義為ap = ((char*)0),使ap不再指向堆棧,而是跟NULL一樣,有些直接定義為((void*)0),這樣編譯器不會為VA_END產(chǎn)生代碼,例如gcc在Linux的X86平臺就是這樣定義的。
要注意的是:由于參數(shù)的地址用于VA_START宏,所以參數(shù)不能聲明為寄存器變量,或作為函數(shù)或數(shù)組類型。
【求解釋va_list、va_start、va_arg、va_end】
這些都C定義的一些宏獲取省略號指定的參數(shù): 在函數(shù)體中聲明一個va_list,然后用va_start函數(shù)來獲取參數(shù)列表中的參數(shù),使用完畢后調(diào)用va_end()結(jié)束。va_start使arg_ptr指向**個可選參數(shù)百科。
va_arg返回參數(shù)列表中的當前參數(shù)并使arg_ptr指向參數(shù)列表中的下一個參數(shù)。
va_end把arg_ptr指針清為NULL。函數(shù)體內(nèi)可以多次遍歷這些參數(shù),但是都必須以va_start開始,并以va_end結(jié)尾。
C語言中可變參數(shù)宏的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這個指針變量所占用的內(nèi)存空間是一樣的(都是sizeof(a),究竟是等于4,還是8取決于CPU的位數(shù))先回答**個問題:你應該知道va_list的定義:typedef char * va_list;也就是說ap可以理解為一個char *類型的變量,va_start(ap,c)這個執(zhí)行之后,ap確實指向了可變參數(shù)列表中的**個參數(shù),注意【是ap這個指針指向了**個參數(shù)】,而如果你的**個參數(shù)是一個字符串(C語言中也就意味著是一個char*的變量),這樣的話,ap這個指針就指向了一個char*類型的指針變量,【指向指針的指針變量是二級指針變量】這個我就不用多說了吧,所以printf(\”%s\”, ap);是無法輸出的,而修改為printf(\”%s\”, *(char **)ap);應該就可以輸出了!然后是第二個問題:這里先說一下函數(shù)調(diào)用過程中參數(shù)傳遞的問題: 【 函數(shù)參數(shù)是以數(shù)據(jù)結(jié)構(gòu):棧的形式存取,從右至左入棧。首先是參數(shù)的內(nèi)存存放格式:參數(shù)存放在內(nèi)存的堆棧段中,在執(zhí)行函數(shù)的時候,從**一個開始入棧。
因此棧底高地址,棧頂?shù)偷刂?,舉個例子如下:void func(int x, char *y, char z);那么,調(diào)用函數(shù)的時候,實參 char z 先進棧,然后是 char *y,**是 int x,因此在內(nèi)存中變量的存放次序是 x->y->z,因此,從理論上說,我們只要探測到任意一個變量的地址,并且知道其他變量的類型,通過指針移位運算,則總可以順藤摸瓜找到其他的輸入變量。
】注意,x,y,z這幾個變量是存放到堆棧中的,所以我們需要獲得的不是y這個變量本身,而是它在堆棧中的地址,而ap這個指針變量就是保存著堆棧中函數(shù)入?yún)⒌牡刂返?,所以在va_start(ap, v)的定義中要使用&v,而不管v變量本身是什么類型的(哪怕v是一個指針變量,甚至是二級指針)&v都表示一個地址,所以可以強制轉(zhuǎn)換為va_list類型(也就是char *)。第三個問題:要睡覺了,先自己想吧,如果還不明白,就留言追問吧。
C語言的變參技術(shù),va_start,va_arg,va_end這幾個函數(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指向**個變參 do { sum+=nArgValue; // 求和 nArgValue = va_arg(arg_ptr,int); // arg_ptr指向下一個變參 } while(nArgValue != 0); // 判斷結(jié)束條件;結(jié)束條件是自定義為=0時結(jié)束 va_end(arg_ptr); // 復位指針 return sum; }函數(shù)的調(diào)用方法為Add(1,2,3,0);這樣,必須以0結(jié)尾,因為變參函數(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*,這是因為在我們目前所用的PC機上,字符指針類型可以用來存儲內(nèi)存單元地址。
而在有的機器上va_list是被定義成void*的 2、定義_INTSIZEOF(n)主要是為了某些需要內(nèi)存的對齊的系統(tǒng).這個宏的目的是為了得到**一個固定參數(shù)的實際內(nèi)存大小。
在我的機器上直接用sizeof運算符來代替,對程序的運行結(jié)構(gòu)也沒有影響。(后文將看到我自己的實現(xiàn))。 3、va_start的定義為 &v+_INTSIZEOF(v) ,這里&v是**一個固定參數(shù)的起始地址,再加上其實際占用大小后,就得到了**個可變參數(shù)的起始內(nèi)存地址。所以我們運行va_start(ap, v)以后,ap指向**個可變參數(shù)在的內(nèi)存地址,有了這個地址,以后的事情就簡單了。
這里要知道兩個事情: ⑴在intel+windows的機器上,函數(shù)棧的方向是向下的,棧頂指針的內(nèi)存地址低于棧底指針,所以先進棧的數(shù)據(jù)是存放在內(nèi)存的高地址處。 (2)在VC等絕大多數(shù)C編譯器中,默認情況下,參數(shù)進棧的順序是由右向左的,因此,參數(shù)進棧以后的內(nèi)存模型如下圖所示:**一個固定參數(shù)的地址位于**個可變參數(shù)之下,并且是連續(xù)存儲的。 |————————–| | **一個可變參數(shù) | ->高內(nèi)存地址處 |————————–| |————————–| | 第N個可變參數(shù) | ->va_arg(arg_ptr,int)后arg_ptr所指的地方, | | 即第N個可變參數(shù)的地址。
|————— | |————————–| | **個可變參數(shù) | ->va_start(arg_ptr,start)后arg_ptr所指的地方 | | 即**個可變參數(shù)的地址 |————— | |———————— –| | | | **一個固定參數(shù) | -> start的起始地址 |————– -| …………….. |————————– | | | |————— | -> 低內(nèi)存地址處 (4) va_arg():有了va_start的良好基礎,我們?nèi)〉昧?*個可變參數(shù)的地址,在va_arg()里的任務就是根據(jù)指定的參數(shù)類型取得本參數(shù)的值,并且把指針調(diào)到下一個參數(shù)的起始地址。 因此,現(xiàn)在再來看va_arg()的實現(xiàn)就應該心中有數(shù)了: #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) – _INTSIZEOF(t)) ) 這個宏做了兩個事情, ①用用戶輸入的類型名對參數(shù)地址進行強制類型轉(zhuǎn)換,得到用戶所需要的值 ②計算出本參數(shù)的實際大小,將指針調(diào)到本參數(shù)的結(jié)尾,也就是下一個參數(shù)的首地址,以便后續(xù)處理。