C语言中free掉一段空间后为啥还要使用NULL

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言中free掉一段空间后为啥还要使用NULL相关的知识,希望对你有一定的参考价值。

free(temp);//其中temp是一个指针
temp=NULL;
请高手解释,如何验证解释的正确性,专业解释,非懂勿进,谢谢配合!

指针free之后,free函数只是把指针指向的内存空间释放了,即内存中存储的值,但是并没有将指针的值赋为NULL,指针仍然指向这块内存。而程序判断一个指针是否合法,通常都是使用if语句测试该指针是否为NULL来判断,导致指针成为所谓的“野指针”,诱导误操作,示例代码如下,
#include <stdio.h>
#include <malloc.h>
int main(int argc, char *argv[])

int *p=NULL;
p=(int *)malloc(sizeof(int));
*p=400;
printf("free前,%d\n",*p);//输出400
free(p);
if(p!=NULL)
printf("free后,未设置NULL,%d\n",*p);//可能输出1497144或其他的垃圾值
return 0;


“野指针”与空指针不同,“野指针”有地址,或者说指向指定的内存,对野指针进行操作很容易造成内存错误,破坏程序数据等危险。“野指针”无法简单地通过if语句判断其是否为 NULL来避免,而只能通过养成良好的编程习惯来尽力减少,free函数执行后,一定要接着设置指针为NULL。
参考技术A “野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if无法判断一个指针是正常指针还是“野指针”。有个良好的编程习惯是避免“野指针”的唯一方法。

指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。

例:
free( p );
if ( p != NULL )
p = NULL;追问

int *p=NULL;
*p=9;
试了一下发生了段错误,这个如何理解?

本回答被提问者和网友采纳
参考技术B free函数只是释放掉了指针所指向的那一段内存空间,但释放后指针本身的值仍然保持,需要显式的置0。这么做是有必要的,一般是结合代码其它部分的参数检查:当别处需要使用temp却发现temp为null时,就会进行相应的处理(报错或者malloc等等),而不会直接导致严重错误或者程序崩溃的出现。 参考技术C temp=NULL并不需要,它只是为了安全,避免野指针。

如果你以后还通过temp指针访问其值,如果没有执行过temp=NULL;那么程序很可能就默默地错误地执行下去了,是一种隐患;如果执行过temp=NULL;,程序一定会报一个错误,说指向0x00000000的内存不能为read。
参考技术D 万一忘记了在这个地方已经free过temp了(当代码很长并且比较复杂的时候极有可能),再一次free(temp)就会导致异常,而free(NULL)是不会有效果的……

玩转C语言动态内存,轻松管理动态内存空间

1、动态内存分配存在的原因

我们普遍定义变量的时候,经常采用以下的写法:

char str;//在栈上开辟1个字节的内存空间
int num;//在栈上开辟4个字节的内存空间
int arr[10];//在栈上开辟40字节连续的空间

但是上述的开辟空间的方式有两个特点:
1、空间开辟大小是固定的。
2、数组在声明的时候,必须指定数组的长度,他所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。

2、动态内存函数的介绍

  • C语言提供了一些动态内存开辟的函数:使得存储空间的使用更加灵活,提高内存的利用率。
  • malloc和free都声明在 stdlib.h 头文件中。所以在使用的时候要引相应的头文件,下面让我们来看一下这些函数的用法。

2.1malloc函数

1、malloc函数的介绍:这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

void* malloc(size_t size);

向内存堆区申请size个字节的空间,申请完毕后返回堆区的地址,因为不知道返回什么类型的地址合适,所以返回void*类型的。
2、如果开辟成功,则返回一个指向开辟好空间的指针。

3、如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

4、返回值的类型是 void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者自己来决定。一般情况下不拿void*来接收,要强制类型转换后进行使用。

5、如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

6、在使用malloc开辟空间的时候,一定要判断是否开辟成功

int main()

    int *p=(int*)malloc(40);
    if(p==NULL)
    
        return -1;
    
    int i=0;
    //开辟的空间地址是连续的,可以看成数组
    for(i=0;i<10;i++)
    
        *(p+i)=i;
    
   

我们申请空间,使用空间,在使用完成后我们要释放空间,此时就用到了free函数。

2.2free函数

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
1、free函数的介绍:

void free (void* ptr);

free函数用来释放动态开辟的内存。
2、free的使用:

int main()

    int *p=(int*)malloc(40);
    if(p==NULL)
    
        return -1;
    
    int i=0;
    //开辟的空间地址是连续的,可以看成数组
    for(i=0;i<10;i++)
    
        *(p+i)=i;
    
    free(p);
    p=NULL;
   

free把p的空间给释放掉了,但是不会改变p的地址。这种释放后依然能找到free释放的空间是非常可怕的,所以要将释放的空间置空。

3、如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。free也不知所措了。

4、如果参数 ptr 是NULL指针,则函数什么事都不做。所以可以给free传一个空指针,但是free什么事都不会干。

