在大学计算机组成原理一课中学习各种汇编语言跟C语言的关系,同时在单片机接口技术中也学习了C51的汇编语言,在一些MCU调试中也看到了反汇编的内容。但是从来没亲自实践一下C语言是怎么变成汇编的过程以及他们之间的对应关系。作为作业今天就在这里写下我的发现吧。

按照要求这是一段很简单的c语言程序

进行编译生成汇编文件

生成的main.s文件 已经把多余的命令删除了(gvim好像确实没vim好用。。。)

一开始是这样的 空栈

ebp!esp!
 
 
 
 

程序从main标签处运行,开始push,mov后(我用ebp!和esp!来表示实际的ebp esp所指向的位置吧,表示起来好麻烦 ebp(main)是指向0那个位置的ebp 就是表格最上边那个位置)

 
ebp!esp!ebp(main)
 
 
 
到了subl esp减4 向下移动一个字节 并且把立即数233存到了esp指向的位置(间接寻址?)
 
ebp!ebp(main)
esp! 233
 
 
当执行call f时 eip会被push
 
ebp!ebp(main)
 233
esp!eip(main)
 
接下来看f标签
在push 和movl后 ebp(f)是指向第二行那个ebp

 
ebp(main)
 233
eip(main)
esp!ebp!ebp(f)
 
同样esp在减4后向下移动1个地址
 
ebp(main)
 233
eip(main)
ebp!ebp(f)
esp!
 
接下来因为栈空间向下生长,变址寻址向上加8也就是向上2个位置(就是233)存入eax
接下来要把eax的内容存入esp指向的位置就成了

 
ebp(main)
 233
eip(main)
ebp!ebp(f)
esp!233
 
又一次的Call
 
ebp(main)
 233
eip(main)
ebp!ebp(f)
233
esp!eip(f)
进入g标签,前两句类似(其实就是存储原来ebp的位置将ebp指向新的位置),变址寻址向上加8也就是向上2个位置(就是233)存入eax
 
ebp(main)
 233
eip(main)
ebp(f)
233
eip(f)
ebp!esp!ebp(g)
 
然后666加上eax里的内容也就是c语言中的 “x +666”啦 到这里函数的嵌套总算是完事了开始一层层恢复,首先是popl ebp,ebp指向f 中的ebp位置了 同时 esp也会加4向上移动
 
ebp(main)
 233
eip(main)
ebp!ebp(f)
233
esp!eip(f)
ebp(g)
 
然后ret相当于 pop eip程序回到 刚才 eip(f)的那个位置啦
 
ebp(main)
 233
eip(main)
ebp!ebp(f)
esp!233
eip(f)
ebp(g)
 
leave实际上是 
mov %ebp %esp    

pop %ebp

也就是将上一级的栈恢复回来(有个问题为什么在g里没有leave? 我认为popl %ebp就ok了 因为当时后序操作并未改变esp,esp和ebp指向同一位置 就没必要mov %ebp %esp 了可能就被优化掉了。)

要注意mov后pop操作又再一次改变了esp指向的位置

 
ebp!ebp(main)
 233
esp!eip(main)
ebp(f)
233
eip(f)
ebp(g)
 
我们继续走ret

 
ebp!ebp(main)
esp!233
eip(main)
ebp(f)
233
eip(f)
ebp(g)
 
别忘了结果一直在存着eax,现在再加1 就是c语言里 的f(233)+1
最后leave恢复之前的 ebp和esp

ebp!esp!
ebp(main)
233
eip(main)
ebp(f)
233
eip(f)
ebp(g)
 
 跟刚开始是不是一样了!

那么到现在为止,计算机是如何工作的?

我认为计算机的工作方式就是执行一系列指令,在其中当我们只用高级语言中的“函数调用”等需要跳转时计算机总会先保存现在的状态然后再处理完成后再依次恢复之前的状态,同时需要“带回去”的数据另存在其他地方比如:eax。此外我终于明白为什么C语言只能有一个返回值了。。。。