【问题标题】:Portability of realloc behaviorrealloc 行为的可移植性
【发布时间】:2021-04-10 12:16:35
【问题描述】:

我一直在编写在许多循环中分配数据的代码,发现使用realloc() 非常方便,因为它一致地处理初始和循环中间数据,而无需添加释放和分配新指针的条件。

我写了这个小测试程序:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    char *x = NULL;
    x = realloc (x, 0);
    printf ("Address of realloc (NULL, 0): %p\n", x);
    assert (x != NULL);
    printf ("Address of realloc (x, 8): %p\n", x);
    x = realloc (x, 8);
    assert (x != NULL);
    x = realloc (x, 0);
    assert (x == NULL);
    printf ("Address of realloc (x, 0): %p\n", x);
    x = realloc (x, 21);
    printf ("Address of realloc (x, 21): %p\n", x);
    assert (x != NULL);
    x = realloc (x, 0); // free.
    assert (x == NULL);
    printf ("Address of realloc (x, 0): %p\n", x);

    return 0;
}

并进行内存检查:

$ gcc -Wall -g test_realloc.c -o test_realloc.o
$ valgrind ./test_realloc.o 
==1192613== Memcheck, a memory error detector
==1192613== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1192613== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info
==1192613== Command: ./test_realloc.o
==1192613== 
Address of realloc (NULL, 0): 0x4a3a040
Address of realloc (x, 8): 0x4a3a040
Address of realloc (x, 0): (nil)
Address of realloc (x, 21): 0x4a3a510
Address of realloc (x, 0): (nil)
==1192613== 
==1192613== HEAP SUMMARY:
==1192613==     in use at exit: 0 bytes in 0 blocks
==1192613==   total heap usage: 4 allocs, 4 frees, 1,053 bytes allocated
==1192613== 
==1192613== All heap blocks were freed -- no leaks are possible
==1192613== 
==1192613== For lists of detected and suppressed errors, rerun with: -s
==1192613== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

到目前为止,很好。唯一需要注意的是,初始值必须初始化(甚至是NULL)。

现在我想知道跨平台的可移植性如何。 Linux x86_64 上的 man 3 realloc 非常明确地说明了这种行为,但 realloc (NULL, 0) 在这种情况下会意外产生非 NULL 地址。

我可以在多大程度上依赖这种行为?

编辑:确切地说,我的问题是我是否可以依靠我的示例程序来避免任何平台上的 UB 或内存泄漏。

谢谢。

【问题讨论】:

  • 您可以依赖 realloc 的行为,但您需要停止假设它永远不会返回 NULL。 assert 不是错误检查。
  • 注意 recalloc(ptr,0) 调用释放内存不在 C 标准中。
  • @12431234123412341234123 来自手册页:“如果 size 等于 0,并且 ptr 不为 NULL,则调用相当于 free(ptr)。”我应该不依赖手册页吗?便携性?
  • 我主要担心我的示例程序在任何平台上都不会出现内存泄漏或 UB。 realloc (NULL, 0) 是否产生 NULL 无关紧要,只要我不依赖它。
  • 我必须纠正自己:释放大小为 0 的行为是 C89 明确要求的,但在较新的 C 标准中没有。据我了解较新的标准和 POSIX 标准,它甚至不符合释放指针的标准。 TLDR:不要使用realloc(somePointer,0);

标签: c memory-management malloc


【解决方案1】:

在询问“这种行为”时,您的问题并没有具体说明您对哪种行为感兴趣。大概您对realloc 是否可能会或可能不会为realloc(x, 0) 返回空指针以及它是否会返回感兴趣释放x指向的先前分配的空间。

这两个问题的答案都是允许的,但 C 实现必须记录它所做的。

可能返回空或非空。

C 2018 7.22.3 1 说“......如果请求的空间大小为零,则行为是实现定义的:返回空指针以指示错误,或者行为就像大小是非零值,但返回的指针不得用于访问对象。” 实现定义意味着实现必须记录它的作用,所以这应该在编译器和/或标准库文档中说明。

空间可以释放也可以不释放。

关于使用realloc 释放空间,C 2018 7.22.3.5 3 表示“......如果size 为零并且未分配新对象的内存,则由实现定义是否释放旧对象......”

结论

要编写可移植代码,不要使用realloc 来释放分配的所有空间。使用free

要分配“零”空间,要么容忍 malloc 的任何返回值(null 或非 null),要么总是分配至少一个字节,即使您不需要它。

