现代计算机大都采用冯诺依曼结构,冯诺依曼结构是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,所以计算机在运行的时候需要从中将数据取出,然后用程序进行处理,最后得到输出。我们以实验中简单的C语言程序作为例子讨论计算机如何工作:
int g(int x) { return x + 3; } int f(int x) { return g(x); } int main(void) { return f(8) + 1; }
将.c文件汇编为.s文件:gcc –S –o main.s main.c -m32
,把前面是“.”的代码行删去,结合教材和云班课视频对汇编代码进行分析,充分体会计算机的工作原理,查看的main.s文件内容如下:
g: pushl %ebp // 压栈 movl %esp,%ebp movl 8(%ebo),%eax // 将[ebp+8]地址里的数据传入eax中,即取出输入参数 addl $3,%eax popl %ebp // 出栈 ret f: pushl %ebp movl %esp,%ebp subl $4,%esp movl 8(%ebp),%eax movl %eax,(%esp) call g leave main: pushl %ebp // 帧指针入栈,便于以后恢复(此帧指针为调用main函数的帧指针) movl %esp,%ebp // 设置当前的帧指针(始终指向栈底,便于对参数之类的进行寻址及以后的恢复) subl $4,%esp // 将栈指针-4 movl $8,(%esp) // 传递参数8 call f // 调用函数f addl $1,%eax // 将eax的值+1并赋给eax leave // 相当于 movl %ebp, %esp; popl %ebp ret // 返回函数调用的下一句
堆栈的变化过程如图所示:
1、关于堆栈的问题:对计算机中的堆栈的知识感到陌生,这一块完全需要自己去补补。
通过上网查询资料,了解到了,栈的生长跟内存地址的生长刚好相反,栈是由内存高地址向内存低地址的方向生长的,所以每进行一个入栈操作的时候,栈顶指针所指向的内存地址自减4(为什么是自减4呢?因为一个内存单元是四个字节,所以当进行一个压栈操作的时候,就把指针自减4个字节,然后再把数据放进去)。
2、在学习过程中曾感到困惑的一个地方是,指令中逗号前面%esp和(%esp)的区别可以理解,但为什么逗号后面仍然有时用%esp有时用(%esp)呢?
通过反复观察,发现最本质的还是:%esp表示地址,(%esp)表示地址对应的存储单元上存放的数据。
如movl %ebp, %esp是让栈顶指针esp指向的存储单元改变为栈底指针ebp指向的存储单元;而movl %ebp, (%esp)是让栈顶指针esp指向的存储单元上存放的数据改变为栈底指针ebp所指的存储单元地址。
3、变址寻址movl 8(%ebp), %eax中ebp寄存器存放的数值有没有变化?
经搜索,ebp寄存器存放的数值没有变化。