【问题标题】:How to avoid overflow in realloc?如何避免 realloc 中的溢出?
【发布时间】:2017-08-28 06:20:27
【问题描述】:

使用calloc(x, y) 可以在C 中安全地分配x 大小为y 的元素,calloc() 将负责乘法x*y

但是,例如 realloc() 仅将新大小作为参数,我想知道如何使用 realloc() 安全地重新分配 x*y 字节。

如果x*y 不适合size_t 怎么办? calloc() 是如何处理这个问题的?

【问题讨论】:

  • 只是一个想法:如果您需要担心分配大小的溢出,您可能需要将算法分解为更小的子部分?
  • @SouravGhosh 这是供其他人使用的数据类型库的一部分,他们只需要提供元素大小和要存储的元素数量。如何使用realloc 安全地执行此操作(我已经有一些内存 - 所以calloc 不是一个选项)?
  • 如果你的算法需要超过 4GB 的内存,那么问题就很严重了 :D
  • @tilz0R 这用于数据类型。我关心溢出有什么问题?
  • 你没毛病,提一下吧:)

标签: c memory-management malloc realloc calloc


【解决方案1】:

size_t是无符号类型,size_t的最大值是reallocmalloc可以分配的对象的绝对最大大小;这在宏 SIZE_MAX 中可用。在 32 位个人计算机上,size_t 通常是 32 位; 64 位计算机上的 64 位。应该够了。

为保证item_size * n_items的计算不溢出,可以将SIZE_MAX除以item_size,并保证结果值大于等于n_items

size_t max_items = SIZE_MAX / item_size;
if (max_items < n_items) {
    // an overflow would occur
}
else {
    // it is ok
}

如果分配不成功,calloc 必须返回NULL,所以calloc 很可能有与上述类似的检查。

【讨论】:

  • 非常感谢!我会尽快投票(目前没有足够的代表)。
  • calloc() 可以分配大于SIZE_MAXarrays,规范没有阻止。发布您的 C 规范引用以支持“size_t 的最大值是可以使用 realloc 分配的对象的绝对最大大小”
  • @chux 肯定是隐含的吧?
  • @Alnitak 我的错误。我读到您的回答是“size_t 的最大值是可以使用 calloc 分配的对象的绝对最大大小。我同意 malloc(), realloc() 的限制,但不同意 calloc()
  • @chux 这不是我的答案
【解决方案2】:

简短的回答是你不能安全地做到这一点。您最多只能限制您尝试分配的内存量,使其不超过SIZE_MAX

SIZE_MAXsizeof 运算符可以产生的最大值。

C 中的每种数据类型都必须具有可以使用 sizeof 计算的大小,包括数组以及使用 malloc()calloc()realloc() 分配的任何连续内存块。

如果x*y 在数学上大于SIZE_MAX,则无法以任何方式分配该内存量。即使底层系统支持,C 程序也无法完全使用该内存块。

还有一个问题是计算 x*y(假设 xy 的类型为 size_t)将使用模运算,因此实际上给出的结果在数学上等同于 (x*y)%(SIZE_MAX + 1)

【讨论】:

    【解决方案3】:

    如果 x*y 不适合 size_t 怎么办? calloc() 是如何处理的?

    realloc()malloc() 受到限制,因为传递给它们的大小参数被限制为 SIZE_MAXcalloc() 不是这样

    兼容的 C 实现不需要calloc() 限制为仅分配SIZE_MAX 的内存。以下可能有效。单个类型的最大大小可以是 SIZE_MAXarray 的大小可以大到 SIZE_MAX SIZE_MAX-1 "bytes",而下面的 iptr不是数组,而是指针。

    // Assume sizeof(double) == 8
    double *iptr = calloc(SIZE_MAX, sizeof *iptr);
    

    重新分配如此大的指针是有问题的,因为它需要再次调用calloc()


    如何避免realloc中的溢出?

    OP 的问题不在于 realloc() 可以处理什么,而是代码如何计算传递给它的值可能会溢出。

    为了确保 unsigned 类型,例如 size_t,不会溢出乘法:

      if (b && a > SIZE_MAX/b) Handle_Overflow();
      prod = a*b;
    

    【讨论】:

    • 数组大小不能和SIZE_MAX一样大,索引不能和SIZE_MAX-1一样大吗?
    • @DavidBowling 当然,数组大小可以是SIZE_MAX-1,我现在确定你是正确的,大小甚至可以是 1 或:SIZE_MAX。然而我并没有关注这个问题,因为calloc() 可以在一次调用中分配比SIZE_MAX 更多的内存。不幸的是,我确实发现许多实现没有正确检测/处理calloc() 中的溢出,因此即使规范不允许,如此大的分配本身就是 ID。
    • calloc() 的问题是我认为的重点;我之前没有注意到calloc()malloc() 之间的这种区别,但现在很明显。有关数组最大大小的文档似乎散布在标准中,但我在revision 5.10 of the C99 Rationale (PDF) 的第 6.5.3.4 节中找到了一个参考资料。
    • @DavidBowling 数组的最大大小与*alloc() 没有直接关系。您关注的是哪一个、数组或分配功能/限制?
    • 数组。 “数组大小可以像SIZE_MAX-1 一样大”这句话让我寻找参考。我在 C11 标准中能找到的最好的内容是 §6.7.6.2/1:“如果他们界定了一个表达式(它指定了一个数组的大小),那么表达式应该是一个整数类型。”这让我想知道一个数组是否可以包含多个 SIZE_MAX 元素,因为这只能保证至少为 65535。似乎 size_t 需要与最宽的 unsigned 整数类型相当,但我不知道在标准中看不到这一点。我错过了什么?
    【解决方案4】:

    size_t 是无符号类型。

    对于无符号类型,溢出行为是确定性的,如 C11 中所述

    [....] 因为不能用生成的无符号整数类型表示的结果是 以比最大值大一的数字为模减少,可以是 由结果类型表示。

    因此,只要确保生成要存储在变量类型size_t 中的值的操作不会在进程中溢出,将该变量传递给realloc() 就不会产生影响。最多,realloc() 将无法分配该内存。

    【讨论】:

    • 我正在努力保持C99 的合规性。这也符合C99 标准吗?你是否暗示我应该只检查x*y &lt; x || x*y &lt; y 并在这种情况下安全失败?
    • 在 C99 中应该也是这样,是的。
    • 谢谢!我会尽快给你我的支持(目前没有足够的代表)。现在我想知道calloc 是如何处理这种情况的?它会因为size_t 溢出而失败还是会使用某种魔法?
    • @user194192 你是什么意思 calloc 如何处理?方法应该相同,它将提供的值作为size_t 并乘以块号。如果结果很大,calloc(0 可能会发出失败的信号。就是这样。
    • @user194192 : x*y &lt; x || x*y &lt; y 不是 一个很好的溢出检查。举个例子。 x = 3; y = 0x80000000;(带有 32 位 size_t)。
    【解决方案5】:

    如果x * y 溢出size_t,realloc 将尝试分配溢出的值:

    bytes = (x * y) % 2^(sizeof(size_t) * 8)
    

    所以realloc 将“看到”bytes 的字节数。它不关心任何事情。

    【讨论】:

    • 谢谢!我还想知道calloc 是否能够分配超过sizeof(size_t) * 8 字节或calloc 在这种溢出情况下也会失败?
    • 无法分配更多。
    • "f x * y 溢出 size_t,realloc 将尝试分配溢出的值:" C 规范不支持。此行为可能是您平台上观察到的行为,但未指定也不应依赖。
    猜你喜欢
    • 2018-04-13
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 1970-01-01
    • 2011-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多