王爽彙編語言綜合研究-函數如何接收不定數量的參數

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,SP

141A: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的這段代碼

1

看到編譯器將字符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處的內存,如圖

2

我們發現,編譯器確實把第一個參數放到了數據段中,並且把偏移地址入棧傳入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++];
	}
}

編譯連接這個程序,運行

7

通過這個程序,已經可以理解了函數接受不定參數的原理


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章