va_list/va_start/va_arg/va_end這幾個(gè)宏,都是用于函數(shù)的可變參數(shù)的。
我們來(lái)看看在vs2008中,它們是怎么定義的:
1: ///stdarg.h2: #define va_start _crt_va_start3: #define va_arg _crt_va_arg4: #define va_end _crt_va_end5:6: ///vadefs.h7: #define _ADDRESSOF(v) ( &reinterPRet_cast<const char &>(v) )8: typedef char * va_list;9: #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )10: #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )11: #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )12: #define _crt_va_end(ap) ( ap = (va_list)0 )再看看各個(gè)宏的功能是什么?va_list用于聲明一個(gè)變量,我們知道函數(shù)的可變參數(shù)列表其實(shí)就是一個(gè)字符串,所以va_list才被聲明為字符型指針,這個(gè)類型用于聲明一個(gè)指向參數(shù)列表的字符型指針變量,例如:va_list ap;//ap:arguement pointerva_start(ap,v),它的第一個(gè)參數(shù)是指向可變參數(shù)字符串的變量,第二個(gè)參數(shù)是可變參數(shù)函數(shù)的第一個(gè)參數(shù),通常用于指定可變參數(shù)列表中參數(shù)的個(gè)數(shù)。va_arg(ap,t),它的第一個(gè)參數(shù)指向可變參數(shù)字符串的變量,第二個(gè)參數(shù)是可變參數(shù)的類型。va_end(ap) 用于將存放可變參數(shù)字符串的變量清空(賦值為NULL).先來(lái)了解C語(yǔ)言中可變參數(shù)函數(shù)實(shí)現(xiàn)原理
C函數(shù)調(diào)用的棧結(jié)構(gòu)
可變參數(shù)函數(shù)的實(shí)現(xiàn)與函數(shù)調(diào)用的棧結(jié)構(gòu)密切相關(guān),正常情況下C的函數(shù)參數(shù)入棧規(guī)則為_(kāi)_stdcall, 它是從右到左的,即函數(shù)中的最右邊的參數(shù)最先入棧。例如,對(duì)于函數(shù):
void fun(int a, int b, int c) { int d; ... }其棧結(jié)構(gòu)為
0x1ffc-->d
0x2000-->a
0x2004-->b
0x2008-->c
對(duì)于在32位系統(tǒng)的多數(shù)編譯器,每個(gè)棧單元的大小都是sizeof(int), 而函數(shù)的每個(gè)參數(shù)都至少要占一個(gè)棧單元大小,如函數(shù) void fun1(char a, int b, double c, short d) 對(duì)一個(gè)32的系統(tǒng)其棧的結(jié)構(gòu)就是
0x1ffc-->a (4字節(jié))(為了字對(duì)齊)
0x2000-->b (4字節(jié))
0x2004-->c (8字節(jié))
0x200c-->d (4字節(jié))
因此,函數(shù)的所有參數(shù)是存儲(chǔ)在線性連續(xù)的棧空間中的,基于這種存儲(chǔ)結(jié)構(gòu),這樣就可以從可變參數(shù)函數(shù)中必須有的第一個(gè)普通參數(shù)來(lái)尋址后續(xù)的所有可變參數(shù)的類型及其值。
先看看固定參數(shù)列表函數(shù):
void fixed_args_func(int a, double b, char *c){ printf("a = 0x%p/n", &a); printf("b = 0x%p/n", &b); printf("c = 0x%p/n", &c);}對(duì)于固定參數(shù)列表的函數(shù),每個(gè)參數(shù)的名稱、類型都是直接可見(jiàn)的,他們的地址也都是可以直接得到的,比如:通過(guò)&a我們可以得到a的地址,并通過(guò)函數(shù)原型聲明了解到a是int類型的。
但是對(duì)于變長(zhǎng)參數(shù)的函數(shù),我們就沒(méi)有這么順利了。還好,按照C標(biāo)準(zhǔn)的說(shuō)明,支持變長(zhǎng)參數(shù)的函數(shù)在原型聲明中,必須有至少一個(gè)最左固定參數(shù)(這一點(diǎn)與傳統(tǒng)C有區(qū)別,傳統(tǒng)C允許不帶任何固定參數(shù)的純變長(zhǎng)參數(shù)函數(shù)),這樣我們可以得到其中固定參數(shù)的地址,但是依然無(wú)法從聲明中得到其他變長(zhǎng)參數(shù)的地址,比如:
void var_args_func(const char * fmt, ...) { ... ... }這里我們只能得到fmt這固定參數(shù)的地址,僅從函數(shù)原型我們是無(wú)法確定"..."中有幾個(gè)參數(shù)、參數(shù)都是什么類型的。回想一下函數(shù)傳參的過(guò)程,無(wú)論"..."中有多少個(gè)參數(shù)、每個(gè)參數(shù)是什么類型的,它們都和固定參數(shù)的傳參過(guò)程是一樣的,簡(jiǎn)單來(lái)講都是棧操作,而棧這個(gè)東西對(duì)我們是開(kāi)放的。這樣一來(lái),一旦我們知道某函數(shù)幀的棧上的一個(gè)固定參數(shù)的位置,我們完全有可能推導(dǎo)出其他變長(zhǎng)參數(shù)的位置。
我們先用上面的那個(gè)fixed_args_func函數(shù)確定一下入棧順序。
int main() { fixed_args_func(17, 5.40, "hello world"); return 0;}a = 0x0022FF50b = 0x0022FF54c = 0x0022FF5C從這個(gè)結(jié)果來(lái)看,顯然參數(shù)是從右到左,逐一壓入棧中的(棧的延伸方向是從高地址到低地址,棧底的占領(lǐng)著最高內(nèi)存地址,先入棧的參數(shù),其地理位置也就最高了)。
我們基本可以得出這樣一個(gè)結(jié)論:
c.addr = b.addr + x_sizeof(b); /*注意: x_sizeof !=sizeof */ b.addr = a.addr + x_sizeof(a);有了以上的"等式",我們似乎可以推導(dǎo)出 void var_args_func(const char * fmt, ... ) 函數(shù)中,可變參數(shù)的位置了。起碼第一個(gè)可變參數(shù)的位置應(yīng)該是:first_vararg.addr = fmt.addr + x_sizeof(fmt); 根據(jù)這一結(jié)論我們?cè)囍鴮?shí)現(xiàn)一個(gè)支持可變參數(shù)的函數(shù):
#include <stdarg.h>#include <stdio.h>void var_args_func(const char * fmt, ...) { char *ap; ap = ((char*)&fmt) + sizeof(fmt); printf("%d/n", *(int*)ap); ap = ap + sizeof(int); printf("%d/n", *(int*)ap); ap = ap + sizeof(int); printf("%s/n", *((char**)ap));}int main(){ var_args_func("%d %d %s/n", 4, 5, "hello world"); return 0;}期待輸出結(jié)果:45hello world
先來(lái)解釋一下這個(gè)程序。我們用ap獲取第一個(gè)變參的地址,我們知道第一個(gè)變參是4,一個(gè)int 型,所以我們用(int*)ap以告訴編譯器,以ap為首地址的那塊內(nèi)存我們要將之視為一個(gè)整型來(lái)使用,*(int*)ap獲得該參數(shù)的值;接下來(lái)的變參是5,又一個(gè)int型,其地址是ap + sizeof(第一個(gè)變參),也就是ap + sizeof(int),同樣我們使用*(int*)ap獲得該參數(shù)的值;最后的一個(gè)參數(shù)是一個(gè)字符串,也就是char*,與前兩個(gè)int型參數(shù)不同的是,經(jīng)過(guò)ap + sizeof(int)后,ap指向棧上一個(gè)char*類型的內(nèi)存塊(我們暫且稱之tmp_ptr, char *tmp_ptr)的首地址,即ap -> &tmp_ptr,而我們要輸出的不是printf("%s/n", ap),而是printf("%s/n", tmp_ptr); printf("%s/n", ap)是意圖將ap所指的內(nèi)存塊作為字符串輸出了,但是ap -> &tmp_ptr,tmp_ptr所占據(jù)的4個(gè)字節(jié)顯然不是字符串,而是一個(gè)地址。如何讓&tmp_ptr是char **類型的,我們將ap進(jìn)行強(qiáng)制轉(zhuǎn)換(char**)ap <=> &tmp_ptr,這樣我們?cè)L問(wèn)tmp_ptr只需要在(char**)ap前面加上一個(gè)*即可,即printf("%s/n", *(char**)ap);
一切似乎很完美,編譯也很順利通過(guò),但運(yùn)行上面的代碼后,不但得不到預(yù)期的結(jié)果,反而整個(gè)編譯器會(huì)強(qiáng)行關(guān)閉(大家可以嘗試著運(yùn)行一下),原來(lái)是ap指針在后來(lái)并沒(méi)有按照預(yù)期的要求指向第二個(gè)變參數(shù),即并沒(méi)有指向5所在的首地址,而是指向了未知內(nèi)存區(qū)域,所以編譯器會(huì)強(qiáng)行關(guān)閉。其實(shí)錯(cuò)誤開(kāi)始于:ap = ap + sizeof(int);由于內(nèi)存對(duì)齊,編譯器在棧上壓入?yún)?shù)時(shí),不是一個(gè)緊挨著另一個(gè)的,編譯器會(huì)根據(jù)變參的類型將其放到滿足類型對(duì)齊的地址上的,這樣棧上參數(shù)之間實(shí)際上可能會(huì)是有空隙的。(C語(yǔ)言內(nèi)存對(duì)齊詳解(1) C語(yǔ)言內(nèi)存對(duì)齊詳解(2) C語(yǔ)言內(nèi)存對(duì)齊詳解(3))所以此時(shí)的ap計(jì)算應(yīng)該改為:ap = (char *)ap +sizeof(int) + __va_rounded_size(int);
改正后的代碼如下:
#include<stdio.h>#define __va_rounded_size(TYPE) / (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))void var_args_func(const char * fmt, ...) { char *ap; ap = ((char*)&fmt) + sizeof(fmt); printf("%d/n", *(int*)ap); ap = (char *)ap + sizeof(int) + __va_rounded_size(int); printf("%d/n", *(int*)ap); ap = ap + sizeof(int) + __va_rounded_size(int); printf("%s/n", *((char**)ap));}int main(){ var_args_func("%d %d %s/n", 4, 5, "hello world"); return 0;}var_args_func只是為了演示,并未根據(jù)fmt消息中的格式字符串來(lái)判斷變參的個(gè)數(shù)和類型,而是直接在實(shí)現(xiàn)中寫死了。
為了滿足代碼的可移植性,C標(biāo)準(zhǔn)庫(kù)在stdarg.h中提供了諸多便利以供實(shí)現(xiàn)變長(zhǎng)長(zhǎng)度參數(shù)時(shí)使用。這里也列出一個(gè)簡(jiǎn)單的例子,看看利用標(biāo)準(zhǔn)庫(kù)是如何支持變長(zhǎng)參數(shù)的:
1 #include <stdarg.h>#include <stdio.h> 2 3 void std_vararg_func(const char *fmt, ...) { 4 va_list ap; 5 va_start(ap, fmt); 6 7 printf("%d/n", va_arg(ap, int)); 8 printf("%f/n", va_arg(ap, double)); 9 printf("%s/n", va_arg(ap, char*));10 11 va_end(ap);12 }13 14 int main() {15 std_vararg_func("%d %f %s/n", 4, 5.4, "hello world"); return 0;}對(duì)比一下 std_vararg_func和var_args_func的實(shí)現(xiàn),va_list似乎就是char*, va_start似乎就是 ((char*)&fmt) + sizeof(fmt),va_arg似乎就是得到下一個(gè)參數(shù)的首地址。沒(méi)錯(cuò),多數(shù)平臺(tái)下stdarg.h中va_list, va_start和var_arg的實(shí)現(xiàn)就是類似這樣的。一般stdarg.h會(huì)包含很多宏,看起來(lái)比較復(fù)雜。
下面我們來(lái)探討如何寫一個(gè)簡(jiǎn)單的可變參數(shù)的C 函數(shù).
使用可變參數(shù)應(yīng)該有以下步驟: 1)首先在函數(shù)里定義一個(gè)va_list型的變量,這里是arg_ptr,這個(gè)變量是指向參數(shù)的指針. 2)然后用va_start宏初始化變量arg_ptr,這個(gè)宏的第二個(gè)參數(shù)是第一個(gè)可變參數(shù)的前一個(gè)參數(shù),是一個(gè)固定的參數(shù). 3)然后用va_arg返回可變的參數(shù),并賦值給整數(shù)j. va_arg的第二個(gè)參數(shù)是你要返回的參數(shù)的類型,這里是int型. 4)最后用va_end宏結(jié)束可變參數(shù)的獲取.然后你就可以在函數(shù)里使用第二個(gè)參數(shù)了.如果函數(shù)有多個(gè)可變參數(shù)的,依次調(diào)用va_arg獲取各個(gè)參數(shù).
在《C程序設(shè)計(jì)語(yǔ)言》中,Ritchie提供了一個(gè)簡(jiǎn)易版printf函數(shù):
1 #include<stdarg.h> 2 3 void minprintf(char *fmt, ...) 4 { 5 va_list ap; 6 char *p, *sval; 7 int ival; 8 double dval; 9 10 va_start(ap, fmt);11 for (p = fmt; *p; p++) {12 if(*p != '%') {13 putchar(*p);14 continue;15 }16 switch(*++p) {17 case 'd':18 ival = va_arg(ap, int);19 printf("%d", ival);20 break;21 case 'f':22 dval = va_arg(ap, double);23 printf("%f", dval);24 break;25 case 's':26 for (sval = va_arg(ap, char *); *sval; sval++)27 putchar(*sval);28 break;29 default:30 putchar(*p);31 break;32 }33 }34 va_end(ap);35 }我們看一段具有可變參數(shù)列表的函數(shù)的代碼:
#include <stdio.h>#include <stdarg.h>void simple_va_fun(int i, ...){ va_list ap; int j = 0; int k = 0; va_start(ap, i); j = va_arg(ap, int); k = va_arg(ap, int); va_end(ap); printf("%d %d %d/n",i,j,k); return;}int main(void){ simple_va_fun(100); simple_va_fun(100,200); simple_va_fun(100,200,300); return 0;}輸出結(jié)果:100 730930504 730930520100 200 -965392000100 200 300va_start的功能是要把,ap指針指向可變參數(shù)的第一個(gè)參數(shù)位置處,
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _ADDRESSOF(v) ( &reinterpret_cast<constchar &>(v) ):reinterpret_cast是C++新標(biāo)準(zhǔn)下的強(qiáng)制類型轉(zhuǎn)換,這里將v強(qiáng)制轉(zhuǎn)換為const char*型,然后取其地址。
先取第一個(gè)參數(shù)的地址,在sum函數(shù)中就是取number的地址并且將其轉(zhuǎn)化為char *的(因?yàn)閏har *的指針進(jìn)行加減運(yùn)算后,偏移的字節(jié)數(shù)才與加的數(shù)字相同, 如果為int *p,那么p+1實(shí)際上將p移動(dòng)了4個(gè)字節(jié)),然后加上4(__INITSIZEOF(number)=(4+3)&~3),這樣就將ap指向了可變參數(shù)字符串的第一個(gè)參數(shù)。
va_arg是要從ap中取下一個(gè)參數(shù)。
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
以int所占的字節(jié)為標(biāo)準(zhǔn)進(jìn)行對(duì)其操作。如果int占四字節(jié),則以四字節(jié)對(duì)齊為標(biāo)準(zhǔn)讀取數(shù)據(jù)。
至于為什么會(huì)這樣計(jì)算是考慮到內(nèi)存對(duì)齊
兩年之前我寫過(guò)一篇可變參數(shù)學(xué)習(xí)筆記,里面曾經(jīng)簡(jiǎn)單的解釋過(guò)一句:代碼((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))的作用是在考慮字節(jié)對(duì)齊的因素下計(jì)算第一個(gè)可變參數(shù)的起始地址。當(dāng)時(shí)限于時(shí)間和水平,未能做更詳細(xì)的解釋。今天(2007-11-26)在csdn論壇上看到了一個(gè)帖子http://topic.csdn.net/u/20071123/16/c8d17d3f-9f49-49af-a6d8-1d7a7d84dc1c.html?seed=303711257問(wèn)題:CRT源碼分析中一個(gè)關(guān)于可變函數(shù)參數(shù)的問(wèn)題提問(wèn)者:Sun_Moon_Stars里面又問(wèn)到了這個(gè)宏,于是決定抽出半天時(shí)間,把這個(gè)問(wèn)題詳細(xì)的說(shuō)清楚。也算是把我的那篇文章做一個(gè)完美的結(jié)尾。
二、引子先看一個(gè)日常生活中的問(wèn)題,問(wèn)題1:假設(shè)有要把一批貨物放到集裝箱里,貨物有12件,一個(gè)箱子最多能裝6件貨物,求箱子的數(shù)目。解答:顯然我們需要12/6=2個(gè)箱子,并且每個(gè)箱子都是滿的。這個(gè)連小學(xué)生都會(huì)算:-)
問(wèn)題2: 把問(wèn)題1的條件改一下,假設(shè)一個(gè)箱子最多能裝5件貨物,那么現(xiàn)在的箱子數(shù)是多少?解答: 12/5=2.4個(gè),但是根據(jù)實(shí)際情況,箱子的個(gè)數(shù)必須為整數(shù),(有不知道這個(gè)常識(shí)的就不要再往下看了,回小學(xué)重讀吧,呵呵)自然我們就要取3,下面把問(wèn)題一般化
三、一般數(shù)學(xué)模型問(wèn)題3:設(shè)一個(gè)箱子最多可以裝M件貨物,且現(xiàn)有N件貨物,則至少需要多少個(gè)箱子,給出一般的計(jì)算公式。這里要注意兩點(diǎn)1、箱子的總數(shù)必須為整數(shù)2、N不一定大于M,很顯然,即使N<m,也得需要一只箱子 <="" p="" style="Word-wrap: break-word;">
四、通項(xiàng)公式1、預(yù)備知識(shí)在討論之問(wèn)題3的解答之前,我們先明確一下/運(yùn)算符的含義。定義/運(yùn)算為取整運(yùn)算,即對(duì)任意兩個(gè)整數(shù)N,M,必然有且只有唯一的整數(shù)X,滿足X*M <= N < (X+1)*M,那么記N/M=X。這個(gè)也正是c里/運(yùn)算的確切含義。x的存在性和唯一性的嚴(yán)格證明可以見(jiàn)數(shù)論教材。以后如無(wú)額外說(shuō)明,/運(yùn)算的含義均和本處一致。
/運(yùn)算有一個(gè)基本的性質(zhì)若N=MX+Y,則N/M=X+Y/M,證明略
注意:N不是可以隨便拆的,設(shè)N=A+B,那么一般情況下N/M 不一定等于 A/M+B/M,如果A和B至少有一個(gè)是M的倍數(shù),才能保證式子一定成立。
2、分步討論根據(jù)上面的/運(yùn)算符的定義,我們可以得到問(wèn)題三的解答,分情況討論一下已知N/M=X,那么當(dāng)(1)、當(dāng)N正好是M的倍數(shù)時(shí)即N=M*X時(shí),那么箱子數(shù)就是X=N/M(2)、如果N不是M的倍數(shù),即N=M*X+Y(1 <=Y <m)時(shí)那么顯然還要多一個(gè)箱子來(lái)裝余下的Y件貨物,則箱子總數(shù)為X+1 = N/M+1
3、一般公式上面的解答雖然完整,但是用起來(lái)并不方便,因?yàn)槊看味家ヅ袛郚和M的倍數(shù)關(guān)系,我們自然就要想一個(gè)統(tǒng)一的公式,于是,下面的公式出現(xiàn)了箱子數(shù)目為 (N+M-1)/M
這個(gè)式子用具體數(shù)字去驗(yàn)證是很簡(jiǎn)單的,留給讀者去做。我這里給一個(gè)完整的數(shù)學(xué)推導(dǎo):現(xiàn)在已經(jīng)假定 /運(yùn)算的結(jié)果為取整(或者說(shuō)取模),即N/M=X,則XM <=N <(X+1)M那么,(1)、當(dāng)N=MX時(shí),(N+M-1)/M= MX/M+(M-1)/M=X(2)、當(dāng)N=MX+Y(1 <=Y <m)時(shí),由1 <=Y < M,同時(shí)加上M-1,得到M <= Y-1+M <= 2M-1 <2M根據(jù) /運(yùn)算的定義 (Y-1+M) /M = 1
所以 (N+M-1)/M = (MX+Y+M-1)/M= MX/M+(Y+M-1)/M= X+1顯然 公式 (N+M-1)/M與2中的分步討論結(jié)果一致。可能有的讀者還會(huì)問(wèn),這個(gè)公式是怎么想出來(lái)的,怎么就想到了加上那個(gè)M-1?這個(gè)問(wèn)題可以先去看看數(shù)論中的余數(shù)理論。
五、對(duì)齊代碼的分析有了上面的數(shù)學(xué)基礎(chǔ),我們?cè)賮?lái)看看開(kāi)頭所說(shuō)的對(duì)齊代碼的含義((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))意義就很明顯了這里。機(jī)器字長(zhǎng)度sizeof(int)相當(dāng)于箱子的容量M,變量的真實(shí)字節(jié)大小相于貨物總數(shù)N,整個(gè)代碼就是求n所占的機(jī)器字?jǐn)?shù)目。
順便仔細(xì)的解釋一下~(sizeof(int)-1))
這里用到了一個(gè)位運(yùn)算的技巧,即若M是2的冪,M=power(2,Y);
則N/M = N>>Y ,
另根據(jù)數(shù)論中的余數(shù)定理,
有N=M*X+Z(1 < =Z < M)而注意到這里的N,M,Z都是二進(jìn)制表示,所以把N的最右邊的Y位數(shù)字就是余數(shù)Z.剩下的左邊數(shù)字就是模X.
而內(nèi)存對(duì)齊要計(jì)算的是占用的總字節(jié)數(shù)(相當(dāng)于箱子的最大容量),所以
總字節(jié)數(shù) = ( N/M)*M =( N>>Y)<<y <="" p="" style="word-wrap: break-word;">
注意,這里的右移和左移運(yùn)算并未相互抵消,最后的結(jié)果實(shí)際上是把N中的余數(shù)Z去掉(被清0),
而左邊模X得以保持不變。
而當(dāng)M = power(2,Y) 時(shí)
(N >>Y) << Y = (N &(~(M-1))也是一個(gè)恒等式(這個(gè)讀者也可以用數(shù)字驗(yàn)證),
所以,就得到我們前面看到的宏
((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
注意:(1)這里最關(guān)鍵的一點(diǎn)就是M必須是2的冪(有人常常理解成2的倍數(shù)也可以,那是不對(duì)的),否則上面的結(jié)論是不成立的(2) ~(M-1)更專業(yè)的叫法就是掩碼(mask)。因?yàn)閿?shù)字和這個(gè)掩碼進(jìn)行與運(yùn)算后,數(shù)字的最右邊Y位的數(shù)字被置0("掩抹"掉了).即掩碼最右邊的0有多少位,數(shù)字最右邊就有多少位被清0。
小結(jié):1、字節(jié)對(duì)齊的數(shù)學(xué)本質(zhì)就是數(shù)論中的取模運(yùn)算。在計(jì)算機(jī)上的含義就是求出一個(gè)對(duì)象占用的機(jī)器字?jǐn)?shù)目。2、在數(shù)學(xué)上看內(nèi)存計(jì)算的過(guò)程就是先右移再左移相同的位數(shù),以得到箱子的最大容量。
3、在c中/運(yùn)算可以用位運(yùn)算和掩碼來(lái)實(shí)現(xiàn)以加快速度(省掉了求位數(shù)的過(guò)程),前提是機(jī)器字長(zhǎng)度必須為2的冪。
——————————————————
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1)
[此問(wèn)題的推薦答案]~是位取反的意思。_INTSIZEOF(n)整個(gè)做的事情就是將n的長(zhǎng)度化為int長(zhǎng)度的整數(shù)倍。比如n為5,二進(jìn)制就是101b,int長(zhǎng)度為4,二進(jìn)制為100b,那么n化為int長(zhǎng)度的整數(shù)倍就應(yīng)該為8。~(sizeof(int) – 1) )就應(yīng)該為~(4-1)=~(00000011b)=11111100b,這樣任何數(shù)& ~(sizeof(int) – 1) )后最后兩位肯定為0,就肯定是4的整數(shù)倍了。(sizeof(n) + sizeof(int) – 1)就是將大于4m但小于等于4(m+1)的數(shù)提高到大于等于4(m+1)但小于4(m+2),這樣再& ~(sizeof(int) – 1) )后就正好將原長(zhǎng)度補(bǔ)齊到4的倍數(shù)了。
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
對(duì)于這個(gè)宏,哥糾結(jié)了很久,最后終于搞清楚了,究其原因就是自己C語(yǔ)言功底不扎實(shí),具體表現(xiàn)在沒(méi)有搞清楚賦值表達(dá)式的值是怎么運(yùn)作的。我們看這個(gè)宏,首先是ap = ap + __INTSIZEOF(t)。注意到,此時(shí)ap已經(jīng)被改變了,它已經(jīng)指向了下一個(gè)參數(shù),我們令x=ap + __INTSIZEOF(t);那么括號(hào)內(nèi)就變成了(x – __INTSIZEOF(t)),但是這里沒(méi)有賦值運(yùn)算符,所以ap的值沒(méi)有發(fā)生變化,此時(shí)ap仍然指向的是當(dāng)前參數(shù)的下一個(gè)參數(shù)的位置,也就是說(shuō)ap指向的位置比當(dāng)前正在處理的位置超前了一個(gè)位置。其實(shí)寫成下面的形式就簡(jiǎn)單明了了:#define va_arg(ap,t) (*(t *)((ap += _INTSIZEOF(t)), ap - _INTSIZEOF(t)) )
分析:為什么要將ap指向當(dāng)前處理參數(shù)的下一個(gè)參數(shù)了?經(jīng)過(guò)上面的分析,我們知道va_start(ap,v)已經(jīng)將ap指向了可變參數(shù)列表的第一個(gè)參數(shù)了,以后我們每一步操作都需要將ap移動(dòng)到下一個(gè)參數(shù)的位置,由于我們每次使用可變參數(shù)的順序是:va_start(ap,v)—>va_arg(ap,t);這樣我們?cè)诘谝淮稳?shù)的時(shí)候,其實(shí)ap已經(jīng)指向了第二個(gè)參數(shù)開(kāi)始的位置,所以我們用表達(dá)式的方式獲得一個(gè)指向第一個(gè)參數(shù)的臨時(shí)指針,這樣我們就可以采用這種一致的方式來(lái)處理可變參數(shù)列表。(感覺(jué)沒(méi)表達(dá)的十分清楚,希望各位朋友糾正~~~~~~)。下圖是我的例子程序中去參數(shù)的情況(時(shí)間倉(cāng)促,畫得很丑,請(qǐng)?jiān)彛?pre style="margin-top:0px; margin-bottom:0px; padding:0px; word-wrap:break-word"> va_end(ap) 將聲明的ap指針置為空,因?yàn)橹羔樖褂煤笞詈笤O(shè)置為空。
va_end(ap) 將聲明的ap指針置為空,因?yàn)橹羔樖褂煤笞詈笤O(shè)置為空。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注