realloc 是否改变它的参数

Does realloc mutate its arguments

  1. realloc 是否改变了它的第一个参数?
  2. 改变第一个参数是否取决于实现?
  3. 有什么理由不应该 const?作为反例 memcpy 使其 src 论点 const.

ISO C 标准,第 7.20.3 节内存管理函数,未指定。 realloc 的 Linux 手册页未指定。

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

int main() {
  int* list = NULL;
  void* mem;
  mem = realloc(list, 64);
  printf("Address of `list`: %p\n", list);
  list = mem;
  printf("Address of `list`: %p\n", list);
  mem = realloc(list, 0);
  printf("Address of `list`: %p\n", list);
  // free(list);  // Double free
  list = mem;
  printf("Address of `list`: %p\n", list);
}

当我在我的 Debian 笔记本电脑上 运行 以上代码时:

首先,形式上,realloc 在分配新对象并复制内容后释放其第一个参数指向的内存。因此,从语义上讲,指向的类型不符合 const 是绝对正确的。在有限的情况下,新对象的地址可能与旧对象的地址相同,但正确的程序基本上甚至看不到这一点(与旧指针相比是未定义的行为),更不用说依赖它了。

其次,我认为您混淆了参数类型和指向类型的 const-ness。 const 在参数类型上没有任何意义(并且被语言忽略,除了在被调用函数的实现中它使局部变量接收参数常量)因为参数总是 values,而不是对调用者中某个对象的引用。当然realloc不能改变你传递给它的调用者指针变量的。但是,由于任何无效指针的使用都是未定义的行为,您的程序可以(因为 UB 允许任何事情)表现出行为 就好像 调用者的副本已被修改。例如,将它与新指针进行比较可能会得出不一致的结果。 memcpysrc 上的 const 生成指向 const 类型的指针,而不是 const 类型。

realloc() 可以释放其参数指向的内存,如果它不能重用相同的内存。我认为这被认为是一种突变(因为它有效地完全摧毁了它)。

在语义上,realloc() 等同于:

void *realloc(void *ptr, size_t size) {
  void *result = malloc(size);
  if (result && ptr) {
    memcpy(result, ptr, min(size, _allocation_size(ptr)));
    free(ptr);
  }
  return result;
}

其中 _allocation_size() 是 C 运行时的一些内部函数,用于确定动态内存分配的大小。

由于 free() 的参数未声明 [​​=15=],realloc() 的第一个参数也未声明。

该函数不会更改原始指针,因为它处理的是指针的副本。那就是指针不是通过引用传递的。

考虑以下程序

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

int main(void) 
{
    int *p = malloc( sizeof( int ) );
    *p = 10;

    printf( "Before p = %p\n", ( void * )p );

    char *q = realloc( p, 2 * sizeof( int ) );

    printf( "After  p = %p\n", ( void * )p );

    free( q );

    return 0;
}

它的输出是

Before p = 0x5644bcfde260
After  p = 0x5644bcfde260

如您所见,指针 p 未更改。

然而,新指针 q 可以具有与调用 realloc 之前指针 p 相同的值。

来自C标准(7.22.3.5的realloc函数)

4 The realloc function returns a pointer to the new object (which may have the same value as a pointer to the old object), or a null pointer if the new object could not be allocated.

当然如果你会写

    p = realloc( p, 2 * sizeof( int ) );

而不是

    char *q = realloc( p, 2 * sizeof( int ) );

那么很明显,通常指针 p 的新值可能与 p 的旧值不同(尽管根据引用可能相同)。例如,如果函数无法重新分配内存。在这种情况下,如果指针 p 的初始值不等于 NULL,则会发生内存泄漏。因为在这种情况下(当指针的初始值不等于NULL时)早期分配的内存地址将丢失。

如果无法释放新的内存范围,则不会释放旧内存 分配是因为函数需要将旧内容复制到 新的内存范围。

来自C标准(7.22.3.5的realloc函数)

If memory for the new object cannot be allocated, the old object is not deallocated and its value is unchanged.

