C語(yǔ)言中變參函數(shù)傳參的實(shí)現(xiàn)示例
背景引入
近期在看一本書(shū),叫做《嵌入式C語(yǔ)言自我修養(yǎng)》,寫(xiě)的內(nèi)容對(duì)我?guī)椭艽?,是一本好?shū)。在第6章,GNU C編譯器擴(kuò)展語(yǔ)法精講一節(jié),這本書(shū)給出了一些變參函數(shù)的例子:
//1.變參函數(shù)初體驗(yàn) #include<stdio.h> void print_num(int count,...) { int *args; args = &count + 1; for(int i = 0;i < count;i++) { printf("*args:%d\n",*args); args++; } } int main(void) { print_num(5,1,2,3,4,5); return 0; }
上面的代碼很好理解:定義一個(gè)變參函數(shù)print_num,在函數(shù)內(nèi)部先取得第一個(gè)參數(shù)的地址賦值給一指針,然后將指針后移,取得后面的參數(shù)并打印出來(lái)。在main函數(shù)中,傳給print_num 6個(gè)參數(shù),按這個(gè)邏輯,應(yīng)該是打印出:
*args:1
*args:2
*args:3
*args:4
*args:5
但是結(jié)果卻出人意料:
打印出的值和傳進(jìn)去的值完全不相等,甚至毫無(wú)規(guī)律可言。
問(wèn)題分析
上述代碼中,是通過(guò)取首個(gè)參數(shù)的地址,并往后移動(dòng)這個(gè)指針來(lái)獲得后面參數(shù)的,那么問(wèn)題很可能出在兩個(gè)地方:
- 指針移動(dòng)的方式不正確
- 參數(shù)的地址排布可能不是連續(xù)的
我們一個(gè)一個(gè)來(lái)看,先暫且假定這些參數(shù)地址是連續(xù)的,且相隔一樣的距離。那么我們就可以聚焦于指針的移動(dòng)方式了。指針移動(dòng)是“args++”這一行語(yǔ)句來(lái)控制的。筆者修改了一下書(shū)上的代碼:
#include<stdio.h> void print_num(int count,...) { int *args; args = &count; for(int i = 0;i <= count;i++) { printf("addr:%p\n",args); printf("*args:%d\n",*args); args++; } } int main(void) { print_num(5,1,2,3,4,5); return 0; }
主要增加了對(duì)于每個(gè)參數(shù)的地址的打印,運(yùn)行結(jié)果如下:
筆者發(fā)現(xiàn)這個(gè)"args++"每次往后移動(dòng)4個(gè)字節(jié),這是因?yàn)閷?duì)于"int"型指針的移動(dòng)操作,是以4(sizeof(int))為基本單位的。同理,對(duì)于"char"型指針的移動(dòng)操作,以1(sizeof(char))為單位。
指針大小
一個(gè)"int"型指針大小如果等于4,那么上述對(duì)于指針移動(dòng)操作就沒(méi)問(wèn)題??墒?int"型指針大小真的等于4嗎?
筆者用代碼來(lái)測(cè)試下:
#include<stdio.h> int main() { char* charPoint; int* intPoint; double* doublePoint; struct st{ int first; }; struct st *structPoint; printf("sizeof(char*):%ld\n",sizeof(charPoint)); printf("sizeof(int*):%ld\n",sizeof(intPoint)); printf("sizeof(double*):%ld\n",sizeof(doublePoint); printf("sizeof(struct*):%ld\n",sizeof(structPoint)); return 0; }
運(yùn)行結(jié)果:
可以看到,不僅"int"型指針是8字節(jié)大小,"char"、"double"和結(jié)構(gòu)體指針也都是8字節(jié)大小。這是因?yàn)楣P者電腦安裝的是64位系統(tǒng)。所以書(shū)上代碼的"int"型指針自增操作不適用于筆者,筆者將其改為“args += 2”,在dev c++這個(gè)IDE中可以得到正確的結(jié)果,但在ubuntu gcc下還是不對(duì)。
參數(shù)位置排布
解決了第一個(gè)指針移動(dòng)步長(zhǎng)問(wèn)題,還是得不到正確答案。筆者懷疑參數(shù)地址很可能不連續(xù)。如何看函數(shù)的參數(shù)地址信息?方法有很多,筆者就選一種比較快捷的方式——看匯編代碼。
在ubuntu的終端框輸入
gcc -S [源文件]
就能得到一個(gè)帶".s"后綴的匯編代碼文件。
我們對(duì)比著看main函數(shù)與print_num函數(shù)中關(guān)于參數(shù)傳遞的部分:
在main函數(shù)中,各個(gè)參數(shù)被放入不同的寄存器,在print_num函數(shù)中,又從寄存器中將參數(shù)取出來(lái)放入print_num的函數(shù)堆棧中。仔細(xì)看各個(gè)參數(shù)最終被放入的堆棧位置,發(fā)現(xiàn)第一個(gè)參數(shù)地址和第二個(gè)參數(shù)地址差了28個(gè)字節(jié),而后面的參數(shù)地址之間都是差8個(gè)字節(jié)。這也就解釋了為何之前的代碼結(jié)果不對(duì)了。
解決問(wèn)題
所以只要在第一個(gè)參數(shù)地址的基礎(chǔ)上加上偏移量28即可("char*"型)。
運(yùn)行結(jié)果符合預(yù)期:
但是為什么第一個(gè)參數(shù)和第二個(gè)參數(shù)間隔28字節(jié),筆者暫時(shí)還不清楚,盲猜需要去看gcc中編譯器的相關(guān)知識(shí)。
額外的測(cè)試
以往對(duì)于固定參數(shù)個(gè)數(shù)的普通函數(shù)的傳參,是這樣處理的:前幾個(gè)參數(shù)放入寄存器,若個(gè)數(shù)超出,則壓入函數(shù)堆棧。筆者有點(diǎn)好奇變參函數(shù)是否也如此,就給這個(gè)print_num傳了18個(gè)參數(shù):
匯編代碼如下:
這說(shuō)明了變參函數(shù)的傳參規(guī)則和普通函數(shù)并無(wú)兩樣。
總結(jié)
在看書(shū)的時(shí)候,我喜歡邊看邊敲代碼,這一次照著書(shū)上敲的代碼運(yùn)行結(jié)果不對(duì),就有了上面的一些探究過(guò)程。如果我沒(méi)有動(dòng)手實(shí)踐,以后碰到類(lèi)似問(wèn)題時(shí)很可能會(huì)蒙圈。所以動(dòng)手實(shí)踐很有必要。
另外,書(shū)上的東西并不一定全對(duì),并且它的正確性需要有特定的前提做保證。比如,要是我使用的是32位系統(tǒng),且編譯器在處理變參函數(shù)時(shí)將參數(shù)連續(xù)壓棧,那么書(shū)上的代碼就是完全正確的。我們無(wú)需害怕這些坑,我們需要做的就是去找到這些前提條件,去找到問(wèn)題的本質(zhì)點(diǎn),最后解決問(wèn)題。
參考資料
《嵌入式C語(yǔ)言自我修養(yǎng)——從芯片、編譯器到操作系統(tǒng)》
到此這篇關(guān)于C語(yǔ)言中變參函數(shù)傳參的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)C語(yǔ)言變參函數(shù)傳參內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于C++的.cpp文件運(yùn)行全過(guò)程
這篇文章主要介紹了C++的.cpp文件運(yùn)行全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02C/C++實(shí)現(xiàn)貪吃蛇逐步運(yùn)動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了C/C++實(shí)現(xiàn)貪吃蛇逐步運(yùn)動(dòng)效果的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05C語(yǔ)言調(diào)用攝像頭實(shí)現(xiàn)生成yuv未壓縮圖片
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言如何調(diào)用攝像頭實(shí)現(xiàn)生成yuv未壓縮圖片,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以參考一下2023-11-11C語(yǔ)言關(guān)鍵字const和指針的結(jié)合使用
這篇文章主要介紹了C語(yǔ)言關(guān)鍵字const和指針的結(jié)合,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02Linux下Select多路復(fù)用實(shí)現(xiàn)簡(jiǎn)易聊天室示例
大家好,本篇文章主要講的是Linux下Select多路復(fù)用實(shí)現(xiàn)簡(jiǎn)易聊天室示例,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12C++實(shí)現(xiàn)簡(jiǎn)單推箱子小游戲
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)簡(jiǎn)單推箱子小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08