一,进程空间与数据分布
程序,是经源码编译后的可编译文件,可执行文件可以多次被执行,比如我们可以多次打开某个应用。
进程,是程序加载到内存后开始执行、至执行结束,这样的一段时间概念,多次打开的应用,每打开一次都是一个进程,当我们关闭该应用时,该进程也就结束了。
程序是静态概念,而进程是动态/时间概念。
接下来,通过一个程序实例来说明:
include<stdio.h>
int a; //a为全局变量且未初始化,位于bss区
static int b; //b为静态变量且未初始化,位于bss区
int c=2; //c为全局变量、已初始化为2且可读可写,位于rw区
static int d=4; //d为静态变量、已初始化为4且可读可写,位于rw区
int main()
{
int m=5; //m为局部变量,位于stack区
static int n=6;
//n为静态变量、已初始化为6,且可读可写,位于rw区
char *p="china"; //p为局部变量,位于stack区
int array[10]={0,1,2,3,4,5,6,7,8,9};
//array为数组,位于stack区
int *q=(int *)malloc(100);
//malloc动态申请的空间位于heap区
int fun();
}
int fun()
{
int m; //m为局部变量,位于stack区
static int n; //n为静态变量、且未初始化,位于bss区
return 0;l
}
二,函数压栈与出栈
1,普通函数
以下面的代码为例,我来展示程序运行时进程空间的数据分布:
#include<stdio.h>
int m=10;
int fun(int a,int b);
int main()
{
int i=4;
int j=5;
m=fun(i,j);
return 0;
}
int fun(int a,int b)
{
int c=0;
c=a+b;
return c;
}
cpu中有三个寄存器,分别为:
- eip——指向代码区将要执行的指令,有两种移动方式,分别是“顺序”和“跳转”;
- ebp——始终指向栈底;
- esp——始终指向栈顶;
接下来,我将逐步以图文结合的方式来分析程序的运行过程:
1,初始状态时,程序尚未运行,栈空间为空、eip指向代码区的第一条指令——int main(),而ebp和esp则指向“内核设置的初始状态。如图所示:
2,程序开始执行main函数的第一条指令时,main函数在栈区建立“main函数栈空间。ebp的地址压栈——被保存到“main函数栈空间”以便结束后返回到初始状态。esp会移动到“main函数栈空间”的栈顶。而此时的ebp也会移动到“main函数栈空间”的栈底。eip指向下一条指令(int =4)。如图所示:
3,main函数栈空间中会开辟一个空间 i,赋值为4。ebp不移动。esp移动到栈顶。eip指向下一条指令(int j=5)。如图所示:
4,main函数栈空间中开辟一个空间 j ,赋值为5。ebp不移动。esp移动到栈顶。eip指向下一条指令( m=fun() )。如图所示:
5,此过程要传递两个参数。这两个参数均保存在“main函数栈空间”中。但却要被“fun函数栈空间”所使用。main函数栈空间中为两个参数a和b开辟空间。传参顺序与代码书写顺序正好相反。j 的值会传给a,而 i 的值会传给b。ebp不移动。esp移动到栈顶。如图所示:
6,此时,main函数栈空间会开辟一个空间来保存fun函数的返回值。然后,main函数栈空间再次开辟一个空间保存fun函数的返回地址,以便fun函数返回时能找到原来的位置。ebp不移动。esp移动到栈顶。如图所示:
7,eip跳转到fun函数的第一条指令( int fun() )。main函数栈空间开辟一个空间用来保存此时ebp的指向位置,即main函数栈空间的栈底地址。这样的话,当fun函数执行完、返回后,ebp能重新回到main函数栈空间的栈底。如图所示:
8,此时,开始构建fun函数栈空间。ebp移动到“fun函数栈空间”的栈底。实际上,由于此时栈内并没有数据,栈底与栈顶处于同一地址,ebp与esp指向重合。eip指向下一条指令(int c=0)。执行这一语句,fun函数栈空间会开辟一个空间C,值为0。ebp不移动。esp移动到栈顶。eip指向下一条指令( c=a+b )。如图所示:
9,此时,main函数栈空间中,a和b的值相加然后赋给C。C值变为9。ebp和esp均不移动。eip指向下一条指令( return 0 )。如图所示:
10,fun函数运行结束,其返回值会传到main函数栈空间的“保存fun函数返回值”的空间中,然后赋值给m。如图所示:
11,fun函数执行完毕。此时有三个任务:
ebp和esp重新指向main函数栈空间的栈底和栈顶,eip只想main函数中将要执行的指令。
fun函数返回到原来地址,继续执行main函数指令。
fun函数栈空间被回收释放
如图所示:
12,main函数返回后,程序执行结束。ebp和esp回到初始状态——内核设置的初始指向地址。main函数栈空间被清空。静态数据区清空。代码区也被清。程序执行结束!