变量的本质
研究过程:
- 1. 程序1
对程序进行编译连接之后,生成.exe文件,再次用debug加载此程序,执行其汇编代码。
再次得到之前已经得到的结论,C语言中函数的参数传递是通过堆栈的方式进行参数传递的。
图1 堆栈传参
同时看出,函数的返回值保存在寄存器AX中,这也是之前得到过的结论。
同时发现,程序运行时,全局变量n的段地址在DS中,而且它具有一个特定的地址。
而对于局部变量a,b,我们发现它所代表的就是堆栈中的实参,也就是说,参数a,b的段地址在寄存器SS中。
而对于变量C而言,我们发现,汇编语言中用寄存器SI代替了C的功能。
函数的返回值一直在AX中保存。
接下来,做了这样的事情,在F2中定义了d,e,f三个变量,但是没有进行其他任何操作,结果发现对应的汇编代码中仍然是用SI来代替C变量,其他代码也不受影响,随即对d,e,f赋值,并做了一定的运算,发现变量d成了栈中的空间,变量e,f被SI,DI等效替代。而CX的作用则被AX取代。
从这里其实可以得出这样的结论了,局部变量只在被用的时候才会分配空间,而且对于局部变量,分配的空间有可能是堆栈段内存区,也有可能是寄存器,这取决于变量使用的频繁度和重要性。
而对于其释放,应该说,子程序调用结束后,但是在还未返回之前堆栈被释放,则以堆栈内存区作为空间的变量就被释放了,以寄存器作变量的从实际意义上讲,也被释放了,尽管里面的值未发生任何变化。
而对于参数,他的空间分配时机要早的多,在其他函数调用此调用函数的时候,也就是还没有进入子程序之前,参数的空间就被分配了,也就是堆栈区。对于其释放,则通过程序发现了一个现象:
图2 参数返回时机
通过程序可以清晰的看出,参数的释放是在子程序已经返回之后,即将执行下一条要执行指令之前,释放到了寄存器CX中。
通过程序,发现,f3返回时汇编指令使RETF而不是RET,也就是说f3本身是个远调用类型。
对于全局变量而言,通过在使用n相关的操作之前,对其进行printf()显示其段地址和偏移地址,发现能够正常显示,这说明,在
进入main函数之前,或者说,在需要用到n之前,它就已经被定义了,具有实实在在的物理地址。
- 2. 程序2
关于静态变量和局部变量的区别研究
程序结果显示为:
图3 静态变量和局部变量
从结果可以看出来,局部变量两次显示的结果一样,而静态变量a 却不同,通过程序1知道,局部变量在退出程序调用后,变量就被释放了,所以两次调用f函数显示的结果自然一样,而a的值是不同的可见,在结束第一个f函数调用的时候,变量a并未得到释放。观察汇编代码以了解其本质。
图4 静态变量汇编代码
可以发现的是,在未进入f函数之前,对静态变量的初始化却已经完成了,这是一个比较有趣的现象,推测其原因是在f函数的声明的时候系统预知要进行这样的处理。
总之,我们得到的一个结论是:静态变零不会再函数调用结束后释放,而且其声明和初始化在进入函数之前就完成了,而且其段地址在DS中,存储空间地址和全局变量似乎很类似。
那么问题是:静态变量和全局变量有什么区别,如何区分他们?
这个问题目前尚未找到一个好的解决方法。
- 3. 程序3
对变量类型进行研究,所得汇编代码如下:
图5 数据类型所占空间
从汇编代码能够看出来,连续定义的变量其存储空间是连续的。无符号整型变量为一个字大小,无符号字符型为一个字节,无符号长整型为两个字空间。
从汇编代码可以看出,对于字符型和普通整型无差别,但是对于长整型而言,inc指令变成了ADD指令,而且还要考虑进位情况。可见,数据类型对运算方式还是有差别的。其实从本质上思考这个问题,是因为8086CPU是16位的CPU,所以才会导致这个问题,如果是32位CPU的话,上面的运算方式应该是无差别的。
- 4. 程序4
从源程序可以看出,struct stu 类型的变量a是一个全局变量,
struct stu 类型的变量b是一个局部变量。
图6 变量a的汇编代码
从其汇编代码中,我们可以得出基本结论:全局变量的段地址仍然放在了DS寄存器中,这一点是具有普遍规律的,全局变量仍然有一个固定的地址,局部变量仍然是通过堆栈的方式定义的。
而且承接程序三得出结论:连续定义的且属于同一类别(比如全局变量)的变量,他们在内存中地址是连续的。
- 5. 程序5
这个程序的研究确实花费了比较长的时间不过确实发现了一些有趣的现象
首先来说,这里再次强调前面已经得到的一般性规律:局部变量是放在了堆栈里面。
现在研究如何返回结构体变量,
图7 结构体返回时的动作
开始的时候读到这里的时候感觉莫名奇妙,不知道为什么会有这样奇怪的指令,后来重新看了一遍代码,并把栈空间以及它的地址给画了出来,再了解了LDS,LES指令的作用,突然想到这两条指令是串搬移的初始化操作,更巧的是DS:SI所指位置就是func函数中a.a变量值1的地址,而042E就是主函数中结构体变量a的值,而且发现cx为3,而下面的指令是字搬移指令,所以,我么得出结论:结构体变量返回是一种串操作!
通过下面的指令我们同时也发现,在像函数传递结构体的时候采用的也是同样的机制,即仍然是一种串操作!
未解决问题:
- 1. 全局变量和静态变量有什么区别,如何区分?
- 3. 汇编中为什么会出现远调用?
总结:
这个实验研究的比较广,几乎涉及到所有类型的变量,研究了所有的变量的存储位置,参数传递方式,申请和释放时机等,是我们深入学习C语言额基础。