亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C++ > 正文

淺析C/C++中的可變參數與默認參數

2020-01-26 15:50:57
字體:
來源:轉載
供稿:網友

千萬要注意,C不支持默認參數

C/C++支持可變參數個數的函數定義,這一點與C/C++語言函數參數調用時入棧順序有關,首先引用其他網友的一段文字,來描述函數調用,及參數入棧:

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

這個結論的后半部分是不難理解的,因為函數自身不知道調用者傳入了多少參數,但是調用者知道,所以調用者應該負責將所有參數出棧。

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

所以,選擇只能是,已經確定的參數,離開函數調用點有確定的距離(較近)。滿足這個條件,只有參數入棧遵從自右向左規則。也就是說,左邊確定的參數后入棧,離函數調用點有確定的距離(最左邊的參數最后入棧,離函數調用點最近)。

這樣,當函數開始執行后,它能找到所有已經確定的參數。根據函數自己的邏輯,它負責尋找和解釋后面可變的參數(在離開調用點較遠的地方),通常這依賴于已經確定的參數的值(典型的如prinf()函數的格式解釋,遺憾的是這樣的方式具有脆弱性)。

據說在pascal中參數是自左向右壓棧的,與C的相反。對于pascal這種只支持固定參數函數的語言,它沒有可變參數帶來的問題。因此,它選擇哪種參數進棧方式都是可以的。
甚至,其參數出棧是由函數自己完成的,而不是調用者,因為函數的參數的類型和數量是完全已知的。這種方式比采用C的方式的效率更好,因為占用更少的代碼量(在C中,函數每次調用的地方,都生成了參數出棧代碼)。

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

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

這兩個函數定義時,都使用了__cdecl關鍵字,__cdecl關鍵字約定函數調用的規則是:
調用者負責清除調用堆棧,參數通過堆棧傳遞,入棧順序是從右到左。

下一步,我們來看看printf()這種函數是如何使用變個數參數的,下面是摘錄MSDN上的例子,
只引用了ANSI系統兼容部分的代碼,UNIX系統的代碼請直接參考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 );
}


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

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

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

當程序確定所有參數訪問結束后,調用va_end()函數結束參數列表訪問。

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

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

復制代碼 代碼如下:

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只是一個類型轉義,其實就是定義成char*類型的指針了,這樣就是為了以字節為單位訪問內存。
其他三個函數其實只是三個宏定義,且慢,我們先看夾在中間的這個宏定義_INTSIZEOF:

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

這個宏的功能是對給定變量或者類型n,計算其按整型字節長度進行字節對齊后的長度(size)。在32位系統中int占4個字節,16位系統中占2字節。
表達式
 (sizeof(n) + sizeof(int) - 1)
的作用是,如果sizeof(n)小于sizeof(int),則計算后
的結果數值,會比sizeof(n)的值在二進制上向左進一位。

如:sizeof(short) + sizeof(n) - 1 = 5
5的二進制是0x00000101,sizeof(short)的二進制是0x00000010,所以5的二進制值比2的二進制值
向左高一位。

表達式
 ~(sizeof(int) - 1)
的作用時生成一個蒙版(mask),以便舍去前面那個計算值的"零頭"部分。
如上例,~(sizeof(int) - 1) = 0x00000011(謝謝glietboys的提醒,此處應該是0xFFFFFF00)
同5的二進制0x00000101做"與"運算得到的是0x00000100,也就是4,而直接計算sizeof(short)應該得到2。
這樣通過_INTSIZEOF(short)這樣的表達式,就可以得到按照整型字節長度對齊的其他類型字節長度。
之所以采用int類型的字節長度進行對齊,是因為C/C++中的指針變量其實就是整型數值,長度與int相同,而指針的偏移量是后面的三個宏進行運算時所需要的。

關于編程中字節對齊的內容請有興趣的朋友到網上參考其他文章,這里不再贅述。

繼續,下面這個三個宏定義:

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

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

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

此處乍一看有點費解,(ap += _INTSIZEOF(t)) - _INTSIZEOF(t)表達式的一加一減,對返回值是不起作用的啊,也就是返回值都是ap的值,什么原因呢?
原來這個計算返回值是一方面,另一方面,請記住,va_start(),va_arg(),va_end這三個宏的調用是有關聯性的,ap這個變量是調用va_start()時給定的參數列表指針,所以

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

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

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

這個很好理解了,不過是將ap指針置為空,算作參數讀取結束。

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

那么,可能有朋友會問,printf()函數就沒有提供這樣的特殊值進行標識啊。

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

printf("%d,%d,%d,%d/n",1,2,3,4,5);

實際提供的參數多于前面給定的參數描述符,這樣執行的結果是

1,2,3,4

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

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

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

1,2,3,2367460

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

總結:
變個數的函數參數在使用時需要注意的地方比較多。我個人建議盡量回避使用這種模式。比如前面的計算平均數,寧可使用數組或其他列表作為參數將一系列數值傳遞給函數,也不用寫這樣的變態函數。一方面是容易出現指針訪問越界,另一方面,在實際的函數調用時,要把所有計算值依次作為參數寫在代碼里,很齷齪。

雖然這么說,但有些地方這個功能還是很有用處的,比如字符串的格式化合成,像printf()函數;在實際應用中,我還經常使用一個自己寫的WriteLog()函數,用于記錄文件日志,定義與printf()相同,使用起來非常靈活便利,如:

WriteLog("用戶%s, 登錄次數%d","guanzhong",10);

寫在文件里的內容就是

用戶guanzhong, 登錄次數10

