释放从 realloc 返回的空指针?

Freeing a null pointer returned from realloc?

当我有一个指针应该重复用作 realloc 的参数并保存它的 return 值时,我知道 realloc 不会触及旧对象,如果无法进行分配,returning NULL。我是否还应该担心像这样的结构中的旧对象:

int *p = (int *)malloc(sizeof(int *));
if (p = (int *)realloc(p, 2 * sizeof(int *)))
 etc...

现在,如果 realloc 成功,我需要在完成后 free(p)。 当 realloc 失败时,我已将其 return NULL 分配给 p 并且 free(p) 不执行任何操作(因为它是 free(NULL)) .同时(根据标准)不释放旧对象。 那么我是否应该有一个 p 的副本(例如 int *last_p = p;)来跟踪旧对象,以便在 realloc 失败时我可以 free(last_p)

So should I have a copy of p (e.g. int *last_p = p;) for keeping track of the old object, so that I can free(last_p) when realloc fails?

是的,没错。通常我看到新指针被分配:

void *pnew = realloc(p, 2 * sizeof(int *)))
if (pnew == NULL) {
    free(p);
    /* handle error */
    return ... ;
}
p = pnew;

So should I have a copy of p (e.g. int *last_p = p;) for keeping track of the old object, so that I can free(last_p) when realloc fails?

基本上:是的,当然。

一般不建议使用您展示的模式:

p = (int *)realloc(p, ...) 

由于您发现的原因,这被认为是不好的做法。

改用这个:

void *new = realloc(p, ...);
if (new != NULL)
  p = new;
else
  ...

在 C 中转换 mallocrealloc 等的 return 值也被认为是不好的做法。这不是必需的。

你应该保存一个临时指针吗?

在某些情况下这可能是一件好事,正如之前的回答中所指出的那样。前提是你有失败后如何继续执行的计划。

最重要的不是你如何处理错误。重要的是你做了一些事情,而不是假设没有错误。退出是处理错误的一种完全有效的方式。

不要这样做,除非你计划一个明智的恢复

但是,请注意,在大多数情况下,realloc 导致的故障很难恢复。退出往往是唯一明智的选择。如果你不能为你的任务获得足够的记忆,你打算做什么?我遇到过只有一次恢复是明智的情况。我有一个解决问题的算法,我意识到如果我分配几千兆字节的 ram,我可以显着提高性能。它在只有几千字节的情况下运行良好,但随着额外的内存使用,它变得明显更快。所以代码基本上是这样的:

int *huge_buffer = malloc(1000*1000*1000*sizeof *hugebuffer);
if(!huge_buffer) 
    slow_version();
else
    fast_version();

在那些情况下,只需这样做:

p = realloc(p, 2 * sizeof *p)
if(!p) {
    fprintf(stderr, "Error allocating memory");
    exit(EXIT_FAILURE);
}

请注意通话的两个变化。我删除了转换并更改了 sizeof。在这里阅读更多相关信息:Do I cast the result of malloc?

如果您总体上不在乎,甚至更好。写一个包装器。

void *my_realloc(void *p, size_t size) {
    void *tmp = realloc(p, size);

    if(tmp) return tmp;

    fprintf(stderr, "Error reallocating\n");
    free(p);
    exit(EXIT_FAILURE);
    
    return NULL; // Will never be executed, but to avoid warnings
}

请注意,这可能与我在下面写的内容相矛盾,我在下面写的是在退出之前并不总是需要释放。原因是,当我将所有错误都抽象为一个函数时,正确的错误处理很容易做到,所以我不妨把它做对。在这种情况下,它只需要额外的一行。对于整个程序。

相关:What REALLY happens when you don't free after malloc?

关于一般的向后兼容性

有人会说在退出前先释放是个好习惯,只是因为它在某些情况下确实很重要。我的观点是,这些情况非常特殊,例如在使用 OS 编写嵌入式系统时,它们在终止时不会自动释放内存。如果您在这样的环境中编码,您应该知道这一点。如果您正在为一个为您执行此操作的 OS 编写代码,那么为什么不利用它来降低代码复杂性呢?

总的来说,我认为一些 C 编码人员过分关注与 1970 年代的古老计算机的向后兼容性,而这些计算机今天只能在博物馆中遇到。在大多数情况下,假设 ascii、两个补码、8 位的字符大小等是相当公平的。

比较是对网页进行编码,以便可以在 Netscape Navigator、Mosaic 和 Lynx 中查看它们。只在确实有需要时才花时间在这上面。

即使你跳过向后兼容性,也要使用一些保护措施

但是,每当您做出假设时,包含一些元代码可能是一件好事,这些元代码会使编译因错误的目标而失败。例如,如果您的程序依赖于 8 位字符:

_Static_assert(CHAR_BITS == 8, "Char bits");

这将使您的程序在编译时崩溃。如果您正在进行交叉编译,这可能会更复杂。那我就不知道怎么办了。