日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java 变长参数 知乎_变长参数探究

發(fā)布時(shí)間:2025/3/20 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 变长参数 知乎_变长参数探究 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

變長參數(shù),指的是函數(shù)參數(shù)數(shù)量可變,或者說函數(shù)接受參數(shù)的數(shù)量可以不固定。實(shí)際上,我們最開始學(xué)C語言的時(shí)候,就用到了這樣的函數(shù):printf,它接受任意數(shù)量的參數(shù),向終端格式化輸出字符串。本文就來探究一下,變長參數(shù)函數(shù)的實(shí)現(xiàn)機(jī)制是怎樣的,以及我們自己如何實(shí)現(xiàn)一個(gè)變長參數(shù)函數(shù)。在此之前,我們先來了解一下參數(shù)入棧順序是怎樣的。

函數(shù)參數(shù)入棧順序

我們可能知道,參數(shù)入棧順序是從右至左,是不是這樣的呢?我們可以通過一個(gè)小程序驗(yàn)證一下。小程序做的事情很簡單,main函數(shù)調(diào)用了傳入8個(gè)參數(shù)的test函數(shù),test函數(shù)打印每個(gè)參數(shù)的地址。

#include

void test(int a,int b,int c,int d,int e,int f,int g,int h)

{

printf("%p\n%p\n%p\n%p\n%p\n%p\n%p\n%p\n",&a,&b,&c,&d,&e,&f,&g,&h);

}

int main(int argc,char *argv[])

{

int a = 1;

int b = 2;

int c = 3;

int d = 4;

int e = 5;

int f = 6;

int g = 7;

int h = 8;

test(a,b,c,d,e,f,g,h);

return 0;

}

編譯成32位程序:

gcc -m32 -o paraTest paraTest.c

運(yùn)行(不同的機(jī)器運(yùn)行結(jié)果不同,且每次運(yùn)行結(jié)果也不一定相同):

0xffdadff0

0xffdadff4

0xffdadff8

0xffdadffc

0xffdae000

0xffdae004

0xffdae008

0xffdae00c

觀察打印出來的地址,可以發(fā)現(xiàn),從a到h地址值依次增加4。我們知道,棧是從高地址向低地址增長的,從地址值可以推測h是最先入棧,a是最后入棧的。也就是說,參數(shù)是從右往左入棧的(注:并非所有語言都是如此)。

但是如果將函數(shù)test參數(shù)b改為char 型呢?運(yùn)行結(jié)果如下:

0xffb29500

0xffb294ec

0xffb29508

0xffb2950c

0xffb29510

0xffb29514

0xffb29518

0xffb2951c

觀察結(jié)果可以發(fā)現(xiàn),b的地址并非是a的地址值加4,也不是在a和c的地址值之間,這是為何?這是編譯器出于對空間,壓棧速度等因素的考慮,對其進(jìn)行了優(yōu)化,但這并不影響變長參數(shù)的實(shí)現(xiàn)。

對于上面的情況,如果我們編譯成64位程序又是什么樣的情況呢?

gcc -o paraTest paraTest.c

./paraTest

運(yùn)行結(jié)果如下:

0x7fff4b83cbcc

0x7fff4b83cbc8

0x7fff4b83cbc4

0x7fff4b83cbc0

0x7fff4b83cbbc

0x7fff4b83cbb8

0x7fff4b83cbe0

0x7fff4b83cbe8

通過觀察可以發(fā)現(xiàn),從參數(shù)a到f,其地址似乎是遞減的,而從g到h地址又變成遞增的了,這是為什么呢?事實(shí)上,對于x86-64,當(dāng)參數(shù)個(gè)數(shù)超過6時(shí),前6個(gè)參數(shù)可以通過寄存器傳遞,而第7~n個(gè)參數(shù)則會通過棧傳遞,并且數(shù)據(jù)大小都向8的倍數(shù)對齊。也就是說,對于7~n個(gè)參數(shù),依然滿足從右往左入棧,只是對于前6個(gè)參數(shù),它們是通過寄存器來傳遞的。另外,寄存器的訪問速度相對于內(nèi)存來說要快得多,因此為了提高空間和時(shí)間效率,實(shí)際中其實(shí)不建議參數(shù)超過6個(gè)。

對于函數(shù)參數(shù)入棧順序我們就了解到這里,但是參數(shù)入棧順序和變長參數(shù)又有什么關(guān)系呢?

變長參數(shù)實(shí)現(xiàn)分析

