近期在看一本書,叫作《嵌入式C語言自我修養》,寫的內容對我幫助很大,是一本好書。在第6章,GNU C編譯器擴展語法精講一節,這本書給出了一些變參函數的例子:c++
//1.變參函數初體驗 #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; }
上面的代碼很好理解:定義一個變參函數print_num,在函數內部先取得第一個參數的地址賦值給一指針,而後將指針後移,取得後面的參數並打印出來。在main函數中,傳給print_num 6個參數,按這個邏輯,應該是打印出:ubuntu
*args:1 *args:2 *args:3 *args:4 *args:5
可是結果卻出人意料:bash
打印出的值和傳進去的值徹底不相等,甚至毫無規律可言。函數
上述代碼中,是經過取首個參數的地址,並日後移動這個指針來得到後面參數的,那麼問題極可能出在兩個地方:測試
咱們一個一個來看,先暫且假定這些參數地址是連續的,且相隔同樣的距離。那麼咱們就能夠聚焦於指針的移動方式了。指針移動是「args++」這一行語句來控制的。筆者修改了一下書上的代碼:網站
#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; }
主要增長了對於每一個參數的地址的打印,運行結果以下:操作系統
筆者發現這個"args++"每次日後移動4個字節,這是由於對於"int"型指針的移動操做,是以4(sizeof(int))爲基本單位的。同理,對於"char"型指針的移動操做,以1(sizeof(char))爲單位。3d
一個"int"型指針大小若是等於4,那麼上述對於指針移動操做就沒問題。但是"int"型指針大小真的等於4嗎?指針
筆者用代碼來測試下:code
#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; }
運行結果:
能夠看到,不只"int"型指針是8字節大小,"char"、"double"和結構體指針也都是8字節大小。這是由於筆者電腦安裝的是64位系統。因此書上代碼的"int"型指針自增操做不適用於筆者,筆者將其改成「args += 2」,在dev c++這個IDE中能夠獲得正確的結果,但在ubuntu gcc下仍是不對。
解決了第一個指針移動步長問題,仍是得不到正確答案。筆者懷疑參數地址極可能不連續。如何看函數的參數地址信息?方法有不少,筆者就選一種比較快捷的方式——看彙編代碼。
在ubuntu的終端框輸入
gcc -S [源文件]
就能獲得一個帶".s"後綴的彙編代碼文件。
咱們對比着看main函數與print_num函數中關於參數傳遞的部分:
在main函數中,各個參數被放入不一樣的寄存器,在print_num函數中,又從寄存器中將參數取出來放入print_num的函數堆棧中。仔細看各個參數最終被放入的堆棧位置,發現第一個參數地址和第二個參數地址差了28個字節,然後面的參數地址之間都是差8個字節。這也就解釋了爲什麼以前的代碼結果不對了。
因此只要在第一個參數地址的基礎上加上偏移量28便可("char*"型)。
運行結果符合預期:
可是爲何第一個參數和第二個參數間隔28字節,筆者暫時還不清楚,盲猜須要去看gcc中編譯器的相關知識。
以往對於固定參數個數的普通函數的傳參,是這樣處理的:前幾個參數放入寄存器,若個數超出,則壓入函數堆棧。筆者有點好奇變參函數是否也如此,就給這個print_num傳了18個參數:
彙編代碼以下:
這說明了變參函數的傳參規則和普通函數並沒有兩樣。
在看書的時候,我喜歡邊看邊敲代碼,這一次照着書上敲的代碼運行結果不對,就有了上面的一些探究過程。若是我沒有動手實踐,之後碰到相似問題時極可能會蒙圈。因此動手實踐頗有必要。
另外,書上的東西並不必定全對,而且它的正確性須要有特定的前提作保證。好比,要是我使用的是32位系統,且編譯器在處理變參函數時將參數連續壓棧,那麼書上的代碼就是徹底正確的。咱們無需懼怕這些坑,咱們須要作的就是去找到這些前提條件,去找到問題的本質點,最後解決問題。
《嵌入式C語言自我修養——從芯片、編譯器到操做系統》
歡迎你們轉載本人的博客(需註明出處),本人另外還有一個我的博客網站:lularible的我的博客,歡迎前去瀏覽。