堆栈在计算机程序运行时发挥强大的作用,能用于临时存储大量的数据,也便于数据的查找。作为一名编程爱好者,小编大学时曾经阅读过《操作系统真相还原》这本书,并根据此书提供的源码动手实践去写操作系统,也为堆栈的奇妙作用感到惊叹。了解程序运行时堆栈的变化情况,对于研究计算机底层原理以及操作系统的学习都有很大的帮助,也能一定程度上利用该知识分析某个算法的空间复杂度。小编今天就分享下C语言的函数在调用时函数的变化情况。
小编本次用的为window系统,采用Intel汇编语法格式,当然也可以在Linux下进行该操作,其采用的是AT&T的汇编语法格式。首先,小编先简单写了个C程序,用于实现两个数的相加。因为小编重装了系统,为了方便下载了VC++6.0来进行演示。截图如下:
我们在VC++6.0里面插入断点,然后打开反汇编界面。调出程序运行时寄存器变化的展示框,查看堆栈的变化情况(栈底与栈顶),截图如下:
现在我们着手来画堆栈图。
从上图的Registers框中,我们可以知晓程序进行函数调用前的栈顶(ESP=0019FEE4)与栈底(EBP=0019FF30),图中所有数据皆为16进制表示,据此先画出图1的堆栈图。断点调试(在VC中按下F11),接下来执行的指令为push 4,push 5,此时栈顶发生变化(执行push指令ESP+4),画出堆栈的变化如图2所示。
接下来执行call这条指令,进行函数调用,汇编语言中执行call指令时会将其下一条指令的地址压入堆栈,从图汇编代码1中可以看到call指令的下一条指令地址为00401041,画出图3。EIP代表程序当前执行的指令的地址。调用函数Add里的第一条指令为push ebp,据此我们继续画出接下来的堆栈变化图如4所示。
接下来函数调用堆栈的变化图如下所示。继续执行指令mov ebp,esp、sub esp,44、push ebx、push esi、push edi。这里push这些寄存器是为了保存现场,防止原有的数据丢失。注意sub esp,44中的44为16进制,转化为10进制为68。内存每一小块为4字节,也就是共有17个小块。
接下来继续执行下面的汇编指令。对于汇编指令不懂的大家可以自行上网查看。在图10中我们可以发现,我们通过mov eax,dword ptr [ebp+8]的方式取得我们之前压入的变量,并进行对应的加法操作等。执行pop指令栈顶下降(ESP-4),有一点需要注意,就是函数调用后要保持原先堆栈的平衡,pop ebp恢复函数调用前的栈底ebp,用add esp,8把函数调用前的栈顶恢复,也就是把堆栈恢复到图一的样子。
大家也可以自行动手用编译器调试下,动手画下堆栈图,“纸上得来终觉浅,绝知此事要躬行”。