久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

探究C/C可變參數(shù)

 ShaneWu 2009-11-20

探究C/C++可變參數(shù) 收藏

C/C++支持可變參數(shù)個(gè)數(shù)的函數(shù)定義,,這一點(diǎn)與C/C++語言函數(shù)參數(shù)調(diào)用時(shí)入棧順序有關(guān),,
首先引用其他網(wǎng)友的一段文字,來描述函數(shù)調(diào)用,及參數(shù)入棧:

------------ 引用開始 ------------
C支持可變參數(shù)的函數(shù),,這里的意思是C支持函數(shù)帶有可變數(shù)量的參數(shù),,最常見的例子就
是我們十分熟悉的printf()系列函數(shù)。我們還知道在函數(shù)調(diào)用時(shí)參數(shù)是自右向左壓棧的
,。如果可變參數(shù)函數(shù)的一般形式是:
    f(p1, p2, p3, …)
那么參數(shù)進(jìn)棧(以及出棧)的順序是:
    …
    push p3
    push p2
    push p1
    call f
    pop p1
    pop p2
    pop p3
    …
我可以得到這樣一個(gè)結(jié)論:如果支持可變參數(shù)的函數(shù),,那么參數(shù)進(jìn)棧的順序幾乎必然是
自右向左的。并且,,參數(shù)出棧也不能由函數(shù)自己完成,,而應(yīng)該由調(diào)用者完成。

這個(gè)結(jié)論的后半部分是不難理解的,,因?yàn)楹瘮?shù)自身不知道調(diào)用者傳入了多少參數(shù),,但是
調(diào)用者知道,所以調(diào)用者應(yīng)該負(fù)責(zé)將所有參數(shù)出棧,。

在可變參數(shù)函數(shù)的一般形式中,,左邊是已經(jīng)確定的參數(shù),右邊省略號代表未知參數(shù)部分
,。對于已經(jīng)確定的參數(shù),,它在棧上的位置也必須是確定的。否則意味著已經(jīng)確定的參數(shù)
是不能定位和找到的,,這樣是無法保證函數(shù)正確執(zhí)行的,。衡量參數(shù)在棧上的位置,就是
離開確切的函數(shù)調(diào)用點(diǎn)(call f)有多遠(yuǎn),。已經(jīng)確定的參數(shù),,它在棧上的位置,不應(yīng)該
依賴參數(shù)的具體數(shù)量,,因?yàn)閰?shù)的數(shù)量是未知的,!

所以,選擇只能是,,已經(jīng)確定的參數(shù),,離開函數(shù)調(diào)用點(diǎn)有確定的距離(較近)。滿足這
個(gè)條件,,只有參數(shù)入棧遵從自右向左規(guī)則,。也就是說,左邊確定的參數(shù)后入棧,,離函數(shù)
調(diào)用點(diǎn)有確定的距離(最左邊的參數(shù)最后入棧,,離函數(shù)調(diào)用點(diǎn)最近)。

這樣,,當(dāng)函數(shù)開始執(zhí)行后,,它能找到所有已經(jīng)確定的參數(shù),。根據(jù)函數(shù)自己的邏輯,它負(fù)
責(zé)尋找和解釋后面可變的參數(shù)(在離開調(diào)用點(diǎn)較遠(yuǎn)的地方),,通常這依賴于已經(jīng)確定的
參數(shù)的值(典型的如prinf()函數(shù)的格式解釋,,遺憾的是這樣的方式具有脆弱性)。

據(jù)說在pascal中參數(shù)是自左向右壓棧的,,與C的相反,。對于pascal這種只支持固定參數(shù)函
數(shù)的語言,它沒有可變參數(shù)帶來的問題,。因此,,它選擇哪種參數(shù)進(jìn)棧方式都是可以的。
甚至,,其參數(shù)出棧是由函數(shù)自己完成的,,而不是調(diào)用者,因?yàn)楹瘮?shù)的參數(shù)的類型和數(shù)量
是完全已知的,。這種方式比采用C的方式的效率更好,,因?yàn)檎加酶俚拇a量(在C中,
函數(shù)每次調(diào)用的地方,,都生成了參數(shù)出棧代碼),。

