很久前就想把自己的想法好好记录下,总是觉得没时间,主要是自己的一点借口吧。
前段时间给女朋友讲c语言的程序运行总过程,本来以为这些过程也不过那样,结果讲完发现这里面还真的有不少东西,这些东西也同样是很多初学者,甚至是编程高手都没注意过的。本文只是记录下自己的想法,也许有很多不对的地方,期待后期自己理解和专家们的指导。
首先是编译过程,编译器会吧.h文件直接copy到.c文件中(include过),每个.c文件都是独立的,然后编译器将c语言语句转换成一条条汇编代码,然后在转换成2进制码也就是.o文件,几个.c文件就生成几个对应的.o文件。
然后就是链接过程,这个过程就是将生成的.o以及系统提供的lib相关的调用关系链接起来,然后去掉没用到的模块和边边角角,精简成一个.exe或者其他格式的可执行文件。
当操作系统通过某些接口调用该模块(.exe文件),就相当是为之开辟一个进程,这时此模块有自己的内存空间,系统为之分配 代码区,静态存储区,堆区,栈区。
静态存储区:主要存储全局变量,静态变量,常量。全局变量和静态变量好理解,常量举个例子:char *p="abcdef";其中p就是局部指针指向常量区数据"abcdef"
堆区:就是malloc和free等函数操作的内存区,这段区分配资源指导该模块运行完毕退出操作系统,才会释放,所以为了避免内存泄露,申请了必须free掉。还有一点需要注意,malloc申请时是有大小的输入(多少字节),但是free函数接口只有提供一个首地址指针即可,这样做的原理就是操作系统在管理堆区的时候在申请区前面添加了一两个字节来标明申请的大小,调用free函数的时候,会首先查找提供给free的首地址前两个字节,然后再按照指示释放长度。
栈区:在函数调用时和调用前动态的管理的一段内存块;这块区域主要是为局部变量存在以及在函数调用时保存现场的一个过程。
举个例子:
int* sb()
{
int a;
a =11;
return &a;
}
int* sb2()
{
int *b;
b = malloc(sizeof(int));
*b=111;
return b;
}
void hehe()
{
int a=0;
int b=0;
int c=0;
}
void main()
{
int* p;
int* pb;
pb =sb2();
p=sb();
hehe();
// printf("%d\n",*p);
// printf("%d\n",*pb);
}
生成可执行文件后,系统调用该模块,然后从main函数开始后内存空间如下:
1、运行到main第25行空间如下:每个框框4字节
2、调用sb2函数过程:
返回后:
此时sb2函数中参数虽然没有了但是他的内容还在内存里,只是不受控制了。
3、调用sb函数:
调用过程:
返回:
这里有个问题,就是利用局部变量返回地址后,虽然该地址区可以访问,但是该区域已经不受保护,可以被任何调用函数修改其值,所以当接下来没有函数调用时,则该区域是安全的能读能写,而且逻辑正确,但是一旦有函数调用则该区域很容易被修改成不正确的值。
4、调用hehe函数:
此时sb函数中返回给p的指针内容已经从11被修改为0。
最后返回;
这个例子只是熟悉下调用过程中堆区和栈区的动态变化过程,由于时间关系没有涉及实参和形参的调用关系,其实都是局部变量,调用不过是将局部变量的值从实参给到形参,多在栈区添加一些局部变量而已;
其实在栈区操作中,一些不好的代码或者说很有意思的代码可以实现隐藏的死循环,比如数组访问过界修改了指令返回地址,导致返回地址又跳到前面继续进行下面语句,则会一直不停的循环,导致死循环。