C语言内存分配
- 栈区(stack):静态内存区,声明的基本数据类型和类的引用保存在该区,栈区的大小是由操作系统动态配置的(自动分配,自动释放);
栈区的较小,一般只有2M(与系统类型有关); - 堆区(heap):堆区由程序员分配和释放,最大能够分配操作系统内存的80%;
- 全局区或静态区
- 字符常量区
- 程序代码区
栈内存
栈一般用来存储基础类型数据,静态数组,其内存空间较小,一般只有2M(系统而定),具有以下特点:
1、栈内存属于静态内存,保存在栈中的数据需要在编译器就确定占用的内存大小;
2、栈内存小(2M),如果申请过量,容易栈溢出;
3、因为在编译器已经确定了大小,为了防止内存不足,不得不分配更多内存;
4、栈内存持有固定,并且由系统控制释放(比如函数执行完,其栈内存才会被释放);
申请栈内存:1
2
3// 静态内存分配下,创建数组,数组的大小是固定的,不能通过变量来赋予
// 存储在栈内存中,必须编译期就指定数组的大小
int a[10];
堆内存
堆内存对应的是动态内存:
1、程序占用的堆内存可以在运行期动态指定;
2、动态内存理论上可以让我们任意的申请,可以申请80%的系统内存;
3、动态内存的持有和释放由我们自己指定,所以我们随时释放、申请、重用内存;
堆内存的获取通过malloc和calloc来动态申请指定大小的内存:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29int len;
printf("输入数组长度:");
// 为什么此处需要用&?,类似于指针,只有取到其内存地址,才能真正操作其内容
scanf_s("%d", &len);
// 动态数组放在堆区中,相当于Java中的集合,可以动态指定大小
// malloc返回一个void* 指针,表示任意类型的指针,可以使用任意类型指针进行接收
// 通过指针可以操作开辟的内存
//int* p = malloc(len*sizeof(int));
// 等价于
int*p = calloc(len, sizeof(int));
int i = 0;
for (; i < len; i++){
// 可以以数组的方式操作指针
p[i] = i;
}
i = 0;
for (; i < len; i++){
// 可以以数组的方式操作指针
printf("%d ", *(p + i));
}
// 堆内存需要我们手动释放
if (p != NULL){
free(p);
p = NULL;
}
如果原先申请的内存不够,可以通过realloc进行重新分配:1
2
3
4
5
6
7
8
9// 可以再次扩大原来申请的内存
// 参数2指定了重新扩大后的大小
// 注意:扩充之后,可能会分配新的地址空间
int* p2 = realloc(p, sizeof(int)* 10);
if (p2 != NULL){
free(p2);
p2 = NULL;
};
注意,重新分配的内存的首地址可能不是原来的首地址,因为内存块可能是完全新的:
动态内存分配的情况:
缩小,缩小部分会丢失;
扩大,由于分配的内存地址是连续的,分为3种情况:
1、原先的内存地址,进行扩充时,发现后续地址已经被别的应用程序使用了,无法扩充达到需求,此时会去堆中查找第一块合适的内存,把首地址返回,并把原来的内存块清空;
2、如果原来的内存地址中,后续地址能够被用来扩充(没有程序占用),则直接扩充,把原地址返回;
3、申请失败,返回null,原来的指针仍然有效;
动态内存注意点:
1、同一个指针不能连续调用free(指针已经被释放,又再释放),将会报错;
2、对于已经free的指针,需要主动置NULL,用来标志该指针已经释放内存(这样做的目的是为了防止指针被多次free,每次free前都应该判断指针是否为NULL)1
2
3
4if (p != NULL){
free(p);
p = NULL;
}
3、每次申请内存都应该对应一次free指针的操作,否则对指针重复申请内存将会导致内存泄漏;