在C语言编程中,栈上开辟的int 、char、定长度数组,空间大小固定、无法灵活调整。但实际开发中,很多场景需要程序运行时才确定内存大小、动态扩容 / 缩容,这就必须掌握动态内存管理。
int vsl = 20; 在栈空间上开辟4字节
char arr[10] = {0}; 在栈空间上开辟10字节
我们之前所学的变量创建是在栈区上,根据类型字节的大小分配空间,但是这样的开辟空间方式有两个特点:
编译时就确定
,运行中无法修改。而实际需求往往是动态的:比如用户输入数据量不确定、列表需要随时增减元素。C 语言引入
堆区
动态内存分配
,由程序员手动申请、释放,完美解决上述问题,灵活可控!函数原型 void* malloc(size_t size);
#include<stdlib.h>
void*
类型的起始地址(需强制类型转换);失败返回NULL
(内存不足时),必须判空。未初始化
,是随机值。
函数原型 void free(void* ptr);
#include<stdlib.h>
释放
ptr指向的堆区动态内存
。不是动态开辟
的,那么free函数的行为是未定义的
;如果参数ptr是NULL指针
,则函数什么事都不做。
函数原型 void* calloc(size_t num,size_t size);
自动初始化为 0
。

realloc函数的出现让动态内存管理更加灵活。
有时候我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们⼀定会对内存的大小做灵活的调整。那realloc
函数就可以做到对动态开辟内存大小的调整。函数原型 void* realloc(void* ptr,size_t size);
保留原数据
。ptr为原内存地址
,NULL时等价于malloc;size为调整后的新字节数
。可能与原地址不同
);失败返回NULL,原内存不变,不释放。直接追加
,返回原地址
;返回新地址
。

malloc、realloc、calloc函数返回值可能为NULL,直接解引用会崩溃。

申请10个int,访问第11个int,越界访问。

free(栈区变量),行为未定义。

指针偏移后free,行为未定义。

同一块内存被多次释放后,程序崩溃。


动态内存忘记释放,程序运行占用内存不释放。



改进方法1:使用二级指针作为参数。


改进方法2:函数返回指针。



改进方法1: 使用静态区内存



C99 规定:
结构体
最后一个成员可以是未知大小的数组
,称为柔性数组// 写法1:常用
struct S
{
int n;
int arr[];柔性数组成员
};
// 写法2:部分编译器支持
struct S
{
int n;
int arr[0];
};
sizeof(结构体)
不包含柔性数组的内存。struct S
{
int n;
char c;
int arr[];
};
int main()
{
printf("%zun",sizeof(struct S)); 8
return 0;
}
struct S
{
int n;
int arr[];柔性数组成员
};
int main()
{
struct S* p=(Struct S*)malloc(sizeof(struct S)+10*sizeof(int));
1.一次malloc,同时包含结构体n+arr数组空间
if(p==NULL)
{
perror("malloc");//给出错误信息
rerturn 1;
}
p->n=100;
for(int i=0;i<10;i++)
{
ps->arr[i]=i+1;
}
free(p);//一次释放空间
p=NULL;
return 0;
}
struct S
{
int n;
int* arr; 指针变量
};
int main()
{
struct S* p=(struct S*)malloc(sizeof(struct S));
1.只开辟了int n + int* arr的大小,此时arr是野指针,没有指向任何可用数组空间
if(p==NULL)
{
perror("malloc");
return 1;
}
p->n=100;
int* ptr=(int*)malloc(10*sizeof(int));
2.单独创建arr指向的数组空间
p->arr=ptr;
for(int i=0;i<10;i++)
{
ps->arr[i]=i+1;
}
free(p->arr); 3.第一次释放数组数据空间
free(p); 4.第二次释放结构体空间
p=NULL;
return 0;
}
n与arr紧紧挨在一起。一次 free 全部释放
,不会漏释放,不易内存泄漏。必须先后两次 free
,任意一次遗漏都会内存泄漏。总结:柔性数组 = 一体连续内存,一次释放;指针成员 = 分体内存,两次释放。

动态内存
(malloc/calloc/realloc),手动分配释放,向上增长,空间大。