通過前面的例子,我們了解到函數(shù)參數(shù)是從右往左依次入棧的,而且第一個(gè)參數(shù)位于棧頂。那么,我們就可以通過第一個(gè)參數(shù)進(jìn)行地址偏移,來得到第二個(gè),第三個(gè)參數(shù)的地址,是不是可以實(shí)現(xiàn)呢?我們來看一個(gè)32位程序的例子。例子同樣很簡單,我們通過a的地址來獲取其他參數(shù)的地址:

#include

void test( int a, char b, int c, int d, int e)

{

printf("%d\n%d\n%d\n%d\n%d\n\n",a,*(&a+1),*(&a+2),*(&a+3),*(&a+4));

}

int main(int argc,char *argv[])

{

int a = 1;

char b = 2;

int c = 3;

int d = 4;

int e = 5;

test(a,b,c,d,e);

return 0;

}

編譯為32位程序運(yùn)行:

gcc -m32 -o paraTest paraTest.c

./paraTest

1

2

3

4

5

通過觀察運(yùn)行結(jié)果我們可以發(fā)現(xiàn),即使只有a的地址也可以訪問到其他參數(shù)。也就是說,即便傳入的參數(shù)是多個(gè),只要我們知道每個(gè)參數(shù)的類型,只需通過第一個(gè)參數(shù)就能夠通過地址偏移正確訪問到其他參數(shù)。同時(shí)我們也注意到,即便b是char類型,訪問c的值也是偏移4的倍數(shù)地址,這是字節(jié)對齊的緣故,有興趣的可以閱讀理一理字節(jié)對齊的那些事。

變長參數(shù)實(shí)現(xiàn)

經(jīng)過前面的理解分析,我們知道,正是由于參數(shù)從右往左入棧(但是要注意的是,對于x86-64,它的參數(shù)不是完全從右往左入棧,且參數(shù)可能不在一個(gè)連續(xù)的區(qū)域中,它的變長參數(shù)實(shí)現(xiàn)也更為復(fù)雜,我們這里不展開)可以實(shí)現(xiàn)變長參數(shù)。當(dāng)然了,這一切,C已經(jīng)有現(xiàn)成可用的一些東西來幫我們實(shí)現(xiàn)變長參數(shù)。

它主要通過一個(gè)類型(va_list)和三個(gè)宏(va_start、va_arg、va_end)來實(shí)現(xiàn)

va_list :存儲參數(shù)的類型信息,32位和64位實(shí)現(xiàn)不一樣。

void va_start ( va_list ap, paramN );

參數(shù):

ap: 可變參數(shù)列表地址

paramN: 確定的參數(shù)

功能:初始化可變參數(shù)列表,會把paraN之后的參數(shù)放入ap中

type va_arg ( va_list ap, type );

功能:返回下一個(gè)參數(shù)的值。

void va_end ( va_list ap );

功能:完成清理工作。

可變參數(shù)函數(shù)實(shí)現(xiàn)的步驟如下:1.在函數(shù)中創(chuàng)建一個(gè)va_list類型變量

2.使用va_start對其進(jìn)行初始化

3.使用va_arg訪問參數(shù)值

4.使用va_end完成清理工作

接下來我們來實(shí)現(xiàn)一個(gè)變長參數(shù)函數(shù)來對給定的一組整數(shù)進(jìn)行求和。程序清單如下:

#include

/*要使用變長參數(shù)的宏,需要包含下面的頭文件*/

#include

/*

* getSum:用于計(jì)算一組整數(shù)的和

* num:整數(shù)的數(shù)量

*

* */

int getSum(int num,...)

{

va_list ap;//定義參數(shù)列表變量

int sum = 0;

int loop = 0;

va_start(ap,num);

/*遍歷參數(shù)值*/

for(;loop < num ; loop++)

{

/*取出并加上下一個(gè)參數(shù)值*/

sum += va_arg(ap,int);

}

va_end(ap);

return sum;

}

int main(int argc,char *argv[])

{

int sum = 0;

sum = getSum(5,1,2,3,4,5);

printf("%d\n",sum);

return 0;

}

上面的小程序接受變長參數(shù),第一個(gè)參數(shù)表明將要計(jì)算和的整數(shù)個(gè)數(shù),后面的參數(shù)是要計(jì)算的值。

編譯運(yùn)行可得結(jié)果:15。

但是我們要注意的是,這個(gè)小程序不像printf那樣,對傳入的參數(shù)做了校驗(yàn),因此一但傳入的參數(shù)num和實(shí)際參數(shù)不匹配,或者傳入類型與要計(jì)算的int類型不匹配,將會出現(xiàn)不可預(yù)知的錯(cuò)誤。我們舉一個(gè)簡單的例子,如果第二個(gè)參數(shù)傳入一個(gè)浮點(diǎn)數(shù),程序清單如下:

#include

