. : : Assembly Language : : .  |  首页  |  我提出的问题  |  我参与的问题  |  我的收藏  |  消息中心   |  游客  登录  | 
刷新 | 提问 | 未解决 | 已解决 | 精华区 | 搜索 |
  《汇编语言》论坛 ->综合研究
  管理员: assembly   [回复本贴] [收藏本贴] [管理本贴] [关闭窗口]
主题 : :  【砖头】综合研究试验5--函数如何接收不定数量的参数  [待解决] 回复[ 7次 ]   点击[ 1636次 ]  
mywiil
[帖 主]   [ 发表时间:2009-03-27 15:31 ]   [引用]   [回复]   [ top ] 
荣誉值:61
信誉值:4
注册日期:2008-10-14 16:29
这个研究试验5是最短的一个研究试验了,但是内容可不简单,也挺费脑筋的(也许是我脑子笨了点)。 

  刚一看,基本没思路,什么叫不定数量的参数,以前好像没注意过,写函数都是既定的参数啊。结果一看内容,突然知道了什么叫做熟视无睹了。C语言中经常使用的printf这个函数原来就是不定参数的。看来自己的洞察力不够,以前总是打印,打印,打印啊的,没发现printf函数不受参数个数的限制。晕菜~~怎么就能解决这未知的东西呢?程序可没有思想啊,也不能随机应变啊。想不明白啊!跟着书上的路学吧。 

  首先,是对一个显示字符的简单程序的分析。这个程序经过debug跟踪,main函数和showchar函数汇编代码如下: 

--------------------------------- 
以下是main函数对应的汇编代码 
--------------------------------- 
194C:01FA 55            PUSH    BP 
;将bp压栈,保存该值 
194C:01FB 8BEC          MOV     BP,SP 

194C:01FD B80200        MOV     AX,0002  
194C:0200 50            PUSH    AX 
;将调用showchar时传入的参数2压栈。可以看出,传入函数的参数,是从右到左开始压栈的。 

194C:0201 B061          MOV     AL,61 
194C:0203 50            PUSH    AX 
;将调用showchar时传入的参数a压栈。a的ascii码为61H 

194C:0204 E80400        CALL    020B 
;调用showchar函数,我们根据ip=020b去看一下showchar函数。在这里别忘了有一个push ip的压栈操作。 

194C:0207 59            POP     CX 
194C:0208 59            POP     CX 
;调用完showchar函数,sp又回到了push ax后的值,也就是指向了栈中压入的最后一个参数的位置,因为有两个参数,而且参数已经完成使用了,所以,这些参数没必要占用栈空间了,就pop掉。 

194C:0209 5D            POP     BP 
;恢复bp的原始值 

194C:020A C3            RET 
--------------------------------- 
以下是showchar函数对应的汇编代码 
--------------------------------- 
194C:020B 55            PUSH    BP 
;进入函数的时候,将bp压栈,因为后面要使用bp定为栈的内存单元。 

194C:020C 8BEC          MOV     BP,SP 

194C:020E 8A4604        MOV     AL,[BP+04] 
;将ss:[bp+4]对应的一个字节送入al,那[bp+4]的值是什么呢?我们从此处往后倒,看看这个位置是什么。首先由于mov bp,sp,所以bp此时指向了栈底,那么ss:[bp] = bp(showchar函数的push bp压入的),ss:[bp+2]=IP(main函数中call 020b指令压入的IP),ss:[sp+4] = 61H='a' (main函数中push ax压入的‘a’)。这下就知道了,ss:[bp+4]对应的数据是main函数传的参数a,由于a占一个字节,而栈操作是字操作,所以,高位就用0填了,不过,由于这里mov al,[bp+4]是字节操作,所以,al取得该栈字单元中的低8位,也就是al=61H。说道这里,就有点醒悟了,参数的传递,是通过栈来进行的。那么,按这个道理,下一个参数应该是ss:[bp+6]的地址,到底是不是,我们往下看。 
194C:0211 BB00B8        MOV     BX,B800 
194C:0214 8EC3          MOV     ES,BX 
194C:0216 BB9006        MOV     BX,0690 
194C:0219 26            ES: 
194C:021A 8807          MOV     [BX],AL 
;将al中的值61H写入显存 

194C:021C 8A4606        MOV     AL,[BP+06] 
;这里是取得main函数传入的第二个参数2的,我们根据前面的分析,发现此处的操作地址正是ss:[bp+6],也再次证明了,我们的推断是正确的:函数参数的传递是通过栈来实现的。 

194C:021F BB00B8        MOV     BX,B800 
194C:0222 8EC3          MOV     ES,BX 
194C:0224 BB9106        MOV     BX,0691 
194C:0227 26            ES: 
194C:0228 8807          MOV     [BX],AL 
;将al中的值2写入显存 

194C:022A 5D            POP     BP 
194C:022B C3            RET 

