c语言函数执行堆栈模型 c 语言的堆栈概念

c语言 :函数调用时,栈的变化问题————(有请高手高手高高手)其实想知道这些东西,单从C代码上是看不出什么来的 。反汇编后看汇编代码一清二楚 。当一个父函数调用子函数时,在父函数中先将子函数用到的参数压入堆栈,然后再以一个call指令调用子函数 。而call指令其实要做两件事:将自己的EIP值压入堆栈;以一个jmp跳转到子函数代码的开始位置 。而在子函数内,首先就是这样:
MOV EBP,ESP
SUB ESP,0x100
... ...
也就是要将当前ESP保存到EBP,然后用一个SUB指令开辟子函数所用的局部内存空间 。子函数对父函数的参数的引用一般都是EBP+4 , EBP+8这样的,这样刚好就能访问父函数压入堆栈的参数 。而对自己申明的参数的访问,就是EBP-0,EBP-4等等 。对汇编不太熟悉的只从C代码的逻辑上去分析这样的问题 , 而实际情况是C代码要转换成机器代码,机器代码做的一些工作只有在汇编中能看到 。堆栈就是一个传递参数的内存块,编译器在编译代码时 , 自动生成了子函数访问父函数参数的代码 。
不知道这样说能不能帮你解决问题 。
C语言函数调用栈程序的执行过程可看作连续的函数调用 。当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行 。函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call stack) 。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量 。
不同处理器和编译器的堆栈布局、函数调用方法都可能不同,但堆栈的基本概念是一样的 。
寄存器是处理器加工数据或运行程序的重要载体,用于存放程序执行中用到的数据和指令 。因此函数调用栈的实现与处理器寄存器组密切相关 。
AX(AH、AL):累加器 。有些指令约定以AX(或AL)为源或目的寄存器 。输入/输出指令必须通过AX或AL实现,例如:端口地址为43H的内容读入CPU的指令为INAL , 43H或INAX,43H 。目的操作数只能是AL/AX,而不能是其他的寄存器 。[5]
BX(BH、BL): 基址寄存器。BX可用作间接寻址的地址寄存器和 基地址寄存器  , BH、BL可用作8位通用数据寄存器 。[5]
CX(CH、CL):计数寄存器 。CX在循环和串操作中充当计数器,指令执行后CX内容自动修改,因此称为计数寄存器 。[5]
DX(DH、DL):数据寄存器 。除用作通用寄存器外,在 I/O指令 中可用作端口 地址寄存器,乘除指令中用作辅助累加器 。[5]
2.指针和 变址寄存器
BP( Base Pointer Register):基址指针寄存器 。[5]
SP( Stack Pointer Register): 堆栈指针寄存器。[5]
SI( Source Index Register):源变址寄存器 。[5]
DI( Destination Index Register):目的变址寄存器 。[5]
函数调用栈的典型内存布局如下图所示:
图中给出主调函数(caller)和被调函数(callee)的栈帧布局,"m(%ebp)"表示以EBP为基地址、偏移量为m字节的内存空间(中的内容) 。该图基于两个假设:第一,函数返回值不是结构体或联合体,否则第一个参数将位于"12(%ebp)" 处;第二,每个参数都是4字节大小(栈的粒度为4字节) 。在本文后续章节将就参数的传递和大小问题做进一步的探讨 。此外,函数可以没有参数和局部变量,故图中“Argument(参数)”和“Local Variable(局部变量)”不是函数栈帧结构的必需部分 。
其中,主调函数将参数按照调用约定依次入栈(图中为从右到左),然后将指令指针EIP入栈以保存主调函数的返回地址(下一条待执行指令的地址) 。进入被调函数时,被调函数将主调函数的帧基指针EBP入栈 , 并将主调函数的栈顶指针ESP值赋给被调函数的EBP(作为被调函数的栈底),接着改变ESP值来为函数局部变量预留空间 。此时被调函数帧基指针指向被调函数的栈底 。以该地址为基准 , 向上(栈底方向)可获取主调函数的返回地址、参数值,向下(栈顶方向)能获取被调函数的局部变量值,而该地址处又存放着上一层主调函数的帧基指针值 。本级调用结束后,将EBP指针值赋给ESP,使ESP再次指向被调函数栈底以释放局部变量;再将已压栈的主调函数帧基指针弹出到EBP,并弹出返回地址到EIP 。ESP继续上移越过参数,最终回到函数调用前的状态,即恢复原来主调函数的栈帧 。如此递归便形成函数调用栈 。

推荐阅读