編程語言的使用,在遵循基本規則的前提下,是仁者見仁,智者見智??傊?,透徹了解之后,選擇一個符合自己的好的習慣即可

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产欧美最新羞羞视频在线观看| 亚洲精品www久久久| 国产亚洲精品va在线观看| 91天堂在线视频| 国产精品亚洲网站| 正在播放国产一区| 久久久久久久久久久久av| 国产精品国产三级国产aⅴ9色| 精品综合久久久久久97| 在线播放亚洲激情| 亚洲欧洲在线视频| 国产精品麻豆va在线播放| 亚洲男女性事视频| 日韩av影视综合网| 欧美丰满老妇厨房牲生活| 68精品国产免费久久久久久婷婷| 日韩av资源在线播放| 一区二区av在线| 欧美成人一区二区三区电影| 超碰精品一区二区三区乱码| 7777精品视频| 黄网站色欧美视频| 自拍偷拍亚洲一区| 日韩电影中文字幕在线| 欧美激情一级二级| 欧美性猛交丰臀xxxxx网站| 日韩电视剧在线观看免费网站| 国产精品爽爽爽爽爽爽在线观看| 亲子乱一区二区三区电影| 亚洲桃花岛网站| 亚洲国产日韩精品在线| 亚洲人a成www在线影院| 久久av在线播放| 亚洲a在线观看| 久久久久久久影视| 国产精选久久久久久| 色悠久久久久综合先锋影音下载| 一区二区三区在线播放欧美| 午夜精品蜜臀一区二区三区免费| 日韩av在线免费播放| 久久久久久久久久久亚洲| 精品人伦一区二区三区蜜桃网站| 国产精品一区二区三区免费视频| 国产精品27p| 日韩视频在线免费观看| 亚洲天堂av在线免费| 亚洲精品国产电影| 久久久久久久91| 久久久精品在线观看| 亚洲免费视频在线观看| 亚洲国产又黄又爽女人高潮的| 成年无码av片在线| 国产精品免费视频久久久| 中文字幕日韩精品在线| 成人欧美一区二区三区在线| 国产精品88a∨| 国产亚洲免费的视频看| 热久久免费视频精品| 国产精品露脸av在线| 97精品在线观看| 青青久久av北条麻妃海外网| 日韩美女中文字幕| 成人性教育视频在线观看| 久久国产精品亚洲| 国产精品视频久久| 欧美放荡办公室videos4k| 欧美激情一级二级| 欧美日韩中文在线观看| 亚洲最大福利视频网站| 一本色道久久88综合日韩精品| 热re91久久精品国99热蜜臀| 国产午夜精品全部视频播放| 久久久久久久一区二区三区| 日韩中文字幕网站| 精品中文视频在线| 日本欧美爱爱爱| 亚洲成人av片| 91久久久久久久一区二区| 欧美在线xxx| 欧美精品免费在线观看| 欧美高清视频在线播放| 亚洲欧美综合区自拍另类| 亚洲欧洲第一视频| 亚洲人成电影网站色xx| 欧美成人在线免费视频| 在线观看日韩av| 92版电视剧仙鹤神针在线观看| 国产精品伦子伦免费视频| 欧美激情视频一区二区三区不卡| 欧美第一淫aaasss性| 午夜精品一区二区三区在线| 久久久免费观看视频| 中文字幕在线看视频国产欧美在线看完整| 北条麻妃99精品青青久久| 久久久免费高清电视剧观看| 久久男人资源视频| 色香阁99久久精品久久久| 亚洲第一免费网站| 日韩亚洲欧美中文在线| 国产精品精品视频| 92版电视剧仙鹤神针在线观看| 亚洲视频在线播放| 中文字幕欧美日韩在线| 日本国产欧美一区二区三区| 日韩精品中文在线观看| 国产999精品视频| 久久国产精品视频| 91精品国产色综合久久不卡98| 亚洲毛片在线观看.| 7777精品久久久久久| 欧美人与性动交a欧美精品| 2019国产精品自在线拍国产不卡| 久久天天躁夜夜躁狠狠躁2022| 久久久久久免费精品| 欧洲中文字幕国产精品| 亚洲欧美日韩图片| 亚洲第一天堂无码专区| 成人免费淫片aa视频免费| 国产国语videosex另类| 久久精品色欧美aⅴ一区二区| 欧美性xxxxhd| 日韩中文字幕在线观看| 亚洲天堂av在线播放| 成人黄色短视频在线观看| 中文字幕欧美国内| 91丨九色丨国产在线| 国产亚洲欧美aaaa| 97在线视频一区| 亚洲视频日韩精品| 精品国产91乱高清在线观看| 日韩av三级在线观看| 国产aⅴ夜夜欢一区二区三区| 美女久久久久久久| 欧美国产日韩一区二区三区| 姬川优奈aav一区二区| 日韩不卡在线观看| 亚洲人成伊人成综合网久久久| 91久久精品国产91久久性色| 亚洲精品之草原avav久久| 一区二区三区四区精品| 2019国产精品自在线拍国产不卡| 欧美成人亚洲成人日韩成人| 中文字幕亚洲一区二区三区五十路| 国产精品久久久久一区二区| 亚洲精品国产美女| 国产亚洲精品久久久久久| 欧美在线视频免费| 77777少妇光屁股久久一区| 亚洲精选中文字幕| 亚洲香蕉成人av网站在线观看| 自拍偷拍亚洲一区| 欧美电影免费观看高清完整| 精品久久久久久国产91| 中文字幕日韩在线观看| 91手机视频在线观看| 热久久这里只有精品| 精品久久久久久中文字幕大豆网| 亚洲激情电影中文字幕| 久久久久北条麻妃免费看| 国产成人精品a视频一区www| 日韩国产高清污视频在线观看| 日韩成人久久久| 欧美国产亚洲精品久久久8v| 8x海外华人永久免费日韩内陆视频|