/*要使用變長參數(shù)的宏,需要包含下面的頭文件*/

#include

/*

* getSum:用于計(jì)算一組整數(shù)的和

* num:整數(shù)的數(shù)量

*

* */

int getSum(int num,...)

{

va_list ap;//定義參數(shù)列表變量

int sum = 0;

int loop = 0;

int value = 0;

va_start(ap,num);

for(;loop < num ; loop++)

{

value = va_arg(ap,int);

printf("the %d value is %d\n",loop.value);

sum += value;

}

va_end(ap);

return sum;

}

int main(int argc,char *argv[])

{

int sum = 0;

float a = 8.25f;

printf("a to int=%d\n",*(int*)&a);

sum = getSum(5,a,2,3,4,5);

printf("%d\n",sum);

return 0;

}

編譯運(yùn)行:

gcc -m32 -o multiPara multiPara.c

./multiPara

a to int=1090781184

the 0 loop value is 0

the 1 loop value is 1075871744

the 2 loop value is 2

the 3 loop value is 3

the 4 loop value is 4

the sum is1075871753

觀察上面的運(yùn)行結(jié)果,發(fā)現(xiàn)結(jié)果與我們所預(yù)期大相徑庭,我們可能會有以下幾個(gè)疑問:1.把a(bǔ)的地址上的值轉(zhuǎn)換為int,為什么會是1090781184?

2.getSum函數(shù)中,為什么第一個(gè)值是0?

3.getSum函數(shù)中,為什么第二個(gè)值是1075871744?

4.getSum函數(shù)中,為什么沒有獲取到5?

5.為什么最后的結(jié)果不是我們預(yù)期的值?

我們逐一解答第一個(gè)問題,我們不在本文解釋,但可以通過對浮點(diǎn)數(shù)的一些理解來找到答案。

對于第二個(gè)、第三個(gè)問題以及第四個(gè)問題,涉及到類型提升。也就是說在C語言中,調(diào)用一個(gè)不帶原型聲明的函數(shù)時(shí),調(diào)用者會對每個(gè)參數(shù)執(zhí)行“默認(rèn)實(shí)際參數(shù)提升",提升規(guī)則如下:

——float將提升到double

——char、short和相應(yīng)的signed、unsigned類型將提升到int

——如果int不能存儲原值,則提升到unsigned int

那么也就可以理解了,調(diào)用者會將提升之后的參數(shù)傳給被調(diào)用者。也就是說a被提升為了8字節(jié)的double類型,自然而然,而我們?nèi)≈凳前磇nt4字節(jié)取值,第一次取值取的double的前4字節(jié),第二次取的后4字節(jié),而由于總共取數(shù)5次,因此最后的5也就不會被取到。

了解了前面幾個(gè)問題的答案,那么最后一個(gè)問題的答案也就隨之而出了。前面取值已經(jīng)不對了,最后的結(jié)果自然不是我們想要的。

總結(jié)

通過前面的分析和示例,我們來做一些總結(jié)變長參數(shù)實(shí)現(xiàn)的基本原理

對于x86來說,函數(shù)參數(shù)入棧順序?yàn)閺挠彝?#xff0c;因此,在知道第一個(gè)參數(shù)地址之后,我們能夠通過地址偏移獲取其他參數(shù),雖然x86-64在實(shí)現(xiàn)上略有不同,但`對于開發(fā)者使用來說,實(shí)現(xiàn)變長參數(shù)函數(shù)沒有32位和64位的區(qū)別。

變長參數(shù)實(shí)現(xiàn)注意事項(xiàng)

1.…前的參數(shù)可以有1個(gè)或多個(gè),但前一個(gè)必須是確定類型。

2.傳入?yún)?shù)會可能會出現(xiàn)類型提升。

3.va_arg的type類型不能是char,short int,float等類型,否則取值不正確,原因?yàn)榈?#xff12;點(diǎn)。

4.va_arg不能往回取參數(shù),但可以使用va_copy拷貝va_list,以備后用。

5.變長參數(shù)類型注意做好檢查,例如可以采用printf的占位符方式等等。

6.即便printf有類型檢查,但也要注意參數(shù)匹配,例如,將int類型匹配%s打印,將會出現(xiàn)嚴(yán)重問題。

7.當(dāng)傳入?yún)?shù)個(gè)數(shù)少于使用的個(gè)數(shù)時(shí),可能會出現(xiàn)嚴(yán)重問題,當(dāng)傳入?yún)?shù)大于使用的個(gè)數(shù)時(shí),多出的參數(shù)不會被處理使用。

8.注意字節(jié)對齊問題。

總結(jié)

以上是生活随笔為你收集整理的java 变长参数 知乎_变长参数探究的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。