【问题标题】:Realloc returns NULL, but does not set errno. How do I properly check for this error/odd behavior?Realloc 返回 NULL,但不设置 errno。如何正确检查此错误/奇怪行为?
【发布时间】:2020-01-28 07:19:46
【问题描述】:

考虑这段代码:

main() {
   float *ptr = NULL;
   while(true) {
      ptr = (float *)realloc(ptr, 0*sizeof(float));
      fprintf(stdout,"Errno: %d, Ptr value: %d\n",errno, (int)ptr);
   }
}

奇怪的是,errno 从未设置(至少对我而言),但调用交替返回 NULL 和一个指针值。我的想法是 0 分配可能会返回某种错误,但不会严重到无法设置 errno。或者带有 realloc 的代码有问题。我不确定。

我有点不在乎,但这会导致(0 字节)内存泄漏。

“Realloc 失败”问题与它在很大程度上假定从 realloc() 返回 NULL 是一个错误并不完全相同。在这种情况下并非如此。这主要是关于 realloc() 在传递零大小时的不同行为。

【问题讨论】:

  • 0*... = 0 - 它应该做什么?
  • NULL 在这种情况下并不表示失败。 如果 size 为 0,则 malloc() 返回 NULL,或稍后可以成功传递给 free() 的唯一指针值 - 来自 man 3 realloc
  • 我需要阅读 realloc 上的手册页,但您要求的内存为 0,但它没有给您,这不是错误情况,我不希望 errno设置。
  • 来自cppreference:“如果 new_size 为零,则行为是实现定义的(可能会返回空指针(在这种情况下,旧的内存块可能会或可能不会被释放),或者一些非可能会返回可能不用于访问存储的空指针)”。您正在分配零字节,因此您处于“依赖于实现”的领域。
  • “这导致我发生(0 字节)内存泄漏” -- 所以根本没有泄漏?

标签: c realloc errno


【解决方案1】:

看着:

https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#__libc_realloc

我可以看到发生了什么。根据代码,如果输入指针不为null,并且新的大小为0,则释放旧指针并返回NULL。

但是,如果输入指针为 NULL,则 realloc 仅充当 malloc 并返回 malloc(0) 生成的内容。 (在这种情况下,它不会检查大小是否为 0。)

所以,没有errno,因为没有错误。但是 realloc() 返回 NULL 不一定是错误,这是我没有意识到的细微差别。

因此,在本例中,第一次调用 (0 size, NULL ptr) 返回一个已分配的指向零大小数据区域的指针。第二次调用(0 大小,非 NULL ptr)例程释放指针并返回 NULL。然后循环重复。

【讨论】:

  • 很好地调查了这个问题,但手册页中明确说明了这一点。
  • 它说:“如果 size 等于 0,并且 ptr 不为 NULL,则调用相当于 free(ptr)。”然后它说:“free() 函数不返回任何值。”然后它还说:“如果 size 等于 0,则返回 NULL 或适合传递给 free() 的指针。”我同意已经说明了这种行为,但可能并不完全清楚,至少对我来说不是。
  • @MarcoBonelli:Unix 手册页不是有关标准 C 问题的适当来源。
  • @MarcoBonelli -- 在这个问题上使用的 c 标签确实声明:“这个标签应该用于有关 C 语言的一般问题,如 ISO 9899 标准(最新版本,9899 :2018,除非另有说明...."
  • @MarcoBonelli:除非另有说明,否则带有 C 标签的问题适用于标准 C。在任何情况下,如果您认为没有要求提供关于 C 标准的答案表明答案不应该是关于 C 标准,那么没有要求提供关于 Unix 的答案也表明答案不应该是关于 Unix。无论哪种情况,Unix 手册页都无关紧要。当然,给出一个依赖于 Unix 特定行为的答案会误导为其他系统编写代码的人。
【解决方案2】:

realloc 返回NULL,但不设置errno。如何正确检查此错误/奇怪行为?

对于realloc()errno 未指定由 C 标准设置。 errno 的任何设置都是此处实现定义的行为。

C 确实指定:

如果请求的空间大小为零,则行为是实现定义的:要么返回空指针以指示错误,要么行为就好像大小是某个非零值,除了返回的指针应不能用于访问对象。 C17/18 § 7.22.3 1

如果大小为零且未分配新对象的内存,则由实现定义是否释放旧对象。 C17/18 § 7.22.3.5 3

考虑避免 3 个实现定义的行为点。使用辅助函数并在新大小为 0 时调用 free()

// Return error status
bool realloc_float(float **ptr, size_t new_size) {
  // Size zero or too big ....
  if (new_size == 0 || new_size > SIZE_MAX/sizeof(float)) {
    free(*ptr);
    *ptr = NULL;
    return new_size > 0;  // fail on large new_size
  }
  float *newp = realloc(*ptr, sizeof(float) * new_size);
  if (newp == NULL) {
    free(*ptr);
    *ptr = NULL;
    return true; // failure
  }
  *ptr = newp;
  return false;
}

用法

int main() {
   float *ptr = NULL;
   for (int i = 0; i < 10; i++) {
      size_t sz = rand()%4; 
      bool err = realloc_float(&ptr, sz);
      printf("Error: %d, Ptr value: %p, size %zu\n", err, (void*)ptr, sz);
   }
   free(ptr); 
}

【讨论】:

    【解决方案3】:

    只需使用中间变量。例如

    int main( void ) {
       float *ptr = NULL;
       while(true) {
          float *tmp = realloc(ptr, 0*sizeof(float));
    
          if ( !tmp ) 
          {
              // report an error
              break;
          }
    
          ptr = tmp;
          //...
       }
    }
    

    至于零大小的内存请求则根据 C 标准(7.22.3 内存管理功能)

    如果请求的空间大小为零,则行为是 实现定义: 要么返回空指针,要么返回 行为就好像大小是某个非零值,除了 返回的指针不得用于访问对象。

    【讨论】:

    • OP 报告的行为不是错误。正在分配零字节,因此行为是“实现定义的”。
    • 这解决了realloc 的常见误用问题,但它与OP 的问题并没有真正的关系(这是对realloc 在请求零字节时的行为方式的误解)。
    • @ShadowRanger:cppreference 是一个非常低质量的源,不应该使用。 “在这种情况下,旧的内存块可能会或可能不会被释放”的说法是不可用的,因为你必须有 memleaks 或双重释放,而你无法分辨是哪个。 C 不允许这样做。
    • 参见 7.22.3.5 realloc 函数¶3:“如果无法为新对象分配内存,则不会释放旧对象并且其值不变。”和 ¶4:“realloc 函数返回一个指向新对象的指针(它可能与指向旧对象的指针具有相同的值),或者如果无法分配新对象,则返回一个空指针。”不允许返回空指针但释放对象。
    • 引用在 C17/18 中已过时。刚刚回答了here“如果请求的空间大小为零,则行为是实现定义的:返回空指针表示错误,...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-25
    • 1970-01-01
    • 2016-07-30
    • 2015-04-29
    • 2016-12-16
    • 2017-06-05
    相关资源
    最近更新 更多