如果新的块大小比初始大小小,我应该强制执行realloc检查吗?

9
在这种情况下,realloc会失败吗?
int *a = NULL;

a = calloc(100, sizeof(*a));
printf("1.ptr: %d\n", a);

a = realloc(a, 50 * sizeof(*a));
printf("2.ptr: %d\n", a);

if(a == NULL){
    printf("Is it possible?\n");
}

return (0);

在我的情况下,输出结果为:

1.ptr: 4072560
2.ptr: 4072560

所以'a'指向同一个地址。那我应该执行realloc检查吗?

后续编辑:

  • 在Windows XP下使用MinGW编译器。
  • 在Linux上,行为是否类似于gcc?

后续编辑2:这种检查方式可以吗?

int *a = NULL, *b = NULL;

a = calloc(100, sizeof(*a));
b = realloc(a, 50 * sizeof(*a));

if(b == NULL){
    return a;
}
a = b;
return a;

这是一个重复的问题,链接为https://dev59.com/xXI-5IYBdhLWcg3wqqXh,但在我看来,这个问题和答案更好。 - Zan Lynx
6个回答

7

是的,你应该始终对realloc或任何其他内存分配进行检查。

当前重复使用相同地址的行为是一种实现细节,不应依赖于它。这样做只会在库切换其实现或您移动到新平台时引起错误。

这可能会失败吗?可能不会,如果你能找到一个会失败的案例,我会感到惊讶。然而,这并不意味着它不会失败。将realloc包装在一个自动执行每个操作检查的函数中非常简单,没有理由不这样做。

void* xrealloc(void* ptr, size_t size) {
  ptr = realloc(ptr, size);
  if ( !ptr ) {
    exit(EXIT_FAILURE);
  }
  return ptr;
}

1
你能举个例子,当重新分配一个较小的内存块时可能会失败吗? - hanno
3
@hanno,我觉得这是不可能的事情,如果真的能够实现,我会感到惊讶。但是,把这当做证明它不会失败的依据是一种无知的论证。我更喜欢使用包装函数的方式,在所有情况下都检查返回值,这样做更安全。 - JaredPar
1
@nomemory您的检查有效且在我看来看起来没问题,只要返回一个更大尺寸的a是可以的。 - JaredPar
1
如果realloc失败,它不应更改原始指针的内容。因此,a仍将指向calloc的返回值。 - JaredPar
2
@hanno:如果您的malloc实现使用bibop分配器,并且较小的大小因此需要放在新页面上,但是页分配器无法分配页面,则realloc可能会失败。当然,聪明的分配器可以选择不释放旧块并将其返回,但这可能会返回NULL。 - Chris Dodd
显示剩余2条评论

6
当传入的大小小于原分配大小时,realloc 失败是令人惊讶的,但 C 标准(7.20.3.4)没有保证它总是成功的:

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

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

返回值

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

realloc 的一个非常简单的符合实现如下:
void *realloc(void *ptr, size_t size)
{
    void *new_ptr= malloc(size);
    if (new_ptr && ptr)
    {
        size_t original_size= _get_malloc_original_size(ptr);
        memcpy(new_ptr, ptr, min(original_size, size));
        free(ptr);
    }

    return new_ptr;
}

在内存不足的情况下(或任何导致malloc返回NULL的情况下),这将返回NULL
如果原始分配的大小大于或等于请求的大小,则返回相同的指针也是一种非常简单的优化。但这并没有在C标准中规定。

额...哪里说缩小 realloc 一定成功了?我引用同样的文本,得出相反的结论。我担心我们中有人是错的。 - Jonathan Leffler
@Jonathan Leffler:嗯...是的,它在哪里说了呢?我认为你们两个都处于同一艘船上。 - dreamlax
@Jonathan,我说过:“如果realloc传递的大小比原始分配小,失败是令人惊讶的”,而不是说缩小realloc总是成功的。 :) 我想措辞足够微妙,容易引起混淆。 - MSN
如果要求将内存块重新分配为大小为零,符合标准的实现是否可能失败(保留旧内存分配)?由于成功时返回空值是合法的,那么是否还有其他方法可以确定仍需要释放内存块? - supercat
@supercat,理论上它是可以的。这主要取决于malloc(0)是否返回空指针。如果是,则无法确定realloc(ptr, 0)是否成功。如果不是,则可以:success= (realloc(ptr, 0)!=0) - MSN

