您不能在未分配的内存上调用
free
,标准已经非常明确地说明了这一点(略有改动,重点是我的强调):
free
函数使其参数指向的空间被释放,即可供进一步分配使用。如果参数是空指针,则不会发生任何操作。否则,如果参数与之前由内存管理函数返回的指针不匹配,或者如果该空间已被free或realloc调用释放,则行为是未定义的。
例如,如果您双倍释放的地址已在新块中重新分配,而分配它的代码刚好将某些东西存储在那里,看起来像是真正的malloc块头,会发生什么情况呢?像这样:
+- New pointer +- Old pointer
v v
+
| <Dodgy bit> |
+
混乱,就是这样。
内存分配函数就像电锯一样是一种工具,只要使用正确,你就不会遇到问题。但如果滥用它们,后果就是你自己的错,可能会损坏内存或更糟糕的情况,甚至可能割掉你的手臂 :-)
关于评论:
“...它可以向最终用户优雅地传达有关在相同位置双倍释放的信息。”
除了记录所有malloc和free调用以确保您不会重复释放块之外,我看不出这是可行的。这将需要巨大的开销,仍然无法解决所有问题。
如果发生以下情况会怎样:
1.线程A在地址42处分配并释放内存。
2.线程B在地址42处分配内存并开始使用它。
3.线程A第二次释放该内存。
4.线程C在地址42处分配内存并开始使用它。
那么,您现在有线程B和C都认为它们拥有该内存(这些不必是执行线程,我在这里使用线程一词只是一个运行的代码片段 - 它可以全部在一个执行线程中被顺序调用)。
不,我认为当前的malloc和free只要正确使用就很好。请考虑实现自己的版本,我认为这没有任何问题,但我怀疑您会遇到一些非常棘手的性能问题。
如果您确实想要在free周围实现自己的包装器,您可以使其更安全(以稍微降低性能为代价),具体请参见下面的myFreeXxx调用:
#include <stdio.h>
#include <stdlib.h>
void myFreeVoid (void **p) { free (*p); *p = NULL; }
void myFreeInt (int **p) { free (*p); *p = NULL; }
void myFreeChar (char **p) { free (*p); *p = NULL; }
int main (void) {
char *x = malloc (1000);
printf ("Before: %p\n", x);
myFreeChar (&x);
printf ("After: %p\n", x);
return 0;
}
代码的要点是你可以使用指向指针的指针来调用
myFreeXxx
函数,它会同时:
后面那部分的意思是,如果你尝试再次释放该指针,它将不起作用(因为标准明确规定了释放 NULL)。
它
不能保护你免受所有情况的影响,例如,如果你在其他地方复制了该指针,释放原始指针,然后释放副本:
char *newptr = oldptr;
myFreeChar (&oldptr); // frees and sets to NULL.
myFreeChar (&newptr); // double-free because it wasn't set to NULL.
如果您使用的是C11,现在有一种比显式调用不同函数更好的方式,因为C具有编译时函数重载。您可以使用通用选择来调用正确的函数,同时仍然允许类型安全:
#include <stdio.h>
#include <stdlib.h>
void myFreeVoid (void **p) { free (*p); *p = NULL; }
void myFreeInt (int **p) { free (*p); *p = NULL; }
void myFreeChar (char **p) { free (*p); *p = NULL; }
#define myFree(x) _Generic((x), \
int** : myFreeInt, \
char**: myFreeChar, \
default: myFreeVoid )(x)
int main (void) {
char *x = malloc (1000);
printf ("Before: %p\n", x);
myFree (&x);
printf ("After: %p\n", x);
return 0;
}
因此,您只需调用myFree
,它将根据类型选择正确的函数。
free()
的基本原理指出:将空指针指定为此函数的有效参数,以减少特殊情况编码的需要。请注意,realloc(p, 0)
(以及malloc(0)
和calloc
的任一参数为0)可能会返回空指针。 - ninjalj