目录
一、前言: 为何使用动态内存?
1.内存大小
struct Code {
int n;
int arr[10];
};
上述这段代码在创建结构体时就已经将他的大小固定了, 但是程序运行时很多时候我们需要的空间大小会超出这个固定的空间,这时候我们就需要一个可以扩大空间的方法;
2. 内存位置
一个临时变量的创建会在内存的栈区上开辟一个空间,这个变量会在函数结束时销毁,意味着你不能在使用这个临时变量;
相信大家都是用过leetcode刷题,或者知道接口型的刷题模式,遇到需要以数组的形似返回的题,代码上方都会有这样一段提示,要求返回的数组必须是malloc创建的,为什么呢?
动态内存开辟是在内存的堆区上开辟的空间,与栈区上开辟的空间不同,堆区上的空间不会因为函数的结束而销毁,也就是说动态开辟的空间的生命周期较长,如果你调用的一个函数需要你返回一个一维或者二维数组,你大可以用malloc(或者其他函数)开辟一个数组,再将这个数组返回
char* test_Code1() {
char ret[20] = { "Hello World!" }; //该字符数组位于栈区上
return ret;
}
char* test_Code2() {
char* ret = (char*)malloc(sizeof(char) * 20); //malloc在堆区上开辟空间
strcpy(ret, "Hello World!");
return ret;
}
int main()
{
char* str1 = test_Code1();
printf( "%s\n",str1); //打印随机值
char* str2 = test_Code2();
printf("%s\n", str2);
return 0;
}
运行结果⬇️
接下来我们再对上述代码进行调试,深入探讨内存:
从内存上来看,test_Code1函数中ret与str1的地址并不是相同的,明明返回值由str1接收,为什么两者地址不一样呢?这就反向验证了之前我们的言论了!
二、动态内存函数
※1. malloc
使用方法及注意事项:
• malloc返回值是void*,所以我们需要对其强制类型转换为我们实际开辟的类型;
• malloc后面括号内需要你填写你所需要开辟的字节数,你可以用
sizeof(数据类型) * 开辟的数据个数
• 如果开辟成功,则返回一个指向开辟好空间的指针;如果开辟失败,则返回一个NULL指
针,因此malloc的返回值一定要做检查。
• 如果参数 size为0,malloc的行为是标准是未定义的,取决于编译器。
实际用法可以参考文章开头的代码⬆️
2. calloc
calloc实际用法与malloc大体相似
不同点:
• calloc函数传参:第一个参数是元素类型(num),第二个参数是元素大小(size);
• 初始值:malloc开辟的空间初始值为随机值;而calloc会将申请空间的每个字节初始化为0;
※3. realloc
使用方法及注意事项
• 第一个参数传入需要调整的内存地址(指针),如果传入NULL,那么此时realloc功能可与malloc划等号;第二个参数传入调整后的空间大小(字节);
• 调整完空间大小之后,realloc函数返回调整好的内存起始位置的地址,还是需要对其强制类型转换;
• 注意最好不要直接用旧空间的指针接收新的空间地址,如果realloc开辟失败,会返回NULL,直接会将旧指针覆盖,导致旧空间内存泄漏
• 调整空间的时候会遇到两种情况:
① 原空间之后又足够空间用于空间的扩容:此时直接在原空间之后追加空间大小即可
② 没有足够的空间进行扩容:这时会把原空间的数据拷贝到一个足够大的新空间去,释放旧空间,并返回新空间的起始地址;
※4. free
简单来说,free是用来释放动态开辟的内存,之前说过动态内存实在堆区上开辟的,出函数不会销毁,那么如果当我们不用这块空间的时候,这块空间会被一直占用;
使用方法及注意事项
• 传入的参数必须是动态开辟的,如果传入NULL,那么函数将什么都不做;
※• free掉一个指针之后,指针所指向的动态开辟的空间和数据还在,但是没有访问权限了,(如果此时访问该空间会造成非法访问),所以在free掉之后,我们需要对指针置空;⬇️
void Test_Code() {
char* str = (char*)malloc(100); //默认开辟成功
strcpy(str, "hello");
free(str);
//这里需要进行 str = NULL;
if (str != NULL) {
strcpy(str, "world");
printf(str);
}
}
运行结果⬇️
这个代码证明了我们之前的说法,free掉str之后,str任指向开辟的空间,仍能找到str所指向的地址,并对其操作,但是编译器会报错,所以free之后需要我们手动将指针置空,防止非法访问。
三、常见的动态内存错误
① 对NULL指针的解引用操作
int main()
{
int* p = (int*)malloc(INT_MAX);
/*
if (p == NULL) {
exit(EXIT_FAILURE);
}
*/
*p = 10;
free(p);
return 0;
}
这时编译器会报出警告
因为malloc开辟空间可能会失败,如果失败了,那么str就是一个空指针,*str就是对空指针的解引用,是非常危险的操作,所以我们需要判断空间是否开辟成功。
② 越界访问
动态开辟的空间也像创建的数组一样,访问了超过大小的下标指向的空间肯定是错误的,即越界访问
int main()
{
int* str = (int*)malloc(sizeof(int) * 10);
if (str == NULL) {
exit(EXIT_FAILURE);
}
for (int i = 0; i <= 10; i++) { //当i == 10 时越界访问
str[i] = i; //等价于 *(str+i) = i;
}
return 0;
}
③ free释放一部分空间
int main()
{
int* p = (int*)malloc(sizeof(int) * 10);
p++;
free(p); //p++后,p指向的位置发生改变
return 0;
}
如上述代码所示,当p++之后,p指向的位置跳过一个整型的大小,指向的位置不再是开辟空间的首地址,如果这时free(p),那么意味着free只释放了一部分空间,还有四个字节的空间没有释放,即造成了内存泄漏的问题
④ free释放非动态开辟的内存
有些时候你可能不知道你已经把指针的位置移动了,最后把指针所指向的这块并不是堆中的内存释放,这时编译器会报错。⬇️
char* test_Code1() {
char* ret = (char*)malloc(sizeof(char) * 20);
ret = "Hello World!"; //这时ret指向的位置发生改变;
return ret;
}
int main()
{
char* str = test_Code1();
printf(str);
free(str); //对非动态开辟内存释放
return 0;
}
解析:上述代码中的 "Hello Wrold!" 属于常量字符串,位于内存中的代码段部分,占有独自的空间,ret = "Hello Wrold!" 相当于把字符串首字母的地址赋值给ret,这时ret所指向的位置已经不是动态开辟的内存了,ret将地址返回给str,所以不能free(str)
解决办法就是用strcpy
*四、柔性数组
1. 什么是柔性数组
struct Flex {
int a;
char str[]; //或 char*str;
};
上述代码中str就是一个柔性数组成员;
• 结构中的柔性数组成员前必须至少有一个其他成员;
• sizeof计算结构大小不会包含柔性数组成员
2. 柔性数组的两种动态开辟方式
数组形式
typedef struct Flex_1 {
int a;
char str1[];
}Flex_1;
int main()
{
//对str1柔性数组成员开辟108个字节的连续空间
Flex_1* p1 = (Flex_1*)malloc(sizeof(Flex_1) + sizeof(char) * 108);
p1->a = 108;
for (int i = 0; i < 108; i++) {
p1->str1[i] = 1;
}
free(p1);
p1 = NULL;
return 0;
}
指针形式
typedef struct Flex_2 {
int a;
char* str2;
}Flex_2;
int main()
{
Flex_2* p2 = (Flex_2*)malloc(sizeof(Flex_2));
p2->a = 108;
//对str2柔性数组成员开辟108个字节的连续空间
p2->str2 = (char*)malloc(sizeof(char) * 108);
for (int i = 0; i < 108; i++) {
p2->str2[i] = 1;
}
//先释放str2的内存
free(p2->str2);
p2->str2 = NULL;
//之后在释p2的内存
free(p2);
p2 = NULL;
return 0;
}
五、动态内存模拟开辟二维数组
行:int** arr是二级指针,指向int*,malloc开辟了ROW个int*,相当于对二位数组开辟ROW行;
列:arr[i]等价于*(arr+i) ,相当与解引用拿出int*,对其malloc开辟COL个int,相当于二维数组每一行有COL个元素
#define ROW 5
#define COL 10
void assignment(int** arr) {
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
arr[i][j] = 0;
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
//模拟arr[ROW][COL]
int** arr = (int**)malloc(sizeof(int*) * ROW);
for (int i = 0; i < ROW; i++)
{
arr[i] = (int*)malloc(sizeof(int) * COL);
}
//赋值
assignment(arr);
return 0;
}