在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;
}
答案 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
更好或更糟。每种行为在某些情况下都是有利的,而在其他情况下则是不利的。