. : : Assembly Language : : .  |  首页  |  我提出的问题  |  我参与的问题  |  我的收藏  |  消息中心   |  游客  登录  | 
提问 | 未解决 | 已解决 | 精华区 | 搜索 |
  《汇编语言》论坛 ->综合研究
主题 : :  【砖头】综合研究试验3---使用内存空间(2)  [待解决] 回复[ 6次 ]   点击[ 1474次 ]  
mywiil
[帖 主] [ 发表时间:2009-03-20 15:00 ] 
荣誉值:61
信誉值:4
注册日期:2008-10-14 16:29
上次由于时间问题,只学习了研究试验3的一部分内容,现在就把后面的三个题目的学习和思考过程继续贴出来交流。 

  实践学习题目(3),思考问题:C语言的全局变量、局部变量存放在哪里?每个函数开头的“push bp mov bp,sp”有什么作用。 
我们编译连接书上的代码,然后反汇编进行研究,反汇编代码及分析如下: 
;main函数部分 
1413:01FA 55            PUSH    BP  
;这里不用说,是保存BP的原始值(作用还得往下看) 
1413:01FB 8BEC          MOV     BP,SP  
;这里是将sp的值赋值给BP,那么SS:BP和SS:SP此时指向了同一位置。() 

1413:01FD 83EC06        SUB     SP,+06 
;将栈空间空出3个字的内存空间来,干什么?看看下面的局部变量赋值就知道了。 

1413:0200 C706A601A100  MOV     WORD PTR [01A6],00A1 
1413:0206 C706A801A200  MOV     WORD PTR [01A8],00A2 
1413:020C C706AA01A300  MOV     WORD PTR [01AA],00A3 
;以上三条指令是给全局变量a1,a2,a3赋值,根据汇编的源代码可以看出,这种寻址方式默认的段寄存器是ds,全局变量是存放在段地址为ds的内存空间中的。 

1413:0212 C746FAB100    MOV     WORD PTR [BP-06],00B1 
1413:0217 C746FCB200    MOV     WORD PTR [BP-04],00B2 
1413:021C C746FEB300    MOV     WORD PTR [BP-02],00B3 
;以上三条指令是给局部变量b1,b2,b3赋值,根据汇编代码,用BP寻址默认的段寄存器是ss,而ss是程序运行时候的栈的段地址,所以,可以推断,局部变量是存放在了程序的栈空间。(这里也就知道了sub sp,6的指令的作用了,空出来三个字单元内存,然后,将三个局部变量的值依次存放到这段栈空间内,存放的时候就用到了BP来定位,也就是,那三个子单元空间是在sp和bp之间的。可以细细体会一下bp的用处了吧,临时的定位。还有作用呢,别急,向下看) 

1413:0221 E80400        CALL    0228   
;为了准确的定位f函数的位置,我在源代码中加了一个f()来调用f函数。 

1413:0224 8BE5          MOV     SP,BP 
;程序运行完毕了,局部变量的生命周期也该结束了,BP的另一个作用又出现了:恢复SP的值。因为局部变量已经结束了自己的作用范围了,所以,就可以释放了,怎么释放呢?我们知道sp和bp之间是存储的局部变量,要想把这段栈空间空闲出来,那么就可以pop掉那几个局部变量,当然,还有一个更简单的办法,我们既然在存出局部变量之前,用BP记录了当时sp的值,那么,我们直接再来个mov sp,bp就OK。所以,这里这条的指令就是释放局部变量占用的栈空间。 

1413:0226 5D            POP     BP 
;恢复BP为函数进入前的原始值,这是汇编子程序的处理方式。 
1413:0227 C3            RET 

;f函数部分 
1413:0228 55            PUSH    BP 
1413:0229 8BEC          MOV     BP,SP 
1413:022B 83EC06        SUB     SP,+06 
;以上三条指令的作用以及跟下面指令的关联和main函数一样,就不在复述。 

1413:022E C706A601A10F  MOV     WORD PTR [01A6],0FA1 
1413:0234 C706A801A20F  MOV     WORD PTR [01A8],0FA2 
1413:023A C706AA01A30F  MOV     WORD PTR [01AA],0FA3 
;这里是给全局变量a1,a2,a3赋值,我们可以看出,跟main函数的一样,这些数据放到了段地址默认为ds的内存空间内。说明,全局变量都是存放到这段内存空间的。 

1413:0240 C746FAC100    MOV     WORD PTR [BP-06],00C1 
1413:0245 C746FCC200    MOV     WORD PTR [BP-04],00C2 
1413:024A C746FEC300    MOV     WORD PTR [BP-02],00C3 
;f函数中局部变量c1,c2,c3进行赋值,跟main函数的一样,这里的局部变量也是存放到了栈中间中,存放的方式方法也完全一样。 

