简谈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],故看到了我们的输出错误。
- 简谈C语言编译运行时的栈
- Java运行C语言编译
- C 语言的程序的编辑,编译和运行
- linux下c语言的编译调试运行
- UltraEdit编译C语言程序并运行的示例
- C语言基础 编译成功的exe运行时闪退
- sublime编译运行c语言后中文乱码的解决
- C语言两个文件模块的编译运行
- C语言中 sizeof 运算的值是在编译时还是运行时确定的?
- C语言中 sizeof 运算的值是在编译时还是运行时确定?
- 【iOS沉思录】Objective-C语言的动态性总结(编译时与运行时)
- linux C 语言入门 编译链接运行
- ubuntu 下编译运行C/C++语言
- 关于 c语言的编译 和编译后程序的运行
- VS2012编译运行VS2013的程序以及VS中运行C语言
- c语言编译时的一个问题
- 命令行编译运行Go语言时参数代入的问题
- C语言的运行系统
- Visual Studio 2017 中的 C++ 一致性改进
- 【工具库】--kafka安装(179)
- python正则表达式系列(1)——正则元字符
- 单源有权图的最短路径 Dijkstra算法(证明不能解决负权边)7.1.2
- 数字图像处理实验(2):PROJECT 02-02, Reducing the Number of Gray Levels in an Image
- 简谈C语言编译运行时的栈
- MySQL
- 不同的SQL JOIN对比
- POJ 2774-Long Long Message(后缀数组+高度数组-最大公共子串长度)
- linux系统之arm架构的CPU与Cache
- Java中的Runnable、Callable、Future、FutureTask的区别与示例
- C#写入数据库访问层时update正常执行后数据库并没改变
- 数据库<1.1> 字段的约束及属性
- 数字图像处理实验(3):PROJECT 02-03, Zooming and Shrinking Images by Pixel Replication