注意这个调用

mem = realloc(list, 0);

不需要return NULL。

来自 C 标准(7.22.3 内存管理函数)

If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.

我不太清楚你所说的 "Does realloc mutate its first argument?" 是什么意思。

它肯定不会改变调用者中指针的值——没有 C 函数可以做到这一点。

但是它会改变指向内存的值吗?这是一个更棘手的问题。

就程序员而言,您将 realloc 一个指向 M 字节的指针交给它,而它 returns 您将一个(可能不同的)指向 N 字节的指针。

如果它返回给你相同的指针(意味着它能够进行重新分配"in place"),并且如果 N ≥ M,它肯定不会触及前 M 个字节。

如果它给你返回相同的指针但是N < M(也就是说,如果你重新分配更小的区域),你将不再被允许访问甚至询问超过M的字节,所以这特别困难说他们是否被修改。 (但事实上,它们很可能已经被修改,在标记它们未使用的过程中,可用于未来分配)。

最后,如果 realloc 给你一个不同的指针,前 M 个字节是 "gone" -- 同样,你不再被允许访问它们,所以很难说是否他们被修改了,但他们可能是,因为他们现在都可以用于未来的分配。

但无论如何:您传递给 realloc 的指针是指向堆的指针,并且 realloc 肯定会在堆工作时改变堆,所以是的,我认为它是安全的说 realloc 改变了它的第一个参数,因此不应声明为 const。 (即使在我讨论的第一种情况下,realloc "definitely did not touch the M former bytes",它可能仍然调整了一些附近的数据结构,以记录新的分配。)

最后,如果 "mutate" 你的意思是 C++ 程序在声明成员变量时允许做的事情 mutable -- 也就是说,在幕后发生了变化到由指针引用的某些数据结构,该指针在其他方面是合格的 const ——嗯,是的,这与 realloc 所做的相差不远。 If realloc's 第一个参数是 const,并且 if realloc 确实执行的修改是数据结构合格为 mutable,那么我想这会起作用——而且 如果 我们在谈论 C++。

但是我们当然不是在谈论 C++;我们谈论的是 C,它甚至没有 mutable 限定符。

(我会说 memcpy 不是一个反例,因为它没有做任何甚至远程闻起来像写入与其第二个参数关联的任何数据结构的任何事情。)

Does realloc mutate its first argument?

如果您的意思是更改作为参数传递的变量的值,答案是否定的。这一点与特定的 realloc() 函数无关,但更普遍地与语言处理参数的方式有关。 C 语言生成每个参数的私有副本,通常在堆栈上,然后再将它们传递给函数,因此对它们的每次更改都限制在本地,并且在函数 returns 和堆栈重用时丢失。形式上,C 语言按值传递几乎所有类型(数组是一个众所周知的例外)。不管怎样,我会在下面回过头来讨论这个问题。

Is mutating the first argument dependent on the implementation?

当然不是。如上所述,这取决于语言。

Is there a reason it should not be const? As a counter example memcpy makes its src argument const.

当然是有原因的

忘记与void* realloc (void* ptr, size_t size)没有关系的void * memcpy ( void * destination, const void * source, size_t num ),让我们考虑动态内存的管理取决于具体的本地实现,但基本上所有的分配例程都是基于内存池,通常划分在小块中,从哪里导出内存块 returned 到我们的程序。我们可以想象,当我们需要缩小块时,系统将删除一些块,返回一个较小的块,这些块顺便保留在同一地址,但如果我们需要扩展,可能我们块后面的块已经分配,​​我们无法继续.

在嵌入式8位微处理器上,可能会发生实际内存块无法扩展,但另一个内存区域足够大的范围,在这种情况下,我们可以将前一个块数据复制到新块中并return它。但在这种情况下,我们在内存中有一个不同的地址。

但是 malloc() 必须是通用的,独立于实现它的机器,从 8 位嵌入式应用程序到具有 GB 级可用内存和虚拟内存支持的 64 位桌面。因此,标准必须提供适合所有情况的定义。