C++為了兼容C,所以仍然支持函數(shù)帶有可變的參數(shù),。但是在C++中更好的選擇常常是函數(shù)
重載,。
------------ 引用結(jié)束 ------------

根據(jù)上文描述,我們查看printf()及sprintf()等函數(shù)的定義,,可以驗(yàn)證這一點(diǎn):
_CRTIMP int __cdecl printf(const char *, ...);
_CRTIMP int __cdecl sprintf(char *, const char *, ...);

這兩個(gè)函數(shù)定義時(shí),,都使用了__cdecl關(guān)鍵字,__cdecl關(guān)鍵字約定函數(shù)調(diào)用的規(guī)則是:
調(diào)用者負(fù)責(zé)清除調(diào)用堆棧,,參數(shù)通過堆棧傳遞,,入棧順序是從右到左。

下一步,,我們來看看printf()這種函數(shù)是如何使用變個(gè)數(shù)參數(shù)的,下面是摘錄MSDN上的例子,,
只引用了ANSI系統(tǒng)兼容部分的代碼,,UNIX系統(tǒng)的代碼請直接參考MSDN。

------------ 例子代碼 ------------
#include <stdio.h>
#include <stdarg.h>
int average( int first, ... );

void main( void )
{
   printf( "Average is: %d\n", average( 2, 3, 4, -1 ) );
}

int average( int first, ... )
{
   int count = 0, sum = 0, i = first;
   va_list marker;

   va_start( marker, first );     /* Initialize variable arguments. */
   while( i != -1 )
   {
      sum += i;
      count++;
      i = va_arg( marker, int);
   }
   va_end( marker );              /* Reset variable arguments.      */
   return( sum ? (sum / count) : 0 );
}
------------ 代碼結(jié)束 ------------

上例代碼功能是計(jì)算平均數(shù),,函數(shù)允許用戶輸入多個(gè)整型參數(shù),,要求作后一個(gè)參數(shù)必須
是-1,表示參數(shù)輸入完畢,,然后返回平均數(shù)計(jì)算結(jié)果,。

邏輯很簡單,,首先定義
   va_list marker;
表示參數(shù)列表,然后調(diào)用va_start()初始化參數(shù)列表,。注意va_start()調(diào)用時(shí)不僅使用了marker
這個(gè)參數(shù)列表變量,,還使用了first這個(gè)參數(shù),說明參數(shù)列表的初始化與函數(shù)給定的第一個(gè)
確定參數(shù)是有關(guān)系的,,這一點(diǎn)很關(guān)鍵,,后續(xù)分析會看到原因。

調(diào)用va_start()初始化后,,即可調(diào)用va_arg()函數(shù)訪問每一個(gè)參數(shù)列表中的參數(shù)了,。注意va_arg()
的第二個(gè)參數(shù)指定了返回值的類型(int)。

當(dāng)程序確定所有參數(shù)訪問結(jié)束后,,調(diào)用va_end()函數(shù)結(jié)束參數(shù)列表訪問,。

這樣看起來,訪問變個(gè)數(shù)參數(shù)是很容易的,,也就是使用va_list,va_start(),va_arg(),va_end()
這樣一個(gè)類型與三個(gè)函數(shù),。但是對于函數(shù)變個(gè)數(shù)參數(shù)的機(jī)制,感覺仍是一頭霧水,??磥硇枰?br>繼續(xù)深入探究,才能的到確切的答案了,。

找到va_list,va_start(),va_arg(),va_end()的定義,,在...\VC98\include\stdarg.h文件中。
.h中代碼如下(只摘錄了ANSI兼容部分的代碼,,UNIX等其他系統(tǒng)實(shí)現(xiàn)略有不同,,感興趣的朋友可以
自己研究):

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 )

