从上面的汇编代码可以看出,不仅仅有局部变量和参数被压入了运行栈,还有帧指针和返回地址信息,返回地址信息是由call函数压入,call函数包含两个步骤,一是将call后面的第一条指令的地址压入栈中,然后跳转到被调函数的第一条指令(函数入口)。这两个信息是前面所说的”控制信息“。
  eax寄存器是用来保存函数的返回值,这也是为什么函数返回的时候仅仅能返回一个值的原因。
  总结来说,操作系统中的运行栈有操作系统和硬件的支持,但是和数据结构中的栈模型是一样的(FIFO),在进行参数传递的函数调用的时候:
  首先将实参压入了栈,以供被调函数访问(这里实际上是为实参申请了空间,然后被调函数进行计算从而访问,并不是调用函数过后,给形参分配空间然后将实参赋值给形参,这样明显会浪费资源----实参保存的资源),这也是C语言中传递指针和变量的不一样之处了,但是本质上都是对实际参数进行了存储。
  其次使用call,将主调函数的下一个下一个指令的地址压入了栈,以便函数调用完成后进行返回,并且跳转到被调函数的函数入口。
  继而在被调函数中将ebp指针压入运行栈(每一个函数在运行时在栈中都是一个栈帧,主函数在调用之前也是有一个ebp的值,这个值是用于对主函数中的数据进行随机访问,现在到了被调函数,必须将ebp的值保存,以便函数调用完成后可以继续对主函数进行相关数据操作),当然接下来是要生成当前函数(被调函数)的ebp了,将esp赋值给ebpOK!
  然后是对被调函数的一系列相关数据操作,这些操作都是基于ebp计算数据位置从而读取数据来进行的,ebp像访问一个数组的下标参考值0一样,根据操作不同进行计算,不过数组中计算的是下标值,这里计算的是地址而已。
  完成被调函数后,便恢复开始的esp和ebp,前面已经压入了ebp,而对于esp,将ebp的值赋值给esp完成,当然,学过数据结构中的栈的都知道,虽然栈指针向低地址移动了,数据有时候并没有消失,只是栈这个模型的规定,从而感觉像元素被弹出了,实际上还是在那个地方,只是下次的操作会覆盖它。
  后ret,弹出栈顶元素,也是返回地址,继而返回到原函数,整个函数调用完成。
  后,是我深的领悟:我们平时在写函数的时候,返回类型,函数名,参数类型变量类型等等都是硬式的规定,但是,主要是为什么会有这些规定,有一句话是这样说的“一个错误,与其被淹没在运行中,不如暴露在编译时”,C语言对这方面的控制确实有点不太好,Java(个人比较喜欢)有优势在这方面,但是当真正的深入到汇编代码时却发现这一切其实都是假象,压根没有什么类型,有的只是地址的计算,所有这一切的规定都是由于编译器需要,为了让编程尽量少出错,由于本人并没有学习过编译,所以这个发现确实让我很惊讶。然而,从上面的汇编代码可以看出,参数传递的时候是首先压入栈的,而后对其调用,所以这有了一个很大的问题,C语言的函数原型存在的意义也是这个,但是它并没有对参数进行检查,所以当你被调函数要求是double,而传入的是int的时候,并不会进行转换,谁都不知道以后会出现什么问题。所以,良好的编译器总能在编译器发现更多的错误,有助于编写良好的代码。