一、前提知识
1、如何传递参数(主函数)
a、函数的参数是通过栈传递,而且是从右到左依次入栈
b、即使是char型变量,在传递参数时,也是占用两个字节,因为push操作是两个字节为单位的。
c、showchar('a',2)这样的传入两个常数,也会在堆栈中开辟两个空间,也即对应两个实参变量。
2、函数如何接收参数(子函数)
a、 函数接受形参是通过从栈中取的
b、通过BP可以找到传入参数的值,BP+4是第一个参数,BP+6是第二个参数......取参数是从左到右取的
3、如何释放参数(主函数)
释放参数可以通过多次pop来实现。有时是通过“add sp,+数值”来实现的。
二、研究一个简单的不定数量参数的函数
测试代码
void showchar(int,char,...); main() { showchar(8,2,'a','b','c','d','e','f','g','h'); } void showchar(int n,char color,...) { int i; char r; for (i = 0; i!=n; i++) { r = *(char *)(_BP+8+i+i); r = color; } }
1、main函数
main() { showchar(8,2,'a','b','c','d','e','f','g','h'); }
对应的反汇编代码
2、showchar函数
void showchar(int n,char color,...) { int i; char r; for (i = 0; i!=n; i++) { r = *(char *)(_BP+8+i+i); r = color; } }
对应的反汇编代码
3、分析
函数通过参数一来控制显示字符的循环次数,通过这种方式来接收多个参数。
三、printf函数确定不定参数个数的方法
通过第一个参数所指向的字符串中%个数来确定不定参数的个数。
四、实现一个printf函数
1、包含printf函数、测试函数的C程序
extern void showenter(void); /* 在模块showchar.obj中定义 */ extern void showchar(char); /* 在模块showchar.obj中定义 */ /* 在光标位置显示字符,同时光标后移 */ static void printf(const char * str,...); /* 使用自己定义的printf函数 */ static void showint(int num); main() { printf(" hello %c%c%cld! ",'w','o','r'); printf(" %c %d %c %d ",'l',6553,'o',123); } /*************************************************************************** 函数功能:支持%c、%d功能的printf函数 输入参数:str以及不定参数 前提知识:无论char型,还是int型,在传递参数时,都是入栈操作,而且都是以两个 字节入栈的。因为push指令只能以两个字节来操作。 实现思路:通过%来统计获取不定参数的个数,参数从堆栈中去获取 ****************************************************************************/ static void printf(const char * str,...) { char strnum = 0; /* 记录读出字符串str的位置 */ char paraaddr = 0; /* 记录读出不定参数的位置 */ while(str[strnum]){ /* 取出字符为NULL,结束 */ if(str[strnum] == '%'){ /* 取出字符为%,看下一个字符 */ strnum++; /* 读取字符串的位置后移 */ switch(str[strnum]){ /* 根据%后边字符的值,选择不同的操作 */ case 'c': showchar(*(char*)(_BP+6+paraaddr));/* 为c,则将一个char型大小的参数取出显示 */ paraaddr += 2; /* 读取不定参数的位置后移 */ break; case 'd': showint(*(int*)(_BP+6+paraaddr)); /* 为d,则将一个int型大小的参数取出显示 */ paraaddr += 2; /* 读取不定参数的位置后移 */ break; default: showchar('%'); /* 不是d,也不是c,就将之前的%显示 */ showchar(str[strnum]); /* 还要把当前字符显示 */ } } else /* 取出字符非%,直接显示 */ { if(str[strnum] == ' ') /* 换行符 */ { showenter(); /* 用函数showenter显示 */ } else{ showchar(str[strnum]); /* 其他情况,直接显示 */ } } strnum++; /* 读取字符串的位置后移 */ } } /*************************************************************************** 函数功能:显示整型数字 输入参数:num 实现思路:先将整型数字从个位依次向高位取数,存在堆栈中。显示的时候,从堆栈的 高位第一个非零数字开始显示。 存在问题:函数在VC6.0上能正确运行,但是在TC2.0上不能,比如说不能显示65535 猜测原因:VC6.0和TC2.0对整型的定义是不同的,前者是4个byte的存储空间,而后者只 有两个 ****************************************************************************/ static void showint(int num) { char bufstk[5]; /* 定义栈空间 */ char p; /* 栈顶 */ for(p = 0; p < 5; p++){ bufstk[p] = num % 10; /* 从低位到高位依次入栈 */ num /= 10; } while((p > 0)&&(bufstk[--p] == 0)); /* 舍去高位为0的数字,但不舍弃num=0的个位0 */ do{ showchar(bufstk[p]+'0'); /* 显示有效数字 */ } while(p--); /* 直到栈空 */ }
2、包含在光标位置显示一个字符和显示换行的汇编程序
PUBLIC _SHOWCHAR PUBLIC _SHOWENTER _TEXT SEGMENT BYTE PUBLIC 'CODE' ASSUME CS: _TEXT ;================================================================== ;函数名称:showchar ;函数功能:显示一个字符 ;输入参数:在堆栈中,具体说来是(返回地址+2),目的是为了和C程序无缝对接 ;================================================================== _SHOWCHAR PROC NEAR push ax ;用到的中间寄存器入栈保存 push cx push bx push bp mov bp,sp ;模拟C程序的反汇编程序 mov ah,0eh ;调用"int 10h"的第0e号功能,显示字符且光标后移 mov al,[bp+10] ;字符 mov bl,07h ;颜色为黑底白字 mov bh,0 ;第0页 mov cx,1 ;重复1次 int 10h pop bp pop bx pop cx pop ax ret _SHOWCHAR ENDP ;================================================================== ;函数名称:showenter ;函数功能:显示' ' ;输入参数:无 ;================================================================== _SHOWENTER PROC NEAR push bx push ax push dx mov bh,0 ;页号为0 mov ah,3 ;取当前光标位置 int 10h ;返回参数。DH=行号,DL=列号 inc dh ;行号加1 mov dl,0 ;列号为0 mov ah,2 ;置光标位置 int 10h ;入口参数。DH=行号,DL=列号 pop dx pop ax pop bx ret _SHOWENTER ENDP _TEXT ENDS END
3、编译方法
《汇编语言》326页 研究实验5 “函数如何接收不定数量的参数”