2.3calloc函数

C语言还提供了一个函数叫 calloc , calloc函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

1、calloc函数介绍:num表示元素的个数,size表示一个元素有多大,单位字节

#incldue<stdio.h>
#include<errno.h>
#include<string.h>
int main()

    int *p=(int*)calloc(10,sizeof(int));
    //开辟10个大小为4字节的连续空间,并且初始化为0
    if(p==NULL)
    
        printf("%s\\n",strerror(errno));
        return -1;
    
    free(p);
    p=NULL;
    return 0;

2、malloc函数只负责在堆区申请空间,并且返回起始地址,不初始化内存空间;calloc函数在堆区上申请空间,并且初始化为0,返回起始地址。

3、如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

2.4realloc函数

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。函数原型如下:

void* realloc (void* ptr, size_t size);

1、realloc函数的介绍:ptr是要调整的内存地址,之前可能有malloc或者calloc开辟好的空间;size 为调整之后新大小,单位字节。

2、返回值为调整之后的内存起始位置。

3、realloc函数的出现让动态内存管理更加灵活。

4、这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。

5、realloc在调整内存空间的是存在两种情况:

1)情况1:原有空间之后有足够大的空间,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。把原先空间的地址当作新地址返回。

2)情况2:原有空间之后没有足够大的空间,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。并将原空间的数据拷贝在新空间上,在释放掉原空间,这样函数返回的是一个新的内存地址。

6、 由于上述的两种情况,realloc开辟失败会返回空指针,如果贸然使用,开辟失败可能会丢失原有空间,所以realloc函数的使用就要注意进行判断,在进行自身的赋值。 举个例子:

#incldue<stdio.h>
#include<errno.h>
#include<string.h>
int main()

    int *p=(int*)calloc(10,sizeof(int));
    if(p==NULL)
    
        printf("%s\\n",strerror(errno));
        return -1;
    
    int* ptr=(int*)realloc(p,20*sizeof(int));
    if(ptr!=NULL)
    
    p=ptr;
    
    free(p);
    p=NULL;
    return 0;

3、常见的动态内存错误

3.1对NULL空指针的解引用操作

int main()

    int *p=(int*) malloc(20);
    *p=0;//这样不进行判断,直接解引用是有风险的。
    if(p==NULL)
    
        return -1;
    

结论:所以对动态内存开辟的空间一定要先判断在使用。如下:

int main()

    int *p=(int*) malloc(20);
    if(p==NULL)
    
        return -1;
    
    *p=0;

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

int main()

    int *p=(int*)malloc(200);
    if(p==NULL)
    
        return -1;
    
    int i=0;
    //200字节也就是50个左右的整型,访问80个越界访问了,程序会直接崩掉
    for(i=0;i<80;i++)
    
        *(p+i)=i;
    
    free(p);
    p=NULL:
    return 0;

结论:在对动态开辟的内存使用时,一定要注意范围。

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

int main()

    int a=10;
    int* p=&a;
    free(p);
    //处在栈上的空间直接free,即非堆上的空间释放程序会直接崩掉。
    p=NULL;
    return 0;

结论:对非堆上的内存不可以使用free释放。

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

int main()

    int* p=(int*)malloc(10*sizeof(int));
    if(p==NULL)
    
        return -1;
    
    int i=0;
    for(i=0;i<10;i++)
    
        *p++=i;
//p在++的过程中,p已经不再执行开辟空间的起始位置,free掉的只是现在位置往后的空间。    
  
    
    free(p);
    p=NULL;
    return 0;

结论:++/–在某些情况下是具有副作用的,对于地址的起始位置是非常重要的,如果要用到地址的偏移,可以先保存原先地址的起始位置。即给原地址进行一个备份。

3.5对同一块内存的多次释放

int main()

    int* p=(int*)malloc(40);
    if(p==NULL)
    
        return -1;
    
    free(p);
    free(p);
    //程序直接崩溃
    return 0;

另一种错误:

int main()

    int* p=(int*)malloc(40);
    if(p==NULL)
    
        return -1;
    
    free(p);
    p=NULL;
    free(p);
    //此时程序可以正常运行
    return 0;

结论:对释放的空间一定要进行及时的置空,并且不能重复释放同一空间。即使有两个指向同一内存的,也只能free一个,因为释放完之后,原有的内存空间已经释放掉了,在进行释放就是重复释放。

4、后续有动态内存的面试题及其运用,收藏+关注不迷路哟。

以上是关于C语言中free掉一段空间后为啥还要使用NULL的主要内容,如果未能解决你的问题,请参考以下文章

关于C语言free函数的问题

C语言中已经有了malloc和free,为啥还需要new和delete?

C语言中free()释放空间时报错

C语言中free函数的用法

在C语言进行编程中,为啥要释放旧内存?

用了free(*T)还要用一个NULL呢?这两个的作用有啥不一样?