设为首页 加入收藏

TOP

从汇编来看C语言之变量(一)
2017-01-02 08:15:09 】 浏览:447
Tags:汇编 来看 语言 变量

1、基础研究


对如图程序进行编译连接,再用debug加载。



我们在偏移地址1fa处查看main函数的内容:



执行到1fd处,发现n的偏移地址为01a6,段地址存储在ds寄存器里,为07c4.



再查看函数f2:



参数a、b的值是用栈来传递的,它们的段地址都存放在ss寄存器中:



局部变量c的值在这里是用si寄存器存储的,因为c正好是int型,那么子函数里定义的局部变量是用寄存器存储吗?我们在这里加一条赋值语句看看会如何:




可见,局部变量d是放在栈里的,而c是放在寄存器si里的,只是函数要将c返回,就将c的值赋给了ax。那么如果返回值不是int型怎么办?这个问题我们之前已经研究过:如果是1字节的数据,用al存放,如果是4字节的数据,高16位用dx传递,低16位用ax传递。


也就是说全局变量n的段地址在ds寄存器里,局部变量a、b、d的段地址在ss寄存器里,局部变量c的值存储在寄存器si里而不是内存里,没有段地址。所以全局变量n存储在程序开始的数据段里,而局部变量c存储在栈段里。参数a、b存储在栈段里。函数的返回值按值的大小存储在寄存器ax和dx中。全局变量的存储空间在程序开始就分配了,在整个程序执行完才释放,分配和释放的工作应该是由c0s.obj里的函数完成的。局部变量的存储空间在什么时候分配呢?我们将增加局部变量d的函数f2与之前的函数f2对比,发现多了一条语句“subsp,2”,之后对d的赋值语句为“movwordptr[bp-2],4”,这说明“subsp,2”就是为局部变量d分配栈段空间的指令,局部变量是在子函数开始执行时分配的,那么是在函数入口处将局部变量全部分配,还是在函数中局部变量定义处分配呢?因为TC2.0所使用的c标准要求在函数开头将要使用的变量全部定义,所以在这里这两种方式是一样的。而函数结束时“movsp,bp”指令将sp的值还原,也就是释放局部变量d的空间,所以局部变量的存储空间是在函数结束时释放的。从程序中可以看到,函数参数的存储空间是在主函数里对函数进行调用时就分配的,也就是将参数的值入栈,而在函数返回后,用popcx将参数从栈段中释放。


主函数里调用f3函数使用的语句是“call076a:0239”,也就是直接call函数的段地址+偏移地址,我们来看f3函数的内容:



发现f3返回时是用retf返回的,也就是将ip和cs都出栈。所以对于far型的函数,调用时要用call段地址+偏移地址,返回时要用retf将段地址和偏移地址都出栈。



再来看程序2:



观察函数f的内容:



发现n的存储空间为si寄存器,a的存储空间为以ds:0194为地址的两个字节。它们的存储空间是什么时候分配的呢?我们知道局部变量n的存储空间是在函数开始时分配的,而a的存储空间是固定的内存空间,不是栈段,在函数结尾处n的空间被释放了而a的空间并没有被释放。在网上查阅资料得知,静态局部变量和全局变量分配存储空间的方式是相同的,而且具有相同的生命周期,只是静态局部变量只能在定义的函数中使用。



观察主函数,也没有释放静态局部变量的语句,可见静态局部变量的存储空间也是由c0s.obj里的函数进行分配和释放的。


我们观察程序的执行结果也可以发现:



不管执行多少次f函数,每次输出n的值都为1,因为它是局部变量,f函数结束后就要释放,而a是静态局部变量,相当于全局变量,它的值是可以不断累加的。



再来看程序3:




main函数的内容为:



这里的a、b、c、a1、a2都是全局变量,只是它们的类型不同而已。他们的存储空间是否相邻呢?看看偏移地址194处的数据段的内容:



可以看到数据段里存储了5个值为1的数,它们的存储空间是紧邻的。


整型的存储空间为2个字节,字符型为1个字节,长整型为4个字节。


在自加1运算时,整型是incwordptr,对1个字的数据进行操作;字符型是incbyteptr,对1个字节的数据进行操作;长整型是先对低四位数据进行运算,再用位运算符adc对高四位进行运算得到结果。



下面来看程序4:



观察main函数的内容:






我们注意对变量a、b各个数据项的赋值部分:a的每个数据项都有固定的内存地址,而b的数据项都是存储在栈段里面,因为a是全局变量而b是局部变量。而且a、b里面的数据项的各自的存储空间是相邻的。


观察发现,在赋值语句后,程序还有一大段的指令,这些指令是用来执行printf函数的功能的。



下面来看程序5:



main函数的内容有:





观察发现程序中出现了lea指令,查询可知lea指令的作用是取偏移地址。程序里面出现了很多call指令,经过实验,发现调用f函数的是call0256,调用func函数的是call0266.main函数是怎么把结构体数据a传给函数f的呢?我们先看看f中调用的结构体数据在什么地方:



调用的数据在栈段里面,a.a是bp+4,a.b是bp+6,a.c是bp+8.


那么main函数传值应该是将数据项压栈的过程。


但我们发现在main函数里从语句call0266到call0256之间没有压栈的语句,只是调用了两个函数:call076a:1085和call076a:10a1,这两个函数肯定是对结构体数据和栈进行处理的,但是我发现难以看懂看懂它们的内容。那么不如换一种思路,我们先看看func()返回的内容放在什么地方,下面是函数func的内容:





我们发现func在对数据项进行赋值后,同样调用了076a:1085处的函数,而与main函数中比较,main函数是将ds、ax寄存器压栈,而这里是将ss、bx寄存器压栈,即将数据项的段地址和第一项的偏移地址压栈,再调用076a:1085进行处理。但是这个函数具体有什么作用呢?我还无法得出结论。在网上找到下面一段话:


C语言中函数返回结构体时如果结构体较大,则在调用函数中产生该结构的临时变量,并将该变量首地址传递给被调用函数,被调用函数返回时根据该地址修改此临时变量的内容,之后在调用函数中再将该变量复制给用户定义的变量,这也正是C语言中所谓值传递的工作方式。
如果结构体较小,则函数返回时所用的临时变量可保存在寄存器中,返回后将寄存器的值复制给用户定义的变量即可。


我对这段话的理解是,函数076a1085创建了一个临时变量,将局部变量的结构体对象a的各项数据复制到这个临时变量里,之后函数func结束,func里的变量a从栈中被释放,之后main函数再调用076a:10a1,将这个临时变量的值压栈传给函数f使用。


再来看看076a:10a1的内容:




观察函数内容,发现这就是一个搬移函数,将数据项从原来的位置搬运到栈段中指定的位置,以供函数调用。076a1085的功能也是类似的。所以从函数传递结构体型的数据是调用搬运函数,用movswmovsb指令将数据项搬运到栈段中供函数调用。因为时间关系,这里不能仔细研究,之后再继续完善。


问题:


(1)程序1函数f2里076a:0234处的语句jmp0236指向的是下一条语句,这不是无意义的吗?它起什么作用呢?


答:这里jmp语句是跳转到释放局部变量的结束语句,所以我的猜想如下:1、编译器为了避免程序出错所以要用jmp精确跳转到结束语句。2、编译器给程序预留了一个接口用来存

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇关于tcc、tlink的编译链接机制的.. 下一篇obj文件的连接问题以及tlib的基本..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目