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

在最近的一次代码审查中 ,有人声称

在select系统上, calloc()可以分配超过SIZE_MAX总字节数,而malloc()是有限的。

我的主张是错误的,因为calloc()为一个对象数组创建了空间 – 作为一个数组,它本身就是一个对象。 并且任何对象的大小都不能超过SIZE_MAX

那么我们哪个是正确的? 在地址空间大于size_t范围的(可能是假设的)系统上,当使用产品大于SIZE_MAX参数调用时, calloc()允许成功?

为了使它更具体:以下程序是否会以非零状态退出?

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

SIZE_MAX不必指定对象的最大大小,而是指定size_t的最大值,这不一定是同一个东西。 请参阅为什么数组的最大大小“太大”? ,

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

malloc / calloc是它们分配没有类型的对象。 具有类型的对象具有限制,例如从不大于某个限制,如SIZE_MAX 。 但是这些函数的结果指出的数据没有类型。 它还不是一个数组。

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

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

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

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

作为断言“在选择系统上, calloc()可以分配超过SIZE_MAX总字节数,而malloc()是有限的。” 来自我发布的评论 ,我将解释我的理由。


为size_t

size_t是一些至少16位的无符号类型。

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都是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为真, 那么这违反了什么规范?

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

 // 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 

calloc()详细信息

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

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


对于具有大型参数的calloc() ,返回非NULL符合或不符合这种情况,这当然是不常见的 。 通常这种分配超出可用内存,因此问题没有实际意义。 我遇到的唯一情况是使用巨大的内存模型 ,其中size_t为16位,对象指针为32位。

7.22.3.2 calloc函数

概要
1

  #include  void *calloc(size_t nmemb, size_t size);` 

描述
2 calloc函数为nmemb对象数组分配空间,每个对象的大小都是size。 空间初始化为所有位零。

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

我不明白为什么分配的空间应该限制在SIZE_MAX字节。

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

该标准也没有定义可能存在的实现限制,所以这有点全权 ,但我认为最大对象大小实际上与对象分配相关是合理的。 (顺便说一句,最大对象大小通常小于 SIZE_MAX ,因为对象中的指针到char的差异必须在ptrdiff_t表示。)

这引出了以下观察:对calloc (SIZE_MAX, 2)调用超出了最大对象大小限制,因此实现可以返回任意值,同时仍符合标准。

某些实现实际上将返回一个指针,该指针对于calloc (SIZE_MAX / 2 + 2, 2)类的调用不为null,因为实现不会检查乘法结果是否不适合size_t值。 考虑到在这种情况下可以如此轻松地检查实现限制,这是一个好主意是否是另一回事,并且有一种非常好的方式来报告错误。 就个人而言,我认为calloc缺少溢出检查是一个实现错误,并且在我看到它时已经向实现者报告了错误,但从技术上讲,它只是一个实施质量问题。

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

 size_t length = SIZE_MAX / 2 + 2; short object[length]; 

这里没有任何实现可以做,所以它必须是未定义的。

根据标准的文本,也许,因为标准是(有些人会故意说)对这种事情含糊不清。

按6.5.3.4¶2:

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

根据7.19¶2:

为size_t

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

如果实现允许任何大小在size_t无法表示的类型(包括数组类型),则前者不能满足。 请注意,无论您是否解释有关calloc返回的指向“数组”的指针的文本,总会有一个涉及任何对象的数组: unsigned char[sizeof object]类型的重叠数组,它是其表示forms

最好的情况是,允许创建大于SIZE_MAX (或PTRDIFF_MAX ,由于其他原因)的任何对象的实现具有致命的QoI(实现质量)问题。 除非您专门尝试确保与特定的破坏C实现的兼容性(有时与嵌入式相关等),否则您应该考虑代码审查的这种不良实现的声明是假的。

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

标准并没有要求实现提供任何创建指向这些大对象的指针的方法。 但是,它定义了一个函数calloc() ,其描述表明可以要求它尝试创建这样的对象,并且如果无法创建对象,则建议calloc()返回空指针。

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