2

无论何种情况下,检查realloc函数的返回值都是一个好习惯(规范并未说明如果缩小内存块比扩大内存块更安全)。但你需要小心,不要误删原始指针(而这却是你当前代码的问题),否则你将完全无法释放它。


2
C99标准§7.20.3.4(realloc)如下所述:
realloc函数释放ptr指向的旧对象,并返回一个新对象的指针,该对象具有指定大小。新对象的内容应与回收之前的旧对象相同,在新旧大小中小者内。新对象中超出旧对象大小的任何字节都具有不确定值。
如果ptr是空指针,则realloc函数将按照指定大小的malloc函数运行。否则,如果ptr与先前由calloc、malloc或realloc函数返回的指针不匹配,或者空间已经被free或realloc函数回收,则其行为是未定义的。如果无法分配新对象的内存,则不会回收旧对象,并且其值不变。
返回值
realloc函数返回指向新对象的指针(该指针可能与指向旧对象的指针相同),或者如果无法分配新对象,则返回空指针。
请注意,旧对象已被释放;新对象可能指向旧对象的同一位置。这可能会导致问题。虽然这种情况很少见,但与其有奇怪的异常情况,还不如遵循“始终”规则更简单。
常规的反驳是:“如果这不能失败,那么意味着我有一个无法测试的错误路径。”在某种程度上,这是正确的。然而,可能存在某些对内存的践踏,以致于无法分配内存——因为控制信息已经被损坏。更可能的是,你只会得到一个核心转储,但也许代码足够强大,能够避免这种情况。(我假设硬编码的100和50是为了提出问题;真实代码在知道自己需要多少内存时不会过度分配。)
当两个“realloc()”调用相邻时,几乎没有什么事情会出错。然而,实际工作代码之间会有一些操作 - 这些代码可能会导致第二个“realloc()”失败。
关于您的'编辑2'...:
代码可能更好地编写为:
if (b != NULL)
    a = b;
return a;

但基本概念是正确的。请注意,标准明确表示,如果无法创建新的分配,则原始分配是安全的。


谢谢你的回答。这段代码只是“虚拟”的,只是为了理解概念。 - Andrei Ciobanu

1

realloc() 在减小内存大小时很容易返回NULL

void *ptr = malloc(10);
ptr = realloc(ptr, 0);
if (ptr == NULL) {
  puts("Failure because return value is NULL? - not really");
}

realloc(any_pointer, 0) 可能会返回 NULL 或者一些 not-NULL 指针,这是由具体实现决定的。

这就是为什么 realloc()/malloc() 失败不应该简单地测试 if (ptr == NULL),而是需要

void *ptr = malloc(newsize); // or realloc(..., newsize)
if (ptr == NULL && newsize > 0) {
  exit(0); // Handle OOM;
}

由于这种歧义性,如果代码想要创建一个 realloc() 封装器,建议采用类似以下方式:

void *xrealloc(void *ptr, size_t newsize, bool *falure) {
  *failure = 0;
  if (newsize > 0) {
    void *tmp = realloc(ptr, newsize);
    if (tmp == NULL) {
      *failure = 1;
      return ptr;  // old value
    }
    return tmp;  // new value
  } 
  free(ptr);
  return NULL; // new value
  }

如果使用realloc()函数时,调整后的大小比原来小并且获取了NULL值,那么这并不是真正的失败,因此这个答案只是间接适用,但是OP的问题是"...如何在新块大小小于初始块大小时强制执行realloc检查?",然后使用了不太可靠的if (ptr == NULL)范例。


1

相对于 realloc() 所花费的时间,检查所需的时间非常短,我甚至看不出为什么这会成为一个问题。或者你想要减少代码行数吗?


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