calloc()可以分配的总数超过SIZE_MAX吗?

时间:2018-10-08 09:47:37

标签: c language-lawyer

a recent code review中,有人声称

  

在某些系统上,calloc()可以分配多于SIZE_MAX个字节,而malloc()受限制。

我的说法是错误的,因为calloc()为对象数组创建了空间-该对象数组本身就是一个对象。而且任何物体的大小都不能超过SIZE_MAX

那么我们哪个是正确的?在地址空间大于size_t范围的(可能是假设的)系统上,用乘积大于calloc()的自变量调用SIZE_MAX是否可以成功?

更具体地说:以下程序是否会以非零状态退出?

#include <stdint.h>
#include <stdlib.h>

int main()
{
     return calloc(SIZE_MAX, 2) != NULL;
}

7 个答案:

答案 0 :(得分:20)

  

calloc()总共可以分配超过SIZE_MAX吗?

作为断言“在某些系统上,calloc()可以分配多于SIZE_MAX个总字节,而malloc()受限制”。来自我发布的comment,我将解释其基本原理。


size_t

size_t是至少16位的 unsigned 类型。

  

size_t,它是sizeof运算符结果的无符号整数类型; C11dr§7.192

     

其实现定义的值的大小应等于或大于   ...低于下面给出的相应值” ... size_t SIZE_MAX的限制... 65535§7.20.32

sizeof

  

sizeof运算符产生其操作数的大小(以字节为单位),可能是   表达式或类型的括号名称。 §6.5.3.42

calloc

void *calloc(size_t nmemb, size_t size);
  

calloc函数为nmemb对象的数组分配空间,每个对象size的大小。 §7.22.3.22


请考虑一种情况,其中nmemb * size远远超过SIZE_MAX

size_t alot = SIZE_MAX/2;
double *p = calloc(alot, sizeof *p); // assume `double` is 8 bytes.

如果calloc()确实分配了nmemb * size个字节,并且如果p != NULL为true,则这违反了什么规范?

每个元素(每个对象)的大小是可表示的。

// Nicely reports the size of a pointer and an element.
printf("sizeof p:%zu, sizeof *p:%zu\n", sizeof p, sizeof *p); 

每个元素都可以访问。

// Nicely reports the value of an `element` and the address of the element
for (size_t i = 0; i<alot; i++) {
  printf("value a[%zu]:%g, address:%p\n", i, p[i], (void*) &p[i]); 
}

calloc()详细信息

nmemb个对象的数组空间”:这当然是争用的关键点。 “为数组分配空间”是否需要<= SIZE_MAX?我在C规范中没有发现任何要求此限制的内容,因此得出结论:

  

calloc()可能总共分配了超过SIZE_MAX


