前言:上篇中我们较为详细地介绍了C语言中动态内存是如何开辟和使用的以及柔性数组等相关概念,但是在实际使用中,动态内存开辟是极易出现一些问题的,本篇将就这些常见问题作以总结,避免大家掉入陷阱。
一.常见的动态内存错误
1.对NULL指针的解引用操作
void test1()
{
int* p =(int*) malloc(INT_MAX / 4);
*p = 20;
//若p值为NULL,则对NULL解引用了,这是错误的
free(p);
}
这里在开辟空间后并没有对p进行检验,这样在空间开辟失败时,p为空指针NULL,此时对它解引用会出现问题。正确方式如下:
#include<assert.h>
void test1()
{
int* p =(int*) malloc(INT_MAX / 4);
//if(p!=NULL)
assert(p);
*p = 20;
free(p);
}
2. 对动态开辟空间的越界访问
void test2()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
exit(EXIT_FAILURE);
//非正常退出
}
for (i = 0;i <= 10;i++)
{
*(p + i) = i;
//当i=10的时候越界访问了
}
free(p);
}
对于动态开辟的内存空间开辟多少使用多少,切不能“僭越”。
3.对非动态开辟内存使用free释放
void test3()
{
int num = 10;
int* p = #
free(p);
//num的空间不是动态内存开辟的,所以free这里使用不当
}
free所释放的是malloc,calloc所开辟的在堆区上的动态内存空间,而对于非动态开辟的内存空间free无法释放其空间。
4.使用free释放一块动态开辟内存的一部分
void test4()
{
int* p = (int*)malloc(100);
p++;
free(p);
//此时p已经不再指向动态内存的起始位置
//使用free释放的的仅仅是一块动态内存开辟内存的一部分
}
当p++后p所指向的位置已经不再是动态内存开辟空间的起始位置,此时再释放空间的话仅仅释放了部分空间,这是操作不当的。并且,我们在使用动态内存空间的时候,尽量不要改变其起始位置的地址!这会让我们找不到所开辟空间的具体起始。
5.对同一块内存多次释放
void test5()
{
int* p = (int*)malloc(100);
free(p);
free(p);
//对同一块空间重复释放
}
一次malloc/calloc搭配一个free,不能二次释放。
6.动态开辟内存忘记释放(内存泄漏)
void test6()
{
int* p = (int*)malloc(100);
if (p != NULL)
{
*p = 20;
}
}
int main()
{
test6();
//p是局部变量,在出函数后空间就销毁了,此时就找不到动态开辟的空间了,想释放都释放不了了
while (1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏。
切记:动态开辟的空间一定要释放,并且正确释放。
二.动态内存经典笔试题分析
例1:
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void test()
{
char* str = NULL;
GetMemory(str);//传值调用
//p在出函数的时候销毁了,对应的空间也找不到了
strcpy(str, "helloword");
//对NULL的解引用,程序崩溃
printf(str);
}
输出结果:(程序崩溃)
这里对GetMemory函数进行的是传值调用(形参是实参的一份临时拷贝),但是当局部变量p在出函数后对应空间销毁,实际上str的值并没有修改。如图:
那我们如何成功将开辟空间的地址传给str呢?有如下修改方式:
改1:
void GetMemory(char** p)
{
*p= (char*)malloc(100);
}
void test()
{
char* str = NULL;
GetMemory(&str);//传址调用
strcpy(str, "helloword");
printf(str);
free(str);
str = NULL;
}
传值调用不行我们还可以进行传址调用,将str地址传给p,对p解引用就找到了str,也就成功地将malloc开辟空间的地址交给了str。如图:
改2:
char* GetMemory(char* p)//char* GetMemory()
{
p = (char*)malloc(100);
return p;
}
void test()
{
char* str = NULL;
str=GetMemory(str);//这里也可以不传参
//str=GetMemory();
strcpy(str, "helloword");
printf(str);
free(str);
str = NULL;
}
第二种方式是通过return直接将p的值带回来,再用str去接收,也能成功将开辟空间的地址交给str。
例2:
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void test(void)
{
char* str = NULL;
str = GetMemory();
//p指向的空间已经被回收了,没有操作权限了
printf(str);
}
int main()
{
test();
return 0;
}
输出结果:烫烫烫烫烫烫烫烫Ol[
这里的问题同样是由于局部变量p在出函数后被销毁,对应的“hello world”的空间我们已经失去了操作权限了,即归还给了操作系统,即便地址还在,但是其所指向的空间也是未知的。
那有人会有疑问,之前的例1的改2也采用了return的方式带回地址,为什么到这里就不行了呢?
注意:malloc开辟的空间不会出函数就销毁,但是数组(局部变量)是出了函数就销毁了
malloc申请的空间是在程序退出或者free的时候才释放空间的。
例3:
void GetMemory(char** p, int num)
{
*p =(char*)malloc(num);
}
void test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
//free(str);
//str=NULL;
}
int main()
{
test();
return 0;
}
其实这个代码已经十分完善了,类似与例1中的改1,但是唯一美中不足的是使用完str后对它没有进行释放。
例4:
void test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
//非法访问
printf(str);
}
}
int main()
{
test();
return 0;
}
输出结果:world
这道题的问题在于对str进行了提前释放,释放之后再对它使用就是非法访问了。
同时许多人存在一个误区:那就是free会使对应指针置空,实则不然,free的作用仅仅是将开辟的空间的使用权限归还给操作系统,对应str的值还是不会改变的,所以free后还有自行给指针置空,以免其成为野指针。
以上便是对于动态内存开辟中可能遇到的一些常见问题作以解释,希望大家避免以上错误,加深理解。