【讨论】:

    【解决方案2】:

    这就是 C11 标准(更准确地说,它是一个草案,但它是最接近公开的标准)所说的:

    概要

        #include <stdlib.h>
        void *realloc(void *ptr, size_t size);
    

    说明

    realloc 函数释放 ptr 指向的旧对象,并返回一个指向具有 size 指定大小的新对象的指针。新对象的内容应与释放前旧对象的内容相同,直至新旧大小中的较小者。新对象中超出旧对象大小的任何字节都具有不确定的值。

    如果 ptr 是空指针,则 realloc 函数的行为类似于指定大小的 malloc 函数。否则,如果 ptr 与内存管理函数先前返回的指针不匹配,或者如果空间已通过调用 free 或 realloc 函数被释放,则行为未定义。如果无法为新对象分配内存,则不会释放旧对象并且其值不变。

    返回

    realloc 函数返回一个指向新对象的指针(它可能与指向旧对象的指针具有相同的值),如果无法分配新对象,则返回一个空指针。

    https://port70.net/~nsz/c/c11/n1570.html#7.22.3.4

    所以realloc (NULL, 0)malloc(0) 相同,可能会返回一个不可取消引用的非空指针。

    【讨论】:

    • 看起来是来自以前的标准,2011 年。2018 年标准有附加文本。
    • @12431234123412341234123:释放空间不是不合格的。我在回答中引用了 2018 C 标准(官方而非草案)中的文本,它说是否释放旧对象是实现定义的。
    【解决方案3】:

    realloc(ptr, 0) 不等同于free(ptr)

    引用标准(C18, §7.22.3.5 - IIRC, C11 没有改变):

    重新分配函数 概要 1 #include void *realloc(void *ptr, size_t 大小); 描述 2 realloc函数释放ptr指向的旧对象,返回一个指向新对象的指针 具有由 size 指定的大小的对象。新对象的内容应与 释放之前的旧对象,直到新旧大小中的较小者。新的任何字节 超出旧对象大小的对象具有不确定的值。 3 如果 ptr 是空指针,则 realloc 函数的行为类似于指定大小的 malloc 函数。 否则,如果 ptr 与内存管理函数先前返回的指针不匹配,或者如果 空间已通过调用 free 或 realloc 函数被释放,行为未定义。 如果 size 不为零且未分配新对象的内存,则不会释放旧对象。 如果大小为零并且未分配新对象的内存,则由实现定义是否 旧对象被释放。如果旧对象未被释放,则其值应保持不变。 4 realloc 函数返回一个指向新对象的指针(可能与 指向旧对象的指针),如果尚未分配新对象,则为空指针。

    如您所见,它没有为大小为 0 的 realloc 调用指定特殊情况。它明确指出如果您调用 realloc(ptr, 0) 会发生什么,行为由实现定义。

    这几乎回答了您的问题:使用 realloc 代替免费不是您可以依赖的东西。

    此外:assert(x != NULL) 是测试代码。每当分配内存时,你真的应该检查:

    if (x == NULL) {}
    

    您看到的行为恰好是 GCC 选择实施规范的方式,但不能依赖它。

    link to C18 standard

    【讨论】:

    • 标准已经改变。 2018 年标准在 realloc 子句中有新文本。
    • @EricPostpischil 对,会更新
    【解决方案4】:

    TL;DR:不。不要使用realloc(somePointer,0)

    看起来realloc(somePointer,0) 在不同的 C 标准之间发生了变化。来自 C89 [1]:

    ... 如果 ptr 是空指针,realloc 函数的行为 就像指定大小的 malloc 函数一样。 ...如果空间 无法分配,ptr指向的对象不变。 如果 size 为零且 ptr 不是空指针,它指向的对象是 释放。

    但在较新的 C 标准中不再是这种情况了。

    来自 POSIX 手册页 [2]:

    realloc() 的描述比之前的有所修改 本标准的版本符合 ISO/IEC 9899:1999 标准。以前的版本明确允许调用 realloc(p, 0) 释放 p 指向的空间并返回 null 指针。虽然这种行为可以解释为允许 这个版本的标准,C 语言委员会有 表明这种解释是不正确的。应用 应该假设如果 realloc() 返回一个空指针,空间 p 指向的还没有被释放。由于这可能导致 双释放,如果为空,实现也应该设置 errno 指针实际上表示失败,应用程序应该 仅在更改 errno 时才释放空间。

    [1]http://port70.net/~nsz/c/c89/c89-draft.html#4.10.3.4

    [2]https://pubs.opengroup.org/onlinepubs/9699919799/functions/realloc.html

    【讨论】:

      猜你喜欢
      • 2013-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-16
      • 1970-01-01
      相关资源
      最近更新 更多