当我们在运行下面一段代码时,会抛出stack overflow的异常:
#include <stdio.h>
void main(){
int i[1024 * 1024 * 10];
getchar();
}
这个错误直译过来就是栈溢出,这里面就涉及到C语言的内存区域的分配问题。
C语言内存区域划分
1、栈区(stack) 先进后出的内存结构,所有的自动变量、函数形参都存储在栈中
每个线程都有自己的栈帧
栈内存尺寸固定,超过则引起栈溢出,如上面的代码:
自动分配(申请方式如:int i[10];),变量离开作用域后自动释放
2、堆区 (heap) 和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序
- 申请方式: int *p = malloc(1024);
由程序员手动分配和释放,
- 可分配约占操作系统80%内存
3、全局、静态区
静态区存放程序中所有的全局变量和静态变量。
4、字符常量区
常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区
程序被操作系统加载到内存的时候,所有的可执行代码(程序代码指令、常量字符串等)都加载到代码区
这块内存在程序运行期间是不变的。
代码区是平行的,里面装的就是一堆指令,在程序运行期间是不能改变的。
动态分配内存
1、malloc函数:向堆申请开启指定大小的内存区域
//分配40M内存
int* p = malloc(1024 * 1024 * 10 * sizeof(int));
2、realloc()函数:调整已分配的内存区域
//
int* p = malloc(100 * sizeof(int));
//发现内存不够用,重新分配内存;
int* p2 = realloc(p, sizeof(int) * 200);
3、free()函数:释放已分配的内存区域;
//开辟40M内存
int* p = malloc(1024 * 1024 * 10 * sizeof(int));
//释放
free(p)
区别:
静态内存分配创建数组,数组大小固定,必须在指定数组长度。
int i[3];
int ids[] = {10, 20, 22};
动态内存分配创建数组,开辟一段内存,然后在这段内存上赋值数组,在程序运行过程中,可以随意的开辟指定大小的内存以供使用,相当于java中的集合。
int len = 1024; //指针长度
//开辟内存,1024*4个字节
int *p = malloc(len * sizeof(int));
int i = 0;
//给数组赋值
for(; i < len; i++){
p[i] = rand();
}
静态内存分配,内存分配的大小是固定的,有以下问题
- 容易超出栈内存的最大值;
- 通常为了防止内存不够用,会开辟更多地内存,容易造成内存浪费
动态内存分配,在程序运行过程中动态指定需要使用的内存大小,手动释放,释放后这些内存还可以被重新利用。同时可以重新分配内存区域的大小
关于realloc重新分配内存:
1、缩小内存,即分配后的内存区域大小比原来的内存区域要小,则直接截去多余的内存,返回的指针和原来的指针指向 地址相同,返回原指针。
2、扩大内存:即分配后的内存区域要大于原内存大小。
(1)因为分配的内存是连续的,若当前指针后的内存区域后续内存端足够分配,则直接在后续的内存区域分配给当前指针,返回原指针。
(2)若当前指针后续内存区域不足以分配,则使用堆中第一块满足这一条件的内存块,并把当前的数据复制到新的内存块中,返回新的指向改内存块的指针,原内存块被释放。
(3)堆中内存不足,申请失败,返回NULL,原来的指针仍有效;
内存分配的几个注意事项
- 内存不能重复释放,所以一般在释放前做一个NULL判断:
- 养成一个良好习惯,在释放掉一个内存后,并置为空NULL;
if( p != NULL){ free(p); //可以看到p仍有值 printf("%#x\n", p); p = NULL; }
- 重复给一个变量调用malloc函数,要在合适的时机调用free,否则造成内存泄漏
//第一次分配内存 int *p = malloc(1024 * 10 * sizeof(int)); //第二次分配内存 p = malloc(1024 * 4 * sizeof(int)); //释放内存 free(p);
这里调了一次free释放内存,然而调用了两次内存分配开辟了两段内存区域,第一次p指向了第一段内存区域,第二次指向了新分配的内存区域,调用了free释放的是第二段内存,则第一段内存仍没有得到释放,从而造成内存泄漏