一、前提知识

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(,,'a','b','c','d','e','f','g','h');
} void showchar(int n,char color,...)
{
int i;
char r;
for (i = ; i!=n; i++)
{
r = *(char *)(_BP++i+i);
r = color;
}
}

1、main函数  

main()
{
showchar(,,'a','b','c','d','e','f','g','h');
}

对应的反汇编代码

2、showchar函数

void showchar(int n,char color,...)
{
int i;
char r;
for (i = ; i!=n; i++)
{
r = *(char *)(_BP++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("\nhello %c%c%cld!\n",'w','o','r');
printf("\n%c %d %c %d\n",'l',,'o',);
} /***************************************************************************
函数功能:支持%c、%d功能的printf函数
输入参数:str以及不定参数
前提知识:无论char型,还是int型,在传递参数时,都是入栈操作,而且都是以两个
字节入栈的。因为push指令只能以两个字节来操作。
实现思路:通过%来统计获取不定参数的个数,参数从堆栈中去获取
****************************************************************************/
static void printf(const char * str,...)
{
char strnum = ; /* 记录读出字符串str的位置 */
char paraaddr = ; /* 记录读出不定参数的位置 */ while(str[strnum]){ /* 取出字符为NULL,结束 */
if(str[strnum] == '%'){ /* 取出字符为%,看下一个字符 */
strnum++; /* 读取字符串的位置后移 */
switch(str[strnum]){ /* 根据%后边字符的值,选择不同的操作 */
case 'c':
showchar(*(char*)(_BP++paraaddr));/* 为c,则将一个char型大小的参数取出显示 */
paraaddr += ; /* 读取不定参数的位置后移 */
break;
case 'd':
showint(*(int*)(_BP++paraaddr)); /* 为d,则将一个int型大小的参数取出显示 */
paraaddr += ; /* 读取不定参数的位置后移 */
break;
default:
showchar('%'); /* 不是d,也不是c,就将之前的%显示 */
showchar(str[strnum]); /* 还要把当前字符显示 */
}
}
else /* 取出字符非%,直接显示 */
{
if(str[strnum] == '\n') /* 换行符 */
{
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[]; /* 定义栈空间 */
char p; /* 栈顶 */ for(p = ; p < ; p++){
bufstk[p] = num % ; /* 从低位到高位依次入栈 */
num /= ;
} while((p > )&&(bufstk[--p] == )); /* 舍去高位为0的数字,但不舍弃num=0的个位0 */ do{
showchar(bufstk[p]+''); /* 显示有效数字 */
}
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+] ;字符
mov bl,07h ;颜色为黑底白字
mov bh, ;第0页
mov cx, ;重复1次
int 10h pop bp
pop bx
pop cx
pop ax
ret _SHOWCHAR ENDP ;==================================================================
;函数名称:showenter
;函数功能:显示'\n'
;输入参数:无
;==================================================================
_SHOWENTER PROC NEAR push bx
push ax
push dx mov bh, ;页号为0
mov ah, ;取当前光标位置
int 10h ;返回参数。DH=行号,DL=列号 inc dh ;行号加1
mov dl, ;列号为0
mov ah, ;置光标位置
int 10h ;入口参数。DH=行号,DL=列号 pop dx
pop ax
pop bx
ret _SHOWENTER ENDP
_TEXT ENDS
END

3、编译方法

 
   《汇编语言》326页 研究实验5 “函数如何接收不定数量的参数”
 

研究不定数量参数的函数并实现一个printf函数的更多相关文章

  1. js闭包(函数内部嵌套一个匿名函数:这个匿名函数可将所在函数的局部变量常驻内存)

    js闭包(函数内部嵌套一个匿名函数:这个匿名函数可将所在函数的局部变量常驻内存) 一.总结 1.闭包:就是在一个函数内部嵌套一个匿名函数,这个匿名函数可以访问这个函数的变量. 二.要点 闭包 闭包的相 ...

  2. 详解Python函数参数定义及传参(必备参数、关键字参数、默认可省略参数、可变不定长参数、*args、**kwargs)

    详解Python函数参数定义及传参(必备参数.关键字参数.默认可省略参数.可变不定长参数.*args.**kwargs) Python函数参数传参的种类   Python中函数参数定义及调用函数时传参 ...

  3. java 不定长参数

    一,不定长参数的规定 一个方法只能有一个不定长参数,并且这个不定长参数必须是该方法的最后一个参数. 示例: public class VariArgs { public static void mai ...

  4. JS基础语法---函数---介绍、定义、函数参数、返回值

    函数: 把一坨重复的代码封装,在需要的时候直接调用即可 函数的作用: 代码的重用 函数需要先定义,然后才能使用 函数名字:要遵循驼峰命名法 函数一旦重名,后面的会把前面的函数覆盖 Ctrl +鼠标左键 ...

  5. c语言中,既然不支持函数重载,那么printf算怎么回事?在c语言中,它不就是被重载了吗?

    这个问题问的不错.其实printf不是重载,c语言不支持函数重载 这句话是对的.printf函数是通过变长参数表实现的.你可以查看一下printf的函数原型声明.printf函数的实现在不同的机器上是 ...

  6. [转]printf 函数实现的深入剖析

    研究printf的实现,首先来看看printf函数的函数体 int printf(const char *fmt, ...) { int i; char buf[256];          va_l ...

  7. s3c2440——实现裸机的简易printf函数

    在单片机开发中,我们借助于vsprintf函数,可以自己实现一个printf函数,但是,那是IDE帮我们做了一些事情. 刚开始在ARM9裸机上自己写printf的实现的时候,包含对应头文件也会提示vs ...

  8. js中的匿名函数和匿名自执行函数

    1.匿名函数的常见场景 js中的匿名函数是一种很常见的函数类型,比较常见的场景:   <input type="button" value="点击" id ...

  9. STM32 printf()函数和scanf()函数重定向到串口

    STM32 printf()函数和scanf()函数重定向到串口 printf()函数和scanf()函数重定向 在学习STM32的时候,常常需要用串口来测试代码的正确与否,这时候就要要用到print ...

随机推荐

  1. 贪心+容器 hdu4268

    Problem Description Alice and Bob's game never ends. Today, they introduce a new game. In this game, ...

  2. 【设计模式 - 15】之解释器模式(Interpreter)

    1      模式简介 解释器模式允许我们自定义一种语言,并定义一个这种语言的解释器,这个解释器用来解释语言中的句子.由于这种模式主要用于编译器的编写,因此在日常应用中不是很常用. 如果一种特定类型的 ...

  3. [置顶] Array ArrayList LinkList的区别剖析

    这是一个面试中我们经常被问到的问题 Array.ArrayList.LinkList之间的区别:Array.ArrayList.LinkList均属于泛型的范畴,都用来存放元素,主要区别是Array是 ...

  4. hdu 4742 Pinball Game 3D 分治+树状数组

    离散化x然后用树状数组解决,排序y然后分治解决,z在分治的时候排序解决. 具体:先对y排序,solve(l,r)分成solve(l,mid),solve(mid+1,r), 然后因为是按照y排序,所以 ...

  5. Android 颜色渲染(九) PorterDuff及Xfermode详解

    版权声明:本文为博主原创文章,未经博主允许不得转载. Android 颜色渲染(九)  PorterDuff及Xfermode详解 之前已经讲过了除ComposeShader之外Shader的全部子类 ...

  6. [转] 用管道获得shell 命令的输出

    用管道: 通过fgets(buf, n, ptr)buf就可以得到命令“ps -ef"一样的信息, 读帮助”man popen": char *cmd = "ps -ef ...

  7. java的继承机制

    这次我想深入探究以下java里类的继承机制.       我们知道,继承是java设计里的一个失败的地方.高司令说过:如果让他重新设计java的话,他会把继承去掉.而java里继承到底怎么了,会这么不 ...

  8. 11.2 morning

    noip模拟题day1——棋盘上的问题 day1模拟题 By FancyCoder总览(Overview)注意事项:共3道题目,时间2.5小时.Pascal选手允许使用math库和ansistring ...

  9. 详细查看数据库SQL执行计划

    DBCC DROPCLEANBUFFERS 清除数据缓存DBCC FREEPROCCACHE  清除执行计划缓存 SET SHOWPLAN_XML ON 此语句导致 SQL Server 不执行 Tr ...

  10. Scrum教练不应兼任product owner

    ScrumMasters Should Not Also Be Product Owners(中文翻译) December 2, 2014 by Mike Cohn 翻译:2015.2.18 by o ...