常见的动态内存错误总结(一)

简介: 该文讨论了C语言中与动态内存和指针相关的常见问题。首先,强调了对NULL指针解引用的危险性,示例展示了未检查动态内存分配结果导致的问题,并指出`free()`函数传入NULL是安全的。接着,通过代码解释了指针传值调用时的陷阱,说明了为何直接调用`GetMemory(str)`无法改变`Test`函数中`str`的值。文章还提到了动态内存的越界访问和非法释放,包括释放非动态内存、只释放内存的一部分以及重复释放同一块内存的错误情况。最后,建议在释放内存后将指针设为NULL以防止后续误用。

一、对NULL指针的解引用操作

代码一  动态内存申请后未进行空指针检查


//err
void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;    //未进行空指针判断,若p的值是NULL,就会有问题
 free(p);
}
 
 
//correct
void test()
{
 int *p = (int *)malloc(INT_MAX/4);
 if(p == NULL)    //进行返回值NULL指针检查,更为严谨
 {
    exit(-1);
 } 
  else 
 {
   *p = 20;
 }  
 free(p);
}


在使用动态内存函数(malloc、calloc、reallo)申请空间后,需要进行空指针判断。因为动态内存函数申请空间有可能会失败,若开辟失败,则函数会返回一个NULL指针。因此对动态内存函数的返回值,一定要做检查。

注意,free函数是允许传入NULL指针的。当free的参数为NULL指针时,函数什么事情也不做,不会出现动态内存错误。

代码二  指针变量的传值调用

*运行Test函数,结果如何?会打印出 hello world 吗?


//运行Test函数,结果如何?
 
void GetMemory(char *p) {
     p = (char *)malloc(100);
}
 
void Test(void) {
     char *str = NULL;
     GetMemory(str);
     strcpy(str, "hello world");
     printf(str);
}


答案是并不会。该程序无法正常输出,原因在于其中也有“NULL指针陷阱”。


如下图所示:


在Test函数中创建指针变量 *str,并赋值为NULL。

在Test函数中调用GetMemory函数。GetMemory函数的形参为char *p,形参是实参的临时拷贝,形参的改变不影响实参。*p与*str是两个不同的变量,存储在栈上两块完全不同的内存空间。

GetMemory函数中,变量p指向了一块新开辟的内存空间,但p的改变并不影响实参*str。

由于局部变量建立在栈上,函数调用结束后,生命周期就结束,因此在GetMemory函数结束后,该函数的栈帧销毁,变量*p也随之销毁。而此时*str的值并未改变,仍然是NULL。

因此最后在Test函数中strcpy试图将字符串"hello world"拷贝到NULL指针str中,strcpy函数出错。该代码无法正常运行。



以上代码可以理解为指针的传值调用。我们知道,要在调用的函数内“远程”改变函数外的变量值,需要传入变量的地址。因此,要让上面的代码能正常打印"hello world",只需在函数中传入指针变量的地址——二级指针即可。(当然,通过返回值的方式,在函数调用后接收返回值,也是可行的。)



//correct
 
void GetMemory(char* *p)     //传入二级指针
{
     *p = (char *)malloc(100);    //对二级指针解引用,获得指针变量的地址,从而改变指针变量的值
}
 
void Test(void) {
     char *str = NULL;
     GetMemory(str);
     strcpy(str, "hello world");
     printf(str);
     free(str);
}


二、对动态开辟空间的越界访问


void test()
{
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;//当i是10的时候越界访问
 }
 
 free(p);
}


该程序中,指针p指向一片开辟了40个字节(即10个整型)的存储空间。若要访问该空间的内存值,则0到9才是合法的下标数值。然而在遍历赋值时,i越界到了10.这时,由于访问了无权限访问的内存空间,程序就会崩溃,无法正常运行。


三、对非动态开辟内存使用free释放

1.void test()
{
 int a = 10;
 int *p = &a;
 free(p);
}


以上程序中,a变量并不是动态内存开辟的。令指针p指向a的内存空间并free(p),编译器会运行出错(Debug Assertion failed)。注意:free只能释放动态开辟的内存空间。


四、使用free释放一块动态开辟内存的一部分


void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);    //p不再指向动态内存的起始位置
}


以上程序中,原本指向动态内存开辟空间的p后来被移动,最终所指向的位置不再是开辟空间的起始位置。这时再将p释放,释放的只是部分动态开辟内存而不是全部,这样程序仍然会在运行时崩溃。


因此要注意:在动态开辟空间之后,如果要遍历开辟的内存空间,不要用开辟时接收的指针来遍历。应当再定义一个别的指针如指针q,进行运算操作。将p指针不动留到最后,用于最终的释放操作。


五、对同一块动态内存多次释放


void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);    //重复释放
}


该程序仍然会出现运行错误。在第一次free(p)后,p所指向的空间就已经被释放。此时的p相当于一个野指针,指向已经被释放了的无权限访问的内存空间。


而之后再进行一次free(p),p所指的内容已经不是动态内存开辟的空间了。因此程序会出错而中止。


注意,若在第一次free(p)后,将p = NULL,然后再进行第二次free(p),此时不会发生任何错误。因此,在释放完动态开辟的内存后,要记得将指针赋值为NULL,使程序更加严谨,避免一时疏忽从而让程序出错。


常见的动态内存错误总结(二)

+https://developer.aliyun.com/article/1519319?spm=a2c6h.13262185.profile.54.71c436831FgmJg

相关文章
|
存储 编译器 C语言
动态内存管理函数【超详解】
动态内存管理函数【超详解】
|
21天前
|
C++ 容器
常见的内存分配错误
【10月更文挑战第11天】
25 2
|
6月前
|
编译器
常见的动态内存错误总结(二)
本文总结了两种常见的动态内存错误:内存泄漏和野指针。在示例代码中,内存泄漏发生于动态分配内存后未进行释放,导致程序结束时内存无法回收。野指针问题出现在函数返回栈上创建的变量地址,由于栈空间销毁,指针变成无效,访问时会导致错误。文章强调了使用`static`修饰局部变量可以延长其生命周期以避免野指针,以及释放内存后应将指针置`NULL`以防止后续误用。
40 3
|
5月前
|
C语言
C语言学习记录——动态内存开辟常见的错误
C语言学习记录——动态内存开辟常见的错误
33 1
|
6月前
6个常见的动态内存的错误和动态内存经典笔试题
6个常见的动态内存的错误和动态内存经典笔试题
|
C语言
【动态内存管理】动态内存函数
【动态内存管理】动态内存函数
|
编译器
C进阶:动态内存函数 malloc calloc realloc free及常见动态内存开辟错误(下)
C进阶:动态内存函数 malloc calloc realloc free及常见动态内存开辟错误(下)
64 1
|
存储 程序员 编译器
C语言 — 动态内存管理(动态内存函数)
本期介绍动态内存函数,函数如何使用、函数格式、在使用在所需要的注意点及C/C++程序的内存开辟区域
94 0
|
编译器
动态内存函数和常见的动态内存错误
动态内存函数和常见的动态内存错误