從代碼可以看出,va_list只是一個(gè)類型轉(zhuǎn)義,,其實(shí)就是定義成char*類型的指針了,,這樣就是為了
以字節(jié)為單位訪問內(nèi)存。
其他三個(gè)函數(shù)其實(shí)只是三個(gè)宏定義,,且慢,,我們先看夾在中間的這個(gè)宏定義_INTSIZEOF:

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

這個(gè)宏的功能是對給定變量或者類型n,計(jì)算其按整型字節(jié)長度進(jìn)行字節(jié)對齊后的長度(size),。在32位系統(tǒng)中
int占4個(gè)字節(jié),,16位系統(tǒng)中占2字節(jié)。
表達(dá)式
 (sizeof(n) + sizeof(int) - 1)
的作用是,,如果sizeof(n)小于sizeof(int),,則計(jì)算后
的結(jié)果數(shù)值,會比sizeof(n)的值在二進(jìn)制上向左進(jìn)一位,。
如:sizeof(short) + sizeof(n) - 1 = 5
5的二進(jìn)制是0x00000101,,sizeof(short)的二進(jìn)制是0x00000010,,所以5的二進(jìn)制值比2的二進(jìn)制值
向左高一位。
表達(dá)式
 ~(sizeof(int) - 1)
的作用是生成一個(gè)蒙版(mask),,以便舍去前面那個(gè)計(jì)算值的"零頭"部分,。
如上例,~(sizeof(int) - 1) = 0xFFFFFFFC(慚愧,,此處有誤,,已修改,謝謝 glietboys,,SCAUniaodan 的提醒)
同5的二進(jìn)制0x00000101做"與"運(yùn)算得到的是0x00000100,,也就是4,而直接計(jì)算sizeof(short)應(yīng)該得到2,。
這樣通過_INTSIZEOF(short)這樣的表達(dá)式,,就可以得到按照整型字節(jié)長度對齊的其他類型字節(jié)長度。
之所以采用int類型的字節(jié)長度進(jìn)行對齊,,是因?yàn)镃/C++中的指針變量其實(shí)就是整型數(shù)值,,長度與int相同,
而指針的偏移量是后面的三個(gè)宏進(jìn)行運(yùn)算時(shí)所需要的,。

關(guān)于編程中字節(jié)對齊的內(nèi)容請有興趣的朋友到網(wǎng)上參考其他文章,,這里不再贅述。

繼續(xù),,下面這個(gè)三個(gè)宏定義:

第一:
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )

編程中這樣使用
   va_list marker;
   va_start( marker, first );
可以看出va_start宏的作用是使給定的參數(shù)列表指針(marker),,根據(jù)第一個(gè)確定參數(shù)(first)所屬類型的
指針長度向后偏移相應(yīng)位置,計(jì)算這個(gè)偏移的時(shí)候就用到了前面的_INTSIZEOF(n)宏,。

第二:
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

此處乍一看有點(diǎn)費(fèi)解,,(ap += _INTSIZEOF(t)) - _INTSIZEOF(t)表達(dá)式的一加一減,對返回值是不起作用
的啊,,也就是返回值都是ap的值,,什么原因呢?
原來這個(gè)計(jì)算返回值是一方面,,另一方面,,請記住,va_start(),va_arg(),va_end這三個(gè)宏的調(diào)用是有關(guān)聯(lián)
性的,,ap這個(gè)變量是調(diào)用va_start()時(shí)給定的參數(shù)列表指針,,所以

(ap += _INTSIZEOF(t)) - _INTSIZEOF(t)

表達(dá)式不僅僅是為了返回當(dāng)前指向的參數(shù)的地址,還是為了讓ap指向下一個(gè)參數(shù)(注意ap跳向下一參數(shù)是,,
是按照類型t的_INTSIZEOF長度進(jìn)行計(jì)算的)。

第三:
#define va_end(ap)      ( ap = (va_list)0 )

這個(gè)很好理解了,,不過是將ap指針置為空,,算作參數(shù)讀取結(jié)束,。

