欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

函数内存分配

程序员文章站 2022-04-14 19:14:00
在最近的代码调试中,遇到一个比较棘手的崩溃问题,现象为程序在函数的返回值处崩溃,报警提示如图: 经过排查,最终发现在对结构体内数组初始化赋值时出现了数组越界现象,导致函数在返回时出现异常,导致程序崩溃,借此机会,对内存栈内空间的函数占用空间总结学习: 1. 进程的内存布局 对于一个进程来说,它在内存 ......

在最近的代码调试中,遇到一个比较棘手的崩溃问题,现象为程序在函数的返回值处崩溃,报警提示如图:

函数内存分配

经过排查,最终发现在对结构体内数组初始化赋值时出现了数组越界现象,导致函数在返回时出现异常,导致程序崩溃,借此机会,对内存栈内空间的函数占用空间总结学习:

1. 进程的内存布局

对于一个进程来说,它在内存中的布局如下所示:

函数内存分配

  代码区与常量区等不再赘述,堆区是由代码动态的申请与释放,只在部分情况下如果代码中没有对申请的内存进行释放,在程序结束的时候os会进行释放。栈区的空间是由编译器自动的分配和释放的。此处需要注意的是:

(1)堆向高内存地址生长,而栈向低内存地址生长;

(2)堆和栈相向生长,他们之间有个临界点,称为stkbrk。

2. 程序如何在进程中映射

对于一个程序,它的执行顺序为:

main( )-->fun1( )-->fun2( )...fun3( ),即主函数逐个调用内部子函数,其内部的映射如下所示:

 函数内存分配

如上图:

1)随着函数调用层次的增加,函数帧栈逐块向低地址方向延伸;

2)当各函数调用返回后(进程中函数的调用层减少),帧栈会被逐块被遗弃而向高内存地址方向缩回;

3)每个子函数的帧栈大小与函数的性质相关,由函数内部非局部变量数量决定;

4)未初始化数据区(bss):用于存放程序的静态变量,这部分内存都是被初始化为零的,而初始化数据区用于存放可执行文件的进程所共享;

5)当出现未初始化数据区(bss)或者stack(栈区)的增长耗尽了系统分配给进程的*内存的情况下,进程 将会被阻塞,重新被操作系统用更大的内存模块来调度运行;

6)每一个函数的帧栈包括:函数的参数(关于被调用函数的参数时放在调用函数的帧栈还是被调用函数的帧栈,依赖于不同的系统实现),函数的帧栈中的局部变量以及恢复该函数的主调函数的栈帧(前一个栈帧)所需的数据,以及主调函数的下一条指令的地址。

3. 函数的栈帧

一个函数建立的栈帧包含如下的信息:

1)主调函数的返回地址。

2)为函数的局部变量分配的栈空间。

3)位被调函数的参数分配的空间。

4)函数的返回地址。

关于函数内数据的占用空间细节,可以通过以下代码了解:

//全局初始化区

int i1 = 0;

int i2 = 0;

int i3 = 0;

 

//全局初始化区

static int i4 = 0;

static int i5 = 0;

static int i6 = 0;

 

//全局未初始化区

int i7;

int i8;

int i9;

int main()

{

    cout << "打印全局初始化区变量i1-i3的地址:" << endl;

    cout << &i1 << " " << &i2 << " " << &i3 << endl;

 

    cout << "打印全局初始化区静态变量i4-i6的地址:" << endl;

    cout << &i4 << " " << &i5 << " " << &i6 << endl;

 

    cout << "打印全局未初始化区变量i7-i9的地址:" << endl;

    cout << &i7 << " " << &i8 << " " << &i9 << endl;

 

    int data1 = 'a';

    int data2 = 'b';

    int data3;

    int data4;

 

    cout << "打印函数内局部变量data1-data4地址,其中data3,data4未初始化" << endl;

    cout << &data1 << "  " << &data2 << "  " << &data3 << "  " << &data4 << "  " << endl;

 

    char *pstr1 = "12345";//12345在常量区,pstr在栈上

    char *pstr2 = "1122";

 

    void *p = pstr1;

    void *q = pstr2;

    cout << "打印常量地址" << endl;

    cout << p << endl;

    cout << q << endl;

 

    static int adata = 0;//全局(静态)初始化区

    cout << "打印局部函数中定义静态变量地址,对比其他全局区地址" << endl;

    cout << &adata << endl;

 

    int *p1 = new int;//堆区

    int *p2 = new int;//堆区

    cout << "打印堆区地址" << endl;

    cout << p1 << endl;

    cout << p2 << endl;

 

    getchar();

    return 0;

}

 

运行结果如下:

 函数内存分配

  观察上面的地址打印信息可以发现,全局未初始化区的变量是按从高到低地址按申明定义的顺序压栈,变量i7紧邻全局初始化段的第一个变量i1。而全局初始化段的变量(包括静态,不做区分的)从低地址到高地址按申明的顺序压栈(不是指上面所指的栈区,请区别开来,这是就地址变化过程而言的,它与局部函数变量起始地址完全不同)。函数在程序代码段中地址是按申明顺序递增的。函数局部变量在栈去是按照申明顺序从高到低的地址进栈的。

常量的开始地址来看跟全局变量应该属于一个区。堆区的地址开头也是另外一个段。

补充一点:数组变量内部元素是按照元素下标从低地址到高地址压栈的。

一般局部变量一般是从高低地址到低地址压栈的。

  通过以上的总结,也就解释了本文开头处的崩溃问题,当函数内的局部变量赋值时出现越界,导致了存放了函数返回地址的空间被影响,使得被调用的函数返回时出现异常,导致程序崩溃。

经验有限,本文中可能存在着自己的一些理解错误之处,后续会继续修改完善,也希望大家指正。