5、程序内存模型和函数调用模型
1、内存四区模型
1.1、内存四区的建立流程

图1、内存四区模型
流程说明:
(1)操作系统把物理硬盘代码load到内存
(2)操作系统把c代码分成四个区
(3)操作系统找到main函数入口执行
一个由c/C++编译的程序占用的内存分为以下几个部分:
(1)栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
(2)堆区(heap: 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
(3)全局区:主要包括静态全局区和常量区,如果要站在汇编角度细分的话还可以分为很多小的区。
全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。
常量区 :常量字符串就是放在这里的。 程序结束后由系统释放
(4)代码区:存放函数体的二进制代码,有操作系统管理的。
1.2、堆栈的生长方向
堆栈的生长方向和存放数据增长方向不一样
图2、堆栈的生长方向
2、函数调用模型
2.1、 函数调用模型
(1)入栈的过程
程序开始运行时,操作系统会找到程序的入口函数main。操作系统会把main函数的返回地址入栈,然后把main函数的参数入栈,如果main中有局部变量,还会把这些变量入栈。
main函数中调用fa函数时,操作系统把main函数的运行状态入栈,把fa的返回地址入栈,把fa的参数入栈。
fa函数调用fb函数时,操作系统把fa函数的运行状态入栈,把fb函数的返回地址入栈,把fb的参数入栈。
入栈的过程如图:

(2)出栈的过程
fb函数执行完成后,根据fa函数的运行状态,返回fa继续执行。
fa函数执行完成后,根据main函数的运行状态,返回main继续执行。
如图:

char*fa() { char*pa = "123456";//pa指针在栈区,“123456”在常量区,该函数调用完后指针变量pa就被释放了 char*p = NULL; //指针变量p在栈中分配4字节 p=(char*)malloc(100);//本函数在这里开辟了一块堆区的内存空间,并把地址赋值给p strcpy(p, "wudunxiong 1234566");//把常量区的字符串拷贝到堆区 return p;//返回给主调函数fb(),相对fa来说fb是主调函数,相对main来说,fa(),fb()都是被调用函数 } char*fb() { char*pstr = NULL; pstr = fa(); return pstr;//指针变量pstr在这就结束 } void main() { char*str = NULL; str = fb(); printf("str = %s\n",str); free(str); //防止内存泄露,被调函数fa()分配的内存存的值通过返回值传给主调函数,然后主调函数释放内存 str = NULL;//防止产生野指针 system("pause"); }
2.2、函数变量的使用范围
(1)fa分配的内存可以被函数fb使用吗?
fa可以在栈区申请内存(局部变量), 可以在堆区申请内存(malloc), 可以在数据区申请内存(全局变量,字符串常量等)。 这些区的内存fb都可以使用吗?
fb可以使用fa申请的这些内存。因为函数fb执行时,fa函数还没有return。所以所以内存空间都没有被析构。
(2)fb分配的内存可以被函数fa使用吗?
fb在栈上分配的内存,不能被fa使用。 因为fb函数return以后,栈上的内存就被析构了。int a=0; char * p = NULL;
fb函数malloc的内存,可以被fa函数使用。因为malloc的内存放在堆中,fb执行结束后,内存没有被析构。malloc(sizeof(char)*10); //申请10字节内存
fb函数中数据区内存,可以被fa函数使用。因为数据区内存,fb执行结束后,内存没有被析构,虽然fb中不会有全局变量,但是可以有字符串常量 char *pstr1 = "adfag";
PS: 可见,主调函数要想使用被调用函数分配的内存,需要用指针做函数参数或返回值,内存必须是堆区内存或数据区内存,栈区内存会被析构。
2.3、C++编译器为每个应用程序建立几个内存四区?
一个主程序有n函数组成,c++编译器会建立有几个堆区?有几个栈区?一个堆区,一个栈区,即为每一个应用建立一个内存四区。
2.4、总结
(1)主调函数分配的内存空间(堆,栈,全局区)可以在被调用函数中使用,可以以指针作函数参数的形式来使用
(2)被调用函数分配的内存空间只有堆区和全局区可以在主调函数中使用(返回值和函数参数),而栈区却不行,因为栈区函数体运行完之后这个函数占用的内存编译器自动帮你释放了。
(3)一定要明白函数的主被调关系以及主被调函数内存分配回收,也就是后面接下几篇总结的函数的输入输出内存模型。

浙公网安备 33010602011771号