简谈C语言编译运行时的栈

来源:互联网 发布:天敏网络机顶盒论坛 编辑:程序博客网 时间:2024/06/05 11:46

1.很多时候,我们在运行一个自以为正确的程序,但是却在实际输出的时候,遇到了困难,比如说,下面这个程序:

#include <stdio.h>int main(){int i,j;int arr[10];for(i = 0;i< 10;i++){arr[i] = i;}j = 0;while(j<10){printf("%d=%d\t",arr[j],arr[j++]);}}
我们想得到的数据输出是0=0 1=1……9=9,但是实际输出却是:1=02=1……9=8;为什么会出现这样的情况呢?我们程序明明写的没有问题,但是就怎么跑出这样的代码呢?

如果我们稍微将代码修改一下,成为下面这个样子:

#include <stdio.h>int main(){int i,j;int arr[10];for(i = 0;i< 10;i++){arr[i] = i;}j = 0;while(j<10){printf("%d=%d\t",arr[j],arr[j]);j++;}}


会发现什么?!竟然出现了我们想要的答案!0=0 1=1……9=9为什么仅仅做一次这样的修改,就能做到正确输出呢?我只是将j++放到了printf()语句的后面执行了,难道这里有什么古怪的地方?

3.通过上面的分析,我们了解到,程序在刚开始未能跑出正确的代码的很大可能原因就是语句:

printf("%d=%d\t",arr[j],arr[j++]);
出了问题,那为什么这个一句看似简单的语句就出了问题呢?这就需要回归到C语言 程序编译运行的原理上来了。

4.程序运行原理
(1)我们编写的这种控制台程序实际在计算机中跑的时候,是这样的情况,我们写的程序代码存放到程序的代码段里,程序中定义的已经初始化的全局变量和局部变量存放到一个空间中;未初始化的全局变量和局部变量存放到另外的空间中;接着我们要运行程序,运行程序是需要分配内存的,而这个动态分配内存的事情就由“堆”来完成;与此同时,一个叫做“栈”的空间存储运行时函数的形参和局部变量,也是自动的分配和释放。

(2)在IA32体系中,存在如下几个寄存器:

eax,ebx,ecx,edx;esp,edp等;

(3)程序机器指令代码在内存中呈顺序排列,所谓顺序排列,就是物理地址紧紧相连,类似分配得到的数组的序列地址一样,故称为顺序排列。

(4)C/C++函数在每次运行时,都在运行栈中有一个栈帧,每个栈帧的高地址用ebp指向,低地址用esp指向。

(5)形式参数在调用函数的栈帧中分配,局部变量在被调函数的栈帧中分配。【注意这里的被调函数指的是运行在main()函数中的其他函数,如果其他函数有形式参数,则将这些形式参数在main()函数的栈帧中分配,如果其他函数有局部变量,则这些局部变量放到该函数自己的栈帧中分配】

(6)eip指针始终指向当前执行代码的下一行。

5.下面我们来看一段简单代码:

int sum(int x, int y){  int m, n, s;  m = x;  n = y;  s = m + n;  return s;}int main(){  int r;  r = sum(10, 20);  printf("%d\n", r);  return 0;}
分析运行步骤:

这里sum()函数是被调函数,这里的main()函数是主函数。在main()函数中调用一次sum()函数,求得r值,并最终返回。

(1)Step1:根据程序的机器指令,给程序动态分配存储空间。可以看到main()函数中定义了一个局部变量r,但是r并未赋值,即意味着,暂时不用给其分配空间。

(2)接着程序执行代码运行到r=sum(10,20);记住这时候eip指向的是r=sum(10,20)的下一行即printf()函数。

(3)这是一个调用函数的过程,sum(10,20)这里的10,20是形式参数,需要将其进栈,根据分配原则,我们知道,10,20这两个变量需要存储到main()函数的栈帧当中。但是需要记住的是!将sum的两个参数的入栈顺序是从右向左20->10!!

(4)然后将eip所指的值入栈(就是将printf("%d\n",r)的机器指令所在的物理地址入栈),将sum(10,20);这条语句的机器指令的物理地址写入eip中(意味着下一时刻,执行sum()函数,而不是执行printf()输出了)。

(5)程序将现在运行的main()函数的ebp指针存放到栈中,再动态的为sum函数分配一段栈帧,esp指向其栈帧的顶部;

(6)现在进入到sum()函数中,发现sum函数中存在局部变量m,n,s;分别为其进行赋值操作m=x;n=y;即真实的用到了这些变量,需要为其动态分配存储空间,这个时候即将这些局部变量分配到sum()程序自己的栈帧当中。

(7)但是这是一个动态分配的过程,分配完成之后,将s的值返回给r;

(8)程序通过刚才将eip入栈操作取到的值(printf("%d\n",r)的入口地址拿到),从而继续从此地址出执行,最后终止程序。

整个运行过程的图形动态如下所示:


6.看到这里,大家或许明白刚才那段代码错误的原因了:

printf("%d=%d\t",arr[j],arr[j++]);
printf()本身即是一个函数,我们在调用这个格式化输出函数时,需要填写参数,这里的形式参数就是“"%d=%d\t",arr[j],arr[j++]”;但是我们知道在调用函数的时候,形式参数是自右往左边存入到main的栈帧中,即先做了j++操作,输出arr[1],而后再是输出arrr[0],故看到了我们的输出错误。



0 0
原创粉丝点击