C语言动态内存 + 柔性数组 + 数组名退化
一、动态内存核心函数
- malloc(size_t size)
- 在堆区申请连续内存,返回 void* 地址;申请失败返回 NULL
- 模板:
int p = (int )malloc(sizeof(int)*5);//两个地方强制类型转化
// ✅ 必须判空
if(p == NULL)
{
perror("malloc fail");//这个是打印错误的原因
return -1; //异常退出
}
// 使用内存
free§; // 释放堆内存
p = NULL; // 置空,防止野指针!
固定流程:申请 → NULL判断 → 使用 → free → 指针置NULL
- calloc(n, size) :申请n个大小为size的空间,自动初始化为0 //这个比maloc多一个初始化为0
- realloc(p, new_size) :扩容/缩容原有堆内存,会自动拷贝旧数据(它会复制原来的加后面要拷贝的)
⚠️重点: free 只是归还内存给操作系统,不会把p本身清空,p依旧存旧地址 → 野指针//所以后面还要重新赋值NULL
二、野指针
定义:指针存了一块已经释放/非法、不受程序管控的内存地址
产生原因:
① free之后没有把指针赋值NULL
② 未初始化的局部指针变量
③ 越界访问后指针指向非法区域
风险:随机崩溃、程序玄学bug,很难排查
三、数组名什么时候不会退化(3种特例,其余全部退化成首元素地址)
- sizeof(数组名) :得到整个数组总字节数,不退化
- &数组名 :取出整个数组的地址(数组指针,类型和首元素地址不一样),不退化
- 字符串字面量初始化数组: char arr\[\] = "hello"; ,不退化
其余所有场景:数组名 → 首元素地址
注意:scanf的时候,只有arr不用&,arr\[\],arr\[\]\[\]这些都要&
四、柔性数组(结构体最后一个成员未知)
它不是arr\[\]={}这种东西,因为这个省略写法的大小已经知道了,柔性数组的大小本身就是未知的,也不占内存
写法:结构体末尾 int arr\[\]; 或者 int arr0;
✅规则
- 结构体里最后一个成员,不能有大小
- 结构体本身不分配数组内存,需要一次malloc申请【结构体大小 + 数组需要的空间】
- 内存整块连续,只需要一次free,相比结构体里 int *arr 优势:减少内存碎片
示例:
c
struct test
{
int num;
int arr\[\]; //柔性数组,不占结构体大小
};
//一次性申请结构体+数组空间
struct test p = (struct test )malloc(sizeof(struct test)+sizeof(int)*10);
if(p == NULL) return -1;
p->num = 10;
p->arr0 = 100;
free§;
p=NULL;
对比: int *arr 是指针,占8字节(64位),需要两次malloc、两次free,内存不连续。
配套经典题型(带考点)
题型1 动态内存标准模板(默写题)
题目:申请能存放5个int的堆内存,写入数据,释放。要求包含判空、置NULL。
c
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int p = (int )malloc(sizeof(int)*5);//这里使用指针变量去承载动态内存
if(p == NULL)
{
perror("malloc");
return 1;
}
for(int i=0;i<5;i++)
{
pi = i;
}
free§;
p = NULL;
return 0;
}
考点:忘记判空 / free后不置NULL产生野指针
题型2 数组名退化选择题
c
char arr\[\] = "abcd";
printf("%zd", sizeof(arr));
printf("%p", &arr);
//printf和scanf是不一样的
问:sizeof(arr)输出?&arr是什么类型?
答案:5字节(后面有一个\0);&arr是整个数组的地址(char(*)5数组指针)
考点:数组名两大不退化场景
题型3 柔性数组基础题
c
struct S
{
int n;
int arr\[\];
};
struct S ps = malloc(sizeof(struct S)+5sizeof(int));
问:malloc申请的内存包含哪两部分?释放时free几次?
答案:结构体int n + 5个int数组;只free一次
题型4 野指针判断题
c
int *p = malloc(4);
free§;
*p = 10;
问题:代码错在哪?
答案:free之后p变成野指针,解引用*p属于非法访问;free后必须 p=NULL;
题型5 易错对比:结构体指针成员 VS 柔性数组
c
//版本A:柔性数组
struct A{
int cnt;
int data\[\];
};
//版本B:普通指针成员
struct B{
int cnt;
int *data;
};
考点:A一次malloc、一次free,内存连续;(柔性数组本身就不用内存,内存直接给结构体就可以了)
B要两次malloc两次free,容易内存泄漏(结构体本身有一块内存,指针变量本生也有一块内存,两块内存要分别malloc和free)