1、c語言如何傳遞參數
編寫這樣一個程序試驗
void showchar(char a,int b); main() { showchar('a',2); } void showchar(char a,int b) { *(char far *)(0xb8000000 + 160*10 + 80) = a; *(char far *)(0xb8000000 + 160*10 + 81) = b; }
調試這個程序
;進入main程序
141A:01FA 55 PUSH BP ;保存寄存器現場
141A:01FB 8BEC MOV BP,SP141A:01FD B80200 MOV AX,0002 ;將2個字節的2h入棧
141A:0200 50 PUSH AX
141A:0201 B061 MOV AL,61 ;將1個字節的'a'入棧
141A:0203 50 PUSH AX
141A:0204 E80400 CALL 020B ;調用子程序
141A:0207 59 POP CX ;釋放局部變量的空間
141A:0208 59 POP CX
141A:0209 5D POP BP ;恢復寄存器現場
141A:020A C3 RET ;main函數返回;進入子程序
141A:020B 55 PUSH BP ;保存寄存器現場
141A:020C 8BEC MOV BP,SP
141A:020E 8A4604 MOV AL,[BP+04] ;讀出字符'a'
141A:0211 BB00B8 MOV BX,B800 ;寫入到b800:0690h
141A:0214 8EC3 MOV ES,BX
141A:0216 BB9006 MOV BX,0690
141A:0219 26 ES:
141A:021A 8807 MOV [BX],AL
141A:021C 8A4606 MOV AL,[BP+06] ;讀出數據2h
141A:021F BB00B8 MOV BX,B800 ;寫入到b800:0691h
141A:0222 8EC3 MOV ES,BX
141A:0224 BB9106 MOV BX,0691
141A:0227 26 ES:
141A:0228 8807 MOV [BX],AL
141A:022A 5D POP BP ;恢復寄存器現場
141A:022B C3 RET ;子程序返回
141A:022C C3 RET
容易分析,c中調用函數是通過棧來傳遞參數的,調用前將參數從右往左依次入棧。
參數在函數中是局部變量,這種方式和創建局部變量的方式類似,可以認爲是在子程序調用前爲子程序創建局部變量
所不同的是子程序裏局部變量通過保存和恢復sp寄存器來釋放局部變量空間,參數的局部變量必須通過調用完成後多次調用pop操作來釋放棧空間
2、c語言如何傳遞不定數量的參數
通過上文的分析,c程序在調用函數前,首先對所有參數依次入棧,在調用後依次出棧。
在函數內部,如果知道了有多少個參數,可以根據sp+偏移量來找到參數所在的位置,完成參數的接收
那麼如果是不定數量的參數呢,函數是怎麼知道有多少個參數的
研究下面的程序
void showchar(int,int,...); main() { showchar(8,2,'a','b','c','d','e','f','g','h'); } void showchar(int n,int color,...) { int a; for (a = 0; a!=n; a++) { *(char far *)(0xb8000000+160*10+80+a+a) = *(int *)(_BP+8+a+a); *(char far *)(0xb8000000+160*10+81+a+a) = color; } }
在這個程序中,函數聲明成了void showchar(int,int,...),參數一傳入了一個字符的數量,參數二傳入顯示的顏色,參數三開始傳入要顯示的字符。函數通過參數一來控制顯示字符的循環次數,通過這種方式來接收多個參數
3、printf函數接收參數的原理
編寫這樣一個簡單的程序
void main() { printf("%c,%c,%c,%c,%c",'a','b','c','d','e'); }
調試跟蹤調用printf的這段代碼
看到編譯器將字符e(65h),d(64h),c(63h),b(62h),a(61h)入棧後,又將0194h入棧後調用的printf子程序
由上邊的分析,0194h肯定是和printf的第一個參數"%c,%c,%c,%c,%c"相關的一個數據,我們猜想可能是指向這樣一個字符串的地址,下面驗證這個猜想
在debug下用g 215命令執行到偏移地址爲215h的位置,也就是printf調用前。用d ds:0194命令查看0194h處的內存,如圖
我們發現,編譯器確實把第一個參數放到了數據段中,並且把偏移地址入棧傳入printf函數中
那麼,printf函數是如何知道有多少個參數的呢?爲了看的更清楚,修改上面的程序
void main() { printf("%c,%c,%c,%c,%c",'a','b','c','d','e'); printf("%d,%d",1,2); }
這個程序進行了兩次printf調用,重複上面的分析過程,首先查看兩次調用時入棧的地址
兩次調用時入棧的地址分別爲0194h和01A3h,下面查看這段內存空間
發現0194h指向的字符串的十六進制爲:25 63 2c 25 63 2c 25 63 2c 25 63 2c 25 63 00
對應的字符串是:%c,%c,%c,%c,%c
01A3h指向的字符串的十六進制爲:25 64 2c 25 64 00
對應的字符串是:%d,%d
通過以上分析,發現字符串後邊都有一個0作爲結尾。
printf可能是根據傳入的%的個數來確定打印的字符數,讀入一個%就會讀取後面一個字符來確定打印的方式,當讀出一個0時打印結束
爲了驗證這個猜想,我們在debug模式下修改這個字符串,再看調用的結果
a、執行到printf函數調用之前,查看0194h處的內存
b、我們把第二個%c後的‘,’對應的16進制值2c修改爲0(也就是我們剛纔猜想的字符串結束的標誌)。再次運行看是否只打印出兩個字符。
c、用debug的e命令修改0199h處的值爲0,如圖
這個字節的內存值已經修改成了0,用debug的g命令將程序運行完畢,屏幕上只打印出了兩個字符
這說明printf函數確實是通過字符串的%個數來傳遞打印的字符數的
4、簡易的print函數
基於上述的基礎,可以自己寫一個print函數,用來在屏幕上打印字符
void print(int color,int row, int col, char * str, ...); void main() { print(2,13,30,"12345"); print(3,14,30,"%c,%c,%c,%c",'a','b','c','d'); print(4,15,30,"%d,%d",7,8); print(5,20,12,"%c,%d,123abc,%d",'z',13,3456); } /*參數列表:顏色,行,列,打印格式,附加參數1,附加參數2...*/ void print(int color,int row, int col, char * str, ...) { int strnum = 0; /*字符串位置計數器*/ int stacknum = 0; /*棧字符位置計數器*/ char ch = str[strnum++]; /*要處理的下一個字符*/ int scrnum = 80*2*row + col*2; /*顯存位置*/ int quotient = 0; /*保存每次除法的商*/ int pushnum = 0; /*除法時入棧次數計數器*/ while (ch) /*如果下一個字符爲0,則跳出循環*/ { if (ch == '%') { /*如果ch是%,那麼先讀出下一個字符*/ ch = str[strnum++]; /*判斷下一個字符是c還是d,並分別處理*/ switch (ch) { /*如果是c,按照字符型輸出棧中相應數據*/ case 'c': *(char far *)(0xb8000000 + (scrnum++)) = *(int *)(_BP + 12 + (stacknum++)); *(char far *)(0xb8000000 + (scrnum++)) = color; break; /*如果是d,按照十進制整形輸出棧中相應的數據*/ case 'd': pushnum = 0; quotient = *(int *)(_BP + 12 + (stacknum++)); if (quotient == 0) { *(char far *)(0xb8000000 + (scrnum++)) = '0'; *(char far *)(0xb8000000 + (scrnum++)) = color; } while(quotient) { _CX = quotient%10; _SP -= 2; /*模擬入棧過程*/ *(int *)(_SP) = _CX; pushnum++; quotient /= 10; } while(pushnum--) { _CX = *(int *)(_SP); /*模擬出棧過程*/ _SP += 2; *(char far *)(0xb8000000 + (scrnum++)) = _CL + 48; *(char far *)(0xb8000000 + (scrnum++)) = color; } break; } stacknum++; } else /*如果當前ch值不是%,那麼直接將ch寫入到顯存*/ { *(char far *)(0xb8000000 + (scrnum++)) = ch; *(char far *)(0xb8000000 + (scrnum++)) = color; } /*讀取下一個字符*/ ch = str[strnum++]; } }
編譯連接這個程序,運行
通過這個程序,已經可以理解了函數接受不定參數的原理