对于calloc()来说,不常见肯定是带有大量参数以返回不符合{{1}通常,此类分配超出了可用内存,因此该问题不存在。我遇到的唯一情况是使用Huge memory model,其中NULL是16位,而对象指针是32位。

答案 1 :(得分:16)

SIZE_MAX不必指定对象的最大大小,而可以指定size_t的最大值,这不一定是同一件事。参见Why is the maximum size of an array "too large"?

但是显然,将大于SIZE_MAX的值传递给期望使用size_t参数的函数的定义并不明确。因此,理论上SIZE_MAX是限制,而理论上calloc将允许分配SIZE_MAX * SIZE_MAX个字节。

使用malloc / calloc的事情是他们分配没有类型的对象。具有类型的对象具有限制,例如永远不能大于某个限制,例如SIZE_MAX。但是这些函数的结果所指向的数据没有类型。它还不是数组。

形式上,数据没有声明的类型,但是当您在分配的数据中存储某些内容时,它会获得用于存储的数据访问的有效类型(C17 6.5§6)。

这反过来意味着calloc可能会分配比C中任何类型都无法容纳的内存更多的内存,因为分配的内容(至今)还没有类型。

因此,就C标准而言,calloc(SIZE_MAX, 2)返回与NULL不同的值是完全可以的。另一回事是如何以合理的方式实际使用分配的内存,或者哪些系统甚至支持堆上如此大的内存。

答案 2 :(得分:2)

来自

  

7.22.3.2 calloc函数

     

简介
  1

 #include <stdlib.h>
 void *calloc(size_t nmemb, size_t size);`
     

说明
  2 calloc函数为nmemb对象数组分配空间,每个对象的大小均为size。该空间初始化为所有零位。

     

退货
  3 calloc函数返回空指针或指向已分配空间的指针。

我看不到为什么分配的空间应限制为SIZE_MAX个字节。

答案 3 :(得分:2)

如果程序超出实现限制,则行为未定义。这是根据将实施限制定义为实施对程序施加的限制(C11中的3.13)得出的。该标准还说,严格遵守的程序必须遵守实施限制(C11中为4p5)。但这通常也意味着程序,因为该标准不会说明在超出大多数实现限制时会发生什么(因此,另一种未定义的行为是标准未指定会发生什么)。

该标准也没有定义可能存在的实现限制,因此有点“空白”,但是我认为最大对象大小实际上与对象分配有关是合理的。 (顺便说一下,最大对象大小通常比SIZE_MAX ,因为对象中指向char的指针的差异必须在{{1}中可表示}。

这使我们得出以下观察结果:对ptrdiff_t的调用超出了最大对象大小限制,因此,实现可以返回任意值,同时仍符合标准。

某些实现实际上会返回一个指针,该指针对于像calloc (SIZE_MAX, 2)这样的调用不为空,因为该实现不会检查乘法结果是否不适合calloc (SIZE_MAX / 2 + 2, 2)值。考虑到在这种情况下可以很容易地检查实现限制,并且报告错误的方法非常好,因此这是否是个好主意。就我个人而言,我认为size_t中没有进行溢出检查是一个实现错误,当我看到这些错误时已经向实现者报告了这些错误,但是从技术上讲,这仅仅是实现质量问题。

对于堆栈上的可变长度数组,关于超过实现限制导致未定义行为的规则更加明显:

calloc

在这里,实现实际上无能为力,因此它必须是未定义的。

答案 4 :(得分:2)

也许是因为该标准的文字,因为该标准(有些人会故意说)含糊不清。

根据6.5.3.4¶2:

  

sizeof运算符产生其操作数的大小(以字节为单位)

并按照7.19¶2:

  

size_t

     

这是sizeof运算符的结果的无符号整数类型;

如果实现允许使用size_t中无法表示大小的任何类型(包括数组类型),则通常无法满足前者。请注意,无论您是否解释由calloc返回的指向“数组”的指针的文本,任何对象都始终包含一个数组:类型为unsigned char[sizeof object]的覆盖数组表示形式

充其量,一个允许创建大于SIZE_MAX(或出于其他原因而成为PTRDIFF_MAX的对象的实现的致命QoI(实现质量)问题非常严重。除非您特别尝试确保与特定的损坏的C实现(有时与嵌入式等相关)兼容,否则代码审查声称应该考虑此类不良实现是虚假的。

答案 5 :(得分:2)

只是一个补充:使用一点点数学,您就可以证明SIZE_MAX * SIZE_MAX = 1(根据C规则评估时)。

但是,calloc(SIZE_MAX,SIZE_MAX)只允许执行以下两项操作之一:返回指向SIZE_MAX字节的SIZE_MAX元素数组的指针,或者返回NULL。不允许仅通过将参数相乘,得到1的结果并分配一个字节(清除为0)来计算总大小。

答案 6 :(得分:0)

该标准没有说明是否有可能以某种方式创建指向ptr+number1+number2的有效指针,但是number1+number2会超过SIZE_MAX。它肯定允许number1+number2超过PTRDIFF_MAX的可能性(尽管出于某些原因,C11决定要求即使具有16位地址空间的实现也必须使用32位ptrdiff_t

该标准不要求实现提供任何创建指向此类大对象的指针的方法。但是,它确实定义了一个函数calloc(),该函数的描述表明可能会要求它尝试创建这样的对象,并且建议calloc()应该返回一个空指针(如果不能)创建对象。

然而,有效分配任何类型对象的能力是实现质量问题。该标准绝不会要求任何特定的分配请求成功,也不会禁止实现返回可能无法使用的指针(在某些Linux环境中,malloc()可能会产生指向以下区域的过度分配的指针):地址空间;在没有足够的物理存储空间时尝试使用指针可能会导致致命陷阱。如果calloc(x,y)x的数值乘积超过SIZE_MAX,那么对于y的非反复无常的实现返回null肯定比产生不能被返回的指针更好。用于访问该字节数。但是,该标准没有提及,是否应该返回一个可用来访问每个y个字节的x个对象的指针比返回null更好或更糟。每种行为在某些情况下都是有利的,而在其他情况下则是不利的。