第二点是如何传递重新分配的结果 pass/fail,如果使用 对内存块指针的引用(即传递 &ptr), 如果 returning NULL 失败,原始指针将丢失!。用户要保存它,必须在 realloc() 之前复制指针,但此过程不自然且容易出错。

出于这个原因,在标准库中,问题是从不同的方面来解决的:重新分配将正式return总是一个新分配的内存块,其中已复制了以前的内存块数据。程序员只需要在使用前检查结果(见下面的代码示例)。

标准在函数定义中非常明确,正如其他答案中已经提到的那样,为了完整起见,我在下面报告。来自 ISO/IEC 9899:2017 §7.22.3.5 realloc 函数:

The realloc function deallocates the old object pointed to by ptr and returns a pointer to a new object that has the size specified by size.

The contents of the new object shall be the same as that of the old object prior to deallocation, up to the lesser of the new and old sizes.

Any bytes in the new object beyond the size of the old object have indeterminate values.

If ptr is a null pointer, the realloc function behaves like the malloc function for the specified size. Otherwise, if ptr does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to the free or realloc function, the behavior is undefined.

If size is nonzero and memory for the new object is not allocated, the old object is not deallocated.

If size is zero and memory for the new object is not allocated, it is implementation-defined whether the old object is deallocated. If the old object is not deallocated, its value shall be unchanged.

The realloc function returns a pointer to the new object (which may have the same value as a pointer to the old object), or a null pointer if the new object has not been allocated.

因为你不知道realloc() return是新对象还是以前的对象,甚至NULL万一出错,你应该考虑realloc()因为总是return创建一个新对象,因此代码:

  int* list = NULL;
  void* mem;
  mem = realloc(list, 64);
  printf("Address of `list`: %p\n", list);

至少有两个原因是错误的:

  1. 显然因为如果 realloc() return 一个新对象并释放 旧内存,变量 list 包含无效指针。此外,它可能会失败 returning NULL,在这种情况下,前一个块仍然有效。
  2. 因为您 不能指望 list 以任何方式将其作为局部参数传递到函数 中。当然 list 将保留其以前的值,即 NULL.

虽然将空指针传递给 realloc() 是符合标准的,因为它明确表示在这种情况下行为将与 malloc() 相同,传递一个 零大小 行为是 实现定义的 暗示前一个块将被某些编译器释放,但不会被其他编译器释放。后者意味着行为可以在编译器的基础上改变,在你的机器上我们可以推断出显然编译器的行为是释放块,因为你得到了双重释放错误和空指针 returned by realloc().另请注意,在后一种情况下,将零大小传递给 realloc() 时,returned NULL 并不意味着发生故障 ,并且功能成功,但如果失败,您将无法正确理解是否有失败。这是函数的歧义(或者据我所知欢迎评论)。

使用 realloc() 时要遵循的黄金法则基本上是这些:

  1. 请记住,从函数 中 return 生成的对象始终是 新对象,你必须保存它。
  2. 因为 realloc() 可能会失败并且 return 一个 NULL 指针,永远不要使用 代码如下,因为如果失败我们将覆盖旧的 对象指针失去恢复数据或释放的可能性 以前的对象。始终使用临时变量来检查 return 值。

示例代码:

void *p = malloc(SIZE);
/* Wrong approach we overwrite anyway teh pointer */
p = realloc(p, 2*SIZE);

/** Correct approach */
void *pTmp = realloc(p, 2*SIZE);
if (NULL == pTmp)
{
    //Error manage code
}
else
{
    p = pTmp;    //assign value
}

现在您可能会问,为什么在许多机器上,如台式机、智能手机等具有虚拟内存管理,经常碰巧没有从 realloc() 编辑的内存地址 return。好吧,关键是,由于虚拟内存管理,更多物理 不连续 内存块可以添加到虚拟内存链,然后可以操作虚拟内存描述符,映射相应的虚拟以用户看到平坦连续虚拟内存的方式对每个物理块进行寻址 space.