释放内存两次

24
在C和C++中,释放一个空指针将不会有任何作用。
尽管如此,我看到有人说如果“free一个已经被释放了的内存”,那么可能会导致内存损坏。
这是真的吗?当你两次释放内存时,底层发生了什么?

6
当您释放一个空指针时,不会释放任何内存。如果您两次释放一个非空指针,则会释放内存两次,这是一个问题。 - jalf
9个回答

25
int *p = malloc(sizeof(int));
//value of p is now lets say 0x12345678

*p = 2;
free(p); //memory pointer is freed, but still value of p is 0x12345678
         //now, if you free again, you get a crash or undefined behavior.

因此,在第一次使用free后,您应该执行p = NULL,这样如果(偶然地)再次调用free(p),则什么也不会发生。

释放内存两次未定义的原因在于:为什么重复调用free会导致崩溃


7
即使我不同意在设置指针为NULL后的正确性上加分,因为这可能会隐藏一个悬而未决的错误(第二次调用"free"), 我宁愿它彻底失败并迅速报错,以便能够处理该错误,而不是将其隐藏起来。 - David Rodríguez - dribeas
@David:我完全同意,如果在指针上两次调用free,那么这是一个(有点)糟糕的实现。 - N 1.1
1
@David:只要你知道如何调试“重复释放”情况,那就是一个好策略。如果不知道,我宁愿在释放后立即将指针置空,并在每个free语句之前检查它是否为空。 - Janusz Lenar
3
@malleor:当出现双重释放时,应用程序会崩溃。你知道,一个指针在你看来是有效的,但实际上它并不是有效的。隐藏它可能导致你对空指针进行解引用,并对其进行操作,这是未定义的行为(并且根据系统不同可能只会提供错误结果)。调试崩溃总比调试不正确的值简单。 - David Rodríguez - dribeas
7
@David:这是一个权衡。我认为,让指针指向已释放的内存会更容易隐藏指针后面使用时出现的错误,因为解引用空指针往往更容易崩溃。重复释放也是未定义的行为,通常也不会立即导致崩溃。 - jamesdlin
显示剩余9条评论

21

释放内存并不会将指针设置为 null。 指针仍然指向它曾经拥有的内存,但现在所有权已被转移回堆管理器。

堆管理器可能已经重新分配了您的陈旧指针所指向的内存。

再次释放它并不意味着说 free(NULL),这将导致未定义行为。


10
这是未定义行为,可能会导致堆损坏或其他严重后果。
对于空指针的free()仅检查内部指针值并返回。该检查无法防止两次释放同一块。
通常情况下,堆实现获取地址并尝试通过修改自己的服务数据来“拥有”该地址处的块。根据堆实现的不同,任何事情都可能发生。也许它可以正常工作,什么都不会发生,也许服务数据已经损坏,你就会遇到堆损坏的问题。
因此,不要这样做。这是未定义行为,可能会发生任何糟糕的事情。

1
我还要明确地说,释放指针并不一定会改变它的值,在通常情况下,在“free”之后它不是空指针(这就是为什么第二个“free”会导致破坏)。 - Ari

5

为了避免重复释放内存,我总是使用宏来释放内存:

#ifdef FREEIF
# undef FREEIF
#endif
#define FREEIF( _p )  \
if( _p )              \
{                     \
        free( _p );   \
        _p = NULL;    \
}

这个宏将p设置为NULL以避免悬空指针。

2
这是一个很好的技巧,但我宁愿尝试修复实际原因。如果对同一变量调用了两次free(),那显然是个bug。 - user206268

4
当您使用free释放指针时,您的指针将不会被设置为NULL。空闲空间仅返回给池以供再次分配。以下是一个测试示例:
#include <stdio.h>
#include <stdlib.h>

int main(){
    int* ptr = (int*)malloc(sizeof(int));
    printf("Address before free: %p\n", ptr);
    free(ptr);
    printf("Address after free: %p\n", ptr);
    return 0;
}

这个程序为我输出:
Address before free: 0x950a008
Address after free: 0x950a008

你可以看到,free函数并没有对指针所指向的内存做任何修改,只是告诉系统这块内存可以被重新使用。


4
是的,“未定义行为”几乎总是导致崩溃。(按照定义,“未定义行为”可以是“任何事情”,但是在实际应用中,不同类型的错误通常会表现出相对可预测的方式。在free()的情况下,行为总是segfault或者与操作系统相关的“内存保护错误”)。
如果你free()一个指向NULL以外的任何东西或者你没有使用malloc()分配的内存,情况也是一样的。 char x; char* p=&x; free(p); // 崩溃。

3
你对未定义行为的结果过于乐观了。 - Roger Pate
1
我和罗杰在一起。实际上,两次释放同一块内存几乎从不会导致程序崩溃,这使得它成为最难追踪的错误之一。 - anon

3

free() 函数释放由 ptr 指向的内存空间,这个指针必须是之前通过调用 malloc()、calloc() 或 realloc() 返回的。否则,或者如果已经调用过 free(ptr),会导致未定义的行为。如果 ptr 是 NULL,则不执行任何操作。

因此,你会得到未定义的行为,任何事情都可能发生。


1

1) 编译器不处理动态内存的处理。有运行时库来处理此问题。例如:glibc提供了像malloc和free这样的API,它们在内部进行系统调用(sys_brk)以处理堆区域。

2) 释放相同的内存两次是指以下情况: 假设您有char *cptr;

您使用以下方式分配内存: cptr =(char *)malloc(SIZE);

现在,当您不再需要此内存时,可以使用以下方法将其释放: free(cptr);

现在,cptr指向的内存已经可以被使用。

假设在程序的后期,您再次调用free(cptr),则这不是有效的条件。您正在释放相同内存两次的情况称为“释放内存两次”问题。


0

释放内存超过一次可能会产生不良后果。您可以运行此代码片段,以查看可能发生的情况。

#include <stdio.h>      /* printf, scanf, NULL */
#include <stdlib.h>     /* malloc, free, rand */

int main ()


  {
  int i,n;
  char * buffer;

  printf ("How long do you want the string? ");
  scanf ("%d", &i);

  buffer = (char*) malloc (i+1);
  if (buffer==NULL) exit (1);

  for (n=0; n<i; n++)
          buffer[n]=rand()%26+'a';
  buffer[i]='\0';

  printf ("Random string: %s\n",buffer);
  free (buffer);
  free (buffer);

  return 0;
}

许多标准库,如CSparse,使用一个处理内存问题的包装函数。我在这里复制了该函数:

 /* wrapper for free */
    void *cs_free (void *p)
    {
        if (p) free (p) ;       /* free p if it is not already NULL */
        return (NULL) ;         /* return NULL to simplify the use of    

    }

这个函数可以处理内存问题。请注意,在某些情况下,您必须注意malloc返回NULL的条件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接