【结论】 
1.main函数是通过栈来向其调用的函数传参数的,main函数将参数反向依次压入栈中。 
2.被调用函数showchar是从栈中接受的调用者传入的参数的,使用bp在栈中进行寻址定位,将main函数传入的参数正向依次取出进行使用。 

  接下来,是实践程序(2)的学习和理解,在前面程序理解的基础上,体会一下对于函数处理参数个数的这个问题点。 

  源程序以及相关函数对应的汇编代码就不贴上来了,在分析过程中只把关键的代码拿来进行分析和说明。 

  我们已经知道了,函数传递参数是靠栈来进行的。我们只看showchar函数怎么使用参数的。为了流程上符合程序运行的顺序,我们用T命令,单步执行从main函数开始的程序。 

在进入showchar对应的代码中,首先遇到的指令为 
194C:027E 3B7604        CMP     SI,[BP+04] 
这里si保存的是变量a的值,我们看源程序,可以知道,a是用来和n来比较的,所以,[bp+4]应该是对应的参数n的值,根据前面程序的经验,可以肯定,[bp+4]就是对应的showchar形式参数中的第一个参数 int n。程序中的for循环就是通过这个指令来进行循环,控制显示字符的个数的,也就是通过这个指令和操作数据来知道显示多少个字符的。 

  我们继续向下,根据源程序,应该是想显存中写入显示字符的步骤了,关键的赋值代码为 
194C:023E 8A07   MOV  AL,[BX]    DS:FFD8=61 
这里就是将参数的值放入al值,以待后面将al值放入显存对应的位置,进行显示。在这条指令之前,有一个结果相当于bp+08+a+a的地址计算。在这里,我们发现一点,main函数传入showchar的那些字符的位置是在ds段的,也就是说*(int *)(_BP+08+a+a)这里取值是从ds:[bp+08+a+a]这里取得的。但是,我们观察寄存器可以看到ds和ss的段地址是一样,所以,这里也就是从栈对应的内存中取得的参数。那这里的8是怎么计算出来的呢?我们知道bp+4是showchar的第一个参数,而前两个都是int型(tc2.0下int型占一个字),所以,第三个参数的位置就是bp+8,而第三个参数就是所要显示的字符的第一个,以后依次是bp+10,bp+12...由于有一个+a+a的操作,位的就是调整取得参数的位置,这个自己体会,思考一下就可以明白了。在这里我们知道了,通过计算参数所占内存的大小,直接定位到栈内我们需要操作的参数的位置。 

  通过前面的思维方式,接下来将显示颜色放入显存的操作作用,对于color变量的定位,应该是bp+4+2,跳过int n这个参数占用的2个字节的内存空间,我们可以看一下相应的汇编代码来验证,代码如下 
194C:025D 8A4606     MOV     AL,[BP+06]            SS:FFD6=02 
说明我们的理解是正确。 

  行了,到了这里,这个程序b.c中的关键部分已经分析完毕了。showchar函数是通过获取第一个参数int n的大小来控制循环来控制显示多少个字符。那么,我们在printf中似乎没有看到过类似先要指定参数个数的参数的,那么printf是怎么控制显示字符的个数呢?我们知道printf函数的第一个参数是一个字符串,这个字符串中有一个特殊的符号,如“%c”、“%d”等,这些“%*”就是用来控制显示制式的,printf就是个根据字符串中这种字符的个数来控制显示字符的个数的。比如说,printf("%c,%c",'a','b'),有两个“%*”,所以要显示两个字符。 

  我们要实现一个printf,首先,我们要能够找到这个函数中的每个参数。 

  定义该函数的形式如下 myprintf(char * , ...) 
   
  由于是不定参数,所以,得声明成上面的那种形式,就像程序b.c中showchar的声明方式,这就是C支持的声明不定参数的函数的格式。 
我们主要解决定位参数的问题,首先char *这个参数我们应该清晰的知道了,那就是bp+4,而char *是一个指针类型,也就是一个偏移地址,所以,这个参数占用2个字节,也就是说,参数“...”第一个参数的位置应该就是bp+6。参数能够定位了,那么接下来就是代码的编写了。 

  以下是我实现的一个简单的myprintf。(因为在输出%d的时候,没有做对整数进行处理的操作,类似汇编中将12666显示出来的那个实验的分割数位操作,所以,这里的%d输出仅仅对0~9这10个数字能够正确处理,由于该题目的目的在于理解不定参数函数,所以,就不在实现数位分割的程序了,也算偷懒了吧。呵呵) 

void myprintf(char * , ...); 

main(){ 

   myprintf("abc%c:123%c:8%d10",'d','4',20); 


void myprintf(char * s, ...){ 

   char * p; 

   int pos ;/*控制要显示的参数的寻址*/ 

   int rpos;/*控制显存中每次显示字符的位置的定位*/ 

   p = *(int *)(_BP+4);/*这里可以简化成p = s ,但为了理解参数定位,所以这里用这个形式*/ 

   pos = 0; 

   rpos = 0; 

   while(*p != 0){ 

     if(*p !='%'){ 

    *(char far *)(0xb8000000+160*10+80+rpos) = *p; 


     }else{ 
    if(*(p+1) == 'c'){ 

     p++; 

     *(char far *)(0xb8000000+160*10+80+rpos) = *(char *)(_BP+6+pos); 

     pos+=2; 


    }else if(*(p+1) == 'd'){ 

     p++; 
          
         /*这里只支持0~9数字的显示,因为直接给数值0~9加上了30H来转化成字符*/ 
     *(char far *)(0xb8000000+160*10+80+rpos) = (char)(*(int *)(_BP+6+pos)+0x30); 

     pos+=2; 

    }else{ 

     printf("%c",*p); 

    } 

     } 
     p++; 
     rpos += 2; 

   } 



在minic环境下进行编译、连接通过,运行显示正确。 

====================================================== 
到现在为止,综合研究算是草草的学习了一边,跟着书中的思维方式走了一遭,不过,还有些乱,准备回头在将知识串一下,然后综合的思考一下这章内容是如何一步步组织深入的,也许就能明白作者的良苦用心了。 

在整个学习过程中,肯定也有不少理解偏颇甚至错误的地方,希望兄弟们多多指教。
blackberry
[第1楼]   [ 回复时间:2009-04-03 20:40 ]   [引用]   [回复]   [ top ] 
荣誉值:16
信誉值:0
注册日期:2008-10-12 13:46
哎呀,该学习学习了,顶!
bame
[第2楼]   [ 回复时间:2009-04-10 17:36 ]   [引用]   [回复]   [ top ] 
荣誉值:8
信誉值:0
注册日期:2008-12-18 16:28
真的很欣赏你的学习精神,佩服!
li4096255
[第3楼]   [ 回复时间:2009-04-13 16:33 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:0
注册日期:2007-10-17 09:53
一,       /*这里只支持0~9数字的显示,因为直接给数值0~9加上了30H来转化成字符*/  
     *(char far *)(0xb8000000+160*10+80+rpos) = (char)(*(int *)(_BP+6+pos)+0x30); 

 楼主的这一句足见功夫,不如.
二,(_BP+4)这里面是第一个字符串的首地址
..........
谢谢!!
xiaosong_os
[第4楼]   [ 回复时间:2009-05-05 20:34 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:0
注册日期:2009-05-03 13:21
push bp   结果就是sp-2

mov bp,sp 结果就是sp-2等于  bp=sp-2


mov ax,0002 

push ax  结果就是sp-4    bp=sp-2

mov al,61

push ax  结果就是sp-6     bp=sp-2

 
CALL    020B  结果就是sp-8 bp=sp-2

---------------------------------

以下是showchar函数对应的汇编代码  

--------------------------------- 


PUSH   BP  结果就是sp-10 bp=sp-2(上一个main函数中的bp=sp-2压栈保存)

MOV     BP,SP  结果就是 bp=sp-10

MOV     AL,[BP+04] 结果就是mov al,[sp-10+04]=mov al,[sp-6]

看上一个main函数中(push ax  结果就是sp-6)

所以:

mov al,[bp+04]就是对应的一个字节送入al
zjkl19
[第5楼]   [ 回复时间:2010-05-14 16:57 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:6
注册日期:2009-07-15 11:17
引用:
194C:0207 59            POP     CX  
194C:0208 59            POP     CX  
;调用完showchar函数,sp又回到了push ax后的值,也就是指向了栈中压入的最后一个参数的位置,因为有两个参数,而且参数已经完成使用了,所以,这些参数没必要占用栈空间了,就pop掉。

-----------------------------------------------------------------
以前“pop cx”不知道是干嘛用的,今天算是学习了。
camel_flying
[第6楼]   [ 回复时间:2010-05-23 12:21 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:0
注册日期:2010-04-01 20:41
精神可嘉。。。。。。学习的对象
cncser
[第7楼]   [ 回复时间:2014-02-25 12:12 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:0
注册日期:2013-02-02 10:57
修改了以下楼主的代码,看看怎样?

void myprintf(char *,...);

main()
{
        myprintf("[%c][%c][%d][%a]",'a','b',1);
}

void myprintf(char *s,...)
{
        char *p;
        int pos = 0;
        int vpos = 0;

        p = s;
        pos = _BP+6;
        while(*p != 0)
        {
                if(*p != '%')
                {
                        *(char far *)(0xb8000000+160*2+vpos) = *p;
                }
                else
                {
                        switch (*(p+1))
                        {
                        case 'c':
                                p++;
                                *(char far *)(0xb8000000+160*2+vpos) = *(char *)pos;
                                pos += 2;
                                break;
                        case 'd':
                                p++;
                                *(char far *)(0xb8000000+160*2+vpos) = (char)(*(int *)pos+0x30);
                                pos += 2;
                                break;
                        default:
                                *(char far *)(0xb8000000+160*2+vpos) = *p;
                                break;
                        }
                }
                p++;
                vpos += 2;
        }
}
需要登录后才能回帖 -->> 请单击此处登录
    Copyright © 2006-2024   ASMEDU.NET  All Rights Reserved