至此,C/C++變個(gè)數(shù)函數(shù)參數(shù)的機(jī)制已經(jīng)很清晰了,。最后還要說一點(diǎn)要注意的問題:
在用va_arg()順序跳轉(zhuǎn)指針讀取參數(shù)的過程中,,并沒有方法去判斷所得到的下一個(gè)指針是否是有效地址,也
沒有地方能夠明確得知到底要讀取多少個(gè)參數(shù),,這就是這種變個(gè)數(shù)參數(shù)的危險(xiǎn)所在,。前面的求平均數(shù)的例子
中,要求輸入者必須在參數(shù)列表最后提供一個(gè)特殊值(-1)來表示參數(shù)列表結(jié)束,,所以可以假設(shè),,萬一調(diào)用
者沒有遵循這種規(guī)則,將導(dǎo)致指針訪問越界,。

那么,,可能有朋友會問,printf()函數(shù)就沒有提供這樣的特殊值進(jìn)行標(biāo)識啊,。

別急,,printf()使用的是另一種參數(shù)個(gè)數(shù)識別方式,可能比較隱蔽,。注意他的第一個(gè)確定參數(shù),,也就是被我
們用作格式控制的format字符串,他的里面有"%d","%s"這樣的參數(shù)描述符,,printf()函數(shù)在解析format字符
串時(shí),,可以根據(jù)參數(shù)描述符的個(gè)數(shù),確定需要讀取后面幾個(gè)參數(shù),。我們不妨做下面這樣的試驗(yàn):

 printf("%d,%d,%d,%d\n",1,2,3,4,5);
 
實(shí)際提供的參數(shù)多于前面給定的參數(shù)描述符,,這樣執(zhí)行的結(jié)果是

1,2,3,4

也就是printf()根據(jù)format字符串認(rèn)為后面只有4個(gè)參數(shù),其他的就不管了,。那么再做一個(gè)試驗(yàn):

 printf("%d,%d,%d,%d\n",1,2,3);

實(shí)際提供的參數(shù)少于給定的參數(shù)描述符,,這樣執(zhí)行的結(jié)果是(如果沒有異常的話)

1,2,3,2367460

這個(gè)地方,每個(gè)人的執(zhí)行結(jié)果可能都不相同,,原因是讀取最后一個(gè)參數(shù)的指針已經(jīng)指向了非法的地址,。這也是
使用printf()這類函數(shù)需要特別注意的地方。

總結(jié):
變個(gè)數(shù)的函數(shù)參數(shù)在使用時(shí)需要注意的地方比較多,。我個(gè)人建議盡量回避使用這種模式,。比如前面的計(jì)算平均
數(shù),寧可使用數(shù)組或其他列表作為參數(shù)將一系列數(shù)值傳遞給函數(shù),,也不用寫這樣的變態(tài)函數(shù),。一方面是容易出
現(xiàn)指針訪問越界,另一方面,,在實(shí)際的函數(shù)調(diào)用時(shí),,要把所有計(jì)算值依次作為參數(shù)寫在代碼里,,很齷齪。
雖然這么說,,但有些地方這個(gè)功能還是很有用處的,,比如字符串的格式化合成,像printf()函數(shù),;在實(shí)際應(yīng)用
中,,我還經(jīng)常使用一個(gè)自己寫的WriteLog()函數(shù),用于記錄文件日志,,定義與printf()相同,,使用起來非常靈
活便利,如:

 WriteLog("用戶%s, 登錄次數(shù)%d","guanzhong",10);
 
寫在文件里的內(nèi)容就是

 用戶guanzhong, 登錄次數(shù)10
 
編程語言的使用,,在遵循基本規(guī)則的前提下,,是仁者見仁,智者見智,??傊笍亓私庵?,選擇一個(gè)符合自
己的好的習(xí)慣即可,。

歡迎各位朋友參與討論,謝謝,!
guanzhong 2007-4-4
[email protected]
http://blog.csdn.net/guanzhongs

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多