一、前提知识

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. 初学scala2——case class

    case class,样例类,有人也叫条件类. 例如: case class Person(name:String, age:Int) 好用之处: 1.新建类实例不用new Person(..),直接 ...

  2. c#基础语言编程-异常处理

    异常的定义 异常就是程序中的运行时错误,当出现异常时,系统会捕获这个错误并抛出一个异常.若程序没有提供处理该异常的代码,系统会挂起这个程序. 常见异常的类型 System.Exception 最泛化的 ...

  3. Android源代码之DeskClock (一)

    一.概述 一直有read the fucking source code的计划,可是实行起来都是断断续续的.到如今也没有真正得读过多少Android的源代码(主要是懒的).如今回忆起来实在是非常羞愧, ...

  4. OpenCV 2.4.9

    2014.4.25 感谢那些參加开发.发送错误报告以及通过其它方式帮助我们的全部人和公司. 源代码如今已经可以从SourceForge和Github上下载了. 2.4.9版本号的帮助文章也更新到如今的 ...

  5. 娓娓道来c指针 (3)指针和数组

    (3)指针和数组 在c中指针和数组似乎有着千丝万缕的关系.事实上它们不是一回事:指针是指针,数组是数组.两者不同样. 说它们有关系,只是是由于常见这种代码: int main() { int arra ...

  6. Swift语言入门之旅

    Swift语言入门之旅  学习一门新的计算机语言,传统来说都是从编写一个在屏幕上打印"Hello world"的程序開始的.那在 Swift,我们使用一句话来实现它: printl ...

  7. javascript实现无缝上下滚动(转)

    js实现上下无缝滚动的原理是这样的: 1.首先给容器设定高度或宽度,然后overflow:hidden: 2.容器高度设定后,内容超出则被隐藏.3.改变容器的scrollTop(上下滚动)属性的值,让 ...

  8. PHP面向对象之旅:模板模式(转)

    抽象类的应用就是典型的模版模式 抽象类的应用就是典型的模版模式,先声明一个不能被实例化的模版,在子类中去依照模版实现具体的应用. 我们写这样一个应用: 银行计算利息,都是利率乘以本金和存款时间,但各种 ...

  9. mysql 锁 实验

    CREATE TABLE `kk` ( `a` ) NOT NULL, `b` ) NOT NULL, `c` ) NOT NULL, PRIMARY KEY (`a`), UNIQUE KEY `b ...

  10. ARCGIS二维三维放大缩小

    private void ULZoomPan() { ESRI.ArcGIS.SystemUI.ICommand com = new ControlsGlobeFixedZoomOutCommand( ...