1413:024F 8BE5          MOV     SP,BP 
1413:0251 5D            POP     BP 
1413:0252 C3            RET 

  上面对函数的汇编代码进行了分析,知道了局部变量是存放到了当前栈空间中,但是,全局变量是什么段呢?我们知道其段地址是ds,那么我们就看看ds指向的是哪里。在汇编的学习过程中,我们知道,一般ds指向的是我们数据段,但是到底是不是呢?前段时间大概的学习了一下汇编网训练营的C教程,预备知识里有一个自己实现的启动代码,通过对那个启动代码的了解,可以知道,ds在程序的初始化时,指向的就是该程序的数据段(至少现在的C程序应该是这样的),所以,我们可以说全局变量是存放在数据段的。 

  下面,我们通过实践程序(4)来了解一下,C语言中的返回值是怎么处理的。 
将书中的实践代码进行编译、连接,debug下查看其汇编代码如下: 
1413:01FA 55            PUSH    BP 
1413:01FB 8BEC          MOV     BP,SP 
1413:01FD 83EC02        SUB     SP,+02 
1413:0200 E80700        CALL    020A 
1413:0203 8946FE        MOV     [BP-02],AX;将ax中的值赋值到局部变量c对应的内存单元。 
1413:0206 8BE5          MOV     SP,BP 
1413:0208 5D            POP     BP 
1413:0209 C3            RET 
1413:020A 55            PUSH    BP 
1413:020B 8BEC          MOV     BP,SP 
1413:020D A1A601        MOV     AX,[01A6];将a变量的值放入ax 
1413:0210 0306A801      ADD     AX,[01A8];ax+b变量的值 
1413:0214 A3AA01        MOV     [01AA],AX;将ax放入ab变量的内存地址 
1413:0217 A1AA01        MOV     AX,[01AA];将ab变量的内存中的值放入ax 
1413:021A EB00          JMP     021C 
1413:021C 5D            POP     BP 
1413:021D C3            RET 

  从上面的分析可以看出,在f函数中,ax寄存器贯穿于函数内外,函数内没有对其进行先保存在恢复的操作,所以,ax被用作了一个能够将函数中的操作结果带到函数外部的存储器,也就是我们用来保存返回值的寄存器,而我们从main函数中的指令call 020A后面的那个给c赋值的指令 mov [bp-02], ax也很明显的可以看出,寄存器ax是c程序中用来存放函数返回值的地方。 

  最后的第(5)个实践程序,是让我们对之前的知识进行理解。 
首先我们理解一下第一行代码: 
#define Buffer ((char *)*(int far *)0x0200000) 
首先这是一条宏指令,程序中的Buffer,都能用后面的字符串((char *)*(int far *)0x0200000)进行物理替换。关键是((char *)*(int far *)0x0200000)怎么理解。我就说一下我的理解。 

从右向左,(int far *)0x0200000将一个16进制的数据强制转换成一个长指针;*(int far *)0x0200000相当于取这个长指针变量对应的地址处的值;然后((char *)*(int far *)0x0200000)),将长指针指向的内存空间中的值转换成一个char型指针。一句话概括一下:地址为0x02000000处的内存空间中存放着一个char型指针。再往明白了说就是,Buffer代表的就是一个char型指针。#define Buffer ((char *)*(int far *)0x0200000)相当于 char * Buffer。 

接下来的程序就不难理解了 

main(){ 
Buffer=(char *)malloc(20); 
;有了上面的结论,这里不难理解了,就是申请了20个字节的内存空间。malloc是申请空间的,然后返回空间的首地址。 

Buffer[10]=0; 
;这里就奇怪了,这是数组的使用形式,而我们没有定义数组。哈哈。牛就牛在这了。以前的学C的理解太死板,太肤浅了。其实,数组的实现是基于指针的,数组名就是代表了数组的首地址。“地址+[下标]” 这个寻址形式很类似我们的汇编中的基址变址寻址的形式。“地址+[下标]”代表的意义:操作“地址+下标值”这个地址处的数据。所以,这行代码的作用就是将申请到的内存中的第10号(游标号从0开始)的char型数据直接赋值为0。 

while(Buffer[10]!=8){ 
  ;我们知道,不论字符还是什么,在内存中都是以数据的行是存放,所以,这里可以用字符指针指向的内存中的数据值与循环控制次数做比较,进行判断。 

  Buffer[Buffer[10]] = 'a'+Buffer[10]; 
  ;因为Buffer[10]从0开始变化,所以,可以将其作为游标,然后以字符'a'的值与Buffer[10]当前的值来使字符从a变到h。 

  Buffer[10]++; 
  ;Buffer[10]处的内存中的值进行子增变化。 



  上面的这段程序非常的巧妙,而且,理解起来有点绕,我也不知道我说的清楚不清楚,总之,通过这个实践任务,我知道了指针可以跟数组一样,用下标来控制和变化,这个真是长见识了,那个C语言教程上也没见过这个啊。呵呵。还得细细品味品味。如果那个兄弟品的更有味道,指教小弟一二。再次做谢啦! 

================================= 
研究试验一个比一个有味道了。还得多体会一下啊。 
下个就是研究试验4了。 

准备中。。。
crazyman
[第1楼] [ 回复时间:2009-03-20 15:02 ] 
荣誉值:152
信誉值:3
注册日期:2008-01-24 21:26
看来mywiil还真下了不少功夫。呵呵
需要登录后才能回帖 -->> 请单击此处登录
    Copyright © 2006-2024   ASMEDU.NET  All Rights Reserved