您的位置:首页 > 其它

王爽汇编语言综合研究-函数如何接收不定数量的参数

2012-04-26 17:27 381 查看
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的这段代码





看到编译器将字符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++];
}
}


编译连接这个程序,运行





通过这个程序,已经可以理解了函数接受不定参数的原理
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  语言 汇编 c 编译器