为啥数组的最大大小“太大”?

Posted

技术标签:

【中文标题】为啥数组的最大大小“太大”?【英文标题】:Why is the maximum size of an array "too large"?为什么数组的最大大小“太大”? 【发布时间】:2017-07-23 07:52:27 【问题描述】:

我的印象与this answer 相同,标准始终保证size_t 大到足以容纳给定系统的最大可能类型。

但是,此代码无法在 gcc/Mingw 上编译:

#include <stdint.h>
#include <stddef.h>

typedef uint8_t array_t [SIZE_MAX];

错误:数组 'array_t' 的大小太大

我在这里是否误解了标准中的某些内容?对于给定的实现,size_t 是否允许太大?或者这是 Mingw 中的另一个错误?


编辑:进一步的研究表明

typedef uint8_t array_t [SIZE_MAX/2];   // does compile
typedef uint8_t array_t [SIZE_MAX/2+1]; // does not compile

恰好是一样的

#include <limits.h>

typedef uint8_t array_t [LLONG_MAX];           // does compile
typedef uint8_t array_t [LLONG_MAX+(size_t)1]; // does not compile

所以我现在倾向于认为这是 Mingw 中的一个错误,因为根据有符号整数类型设置允许的最大大小没有任何意义。

【问题讨论】:

大小为SIZE_MAX 的数组可能会占用所有内存。 @PaulOgilvie 那他们为什么选择一个对于给定实现来说太大的数字? 根据 GCC source code,该限制由 sizetype 的签名对应方强制执行(评论中的INT_MAX 具有误导性)。 index 在第 5933 行分配有 c_common_signed_type (sizetype);。这可能解释了“半范围”问题。 @Lundin:我还没有找到任何 cmets,为什么他们采用签名类型,所以这可能是一个错误。编辑:我认为 2501 是正确的,这是由于 ptrdiff_t 类型,它已签名。 您会注意到标准中没有任何内容暗示编译器必须允许最大为SIZE_MAX 的任何大小的对象。它仅意味着编译器不得允许大于SIZE_MAX 的对象。即使您没有实际创建对象也是如此,因为 sizeof 也可以应用于类型。 【参考方案1】:

限制 SIZE_MAX / 2 来自您的实现中 size_t 和 ptrdiff_t 的定义,它们选择类型 ptrdiff_t 和 size_t 具有相同的宽度。

C 标准要求1 size_t 类型是无符号的,而 ptrdiff_t 类型是有符号的。

两个指针之间差异的结果,总是2的类型为ptrdiff_t。这意味着,在您的实现中,对象的大小必须限制为 PTRDIFF_MAX,否则无法在类型 ptrdiff_t 中表示两个指针的有效差异,导致未定义的行为。

因此值 SIZE_MAX / 2 等于值 PTRDIFF_MAX。如果实现选择使最大对象大小为 SIZE_MAX,则必须增加类型 ptrdiff_t 的宽度。但是将对象的最大大小限制为 SIZE_MAX / 2 要容易得多,那就是让 ptrdiff_t 类型比 size_t 类型具有更大或相等的正范围。

Standard 针对该主题提供了这些3 cmets4


(引自 ISO/IEC 9899:201x)

1(7.19 常用定义 2) 类型是 ptrdiff_t 这是两个指针相减结果的有符号整数类型; 尺寸_t 是 sizeof 运算符结果的无符号整数类型;

2(6.5.6 加法运算符 9) 当两个指针相减时,都应指向同一个数组对象的元素, 或者数组对象的最后一个元素;结果是 两个数组元素的下标。结果的大小是实现定义的, 它的类型(一个有符号整数类型)是在头文件中定义的ptrdiff_t。 如果结果在该类型的对象中不可表示,则行为未定义。

3(K.3.4 整数类型 3) 非常大的物体尺寸通常表明物体的尺寸是经过计算的 不正确。例如,负数在以下情况下显示为非常大的正数 转换为无符号类型,如 size_t。此外,某些实现不支持 与 size_t 类型可以表示的最大值一样大的对象。

4(K.3.4 整数类型 4) 由于这些原因,有时限制要检测的对象大小范围是有益的 编程错误。对于针对具有大地址空间的机器的实现, 建议将 RSIZE_MAX 定义为最大尺寸中的较小值 支持的对象或 (SIZE_MAX >> 1),即使此限制小于 一些合法但非常大的对象。针对小型机器的实现 地址空间可能希望将 RSIZE_MAX 定义为 SIZE_MAX,这意味着不存在被视为违反运行时约束的对象大小。

【讨论】:

这是有道理的。那么这是C标准中的一个缺陷吗?这意味着SIZE_MAX 永远不能以有意义的方式使用,但size_t 应该改为使用PTRDIFF_MAX @Lundin 这不是缺陷,因为 SIZE_MAX 不代表对象的最大允许大小的值。即使使用 PTRDIFF_MAX 作为限制也是不正确的,因为理论上它可能大于 SIZE_MAX。我认为正确的值是min(SIZE_MAX,PTRDIFF_MAX) @Lundin:我认为这是不允许的,因为 C 标准将 SIZE_MAX 定义为“size_t 的限制”(C99 第 7.20.3 节)。 @Lundin:您似乎误解了SIZE_MAX 的目的。它不是旨在成为“对象的最大可能大小”。它旨在成为“size_t 类型整数的最大可能值”。虽然我们确实使用size_t 来测量对象的大小,但并不要求实现实际上允许创建如此巨大的对象。需要明确的是,SIZE_MAX / 2 在 64 位系统上仍然是一个荒谬的巨大数字;没有一个理智的程序员会想要创建一个即使是静态全局变量也这么大的数组。 该标准允许实现提供大于 PTRDIFF_MAX 的对象;唯一的缺点是相距足够远的两个指针相减会导致未定义的行为。 (这是一个很大的缺点,并解释了为什么他们选择不这样做)。【参考方案2】:

size_t 的范围保证足以存储实现支持的最大对象的大小。反之则不然:不能保证您能够创建一个大小填满size_t 整个范围的对象。

在这种情况下,问题是:SIZE_MAX 代表什么?支持的最大对象大小?还是size_t 中可表示的最大值?答案是:是后者,即SIZE_MAX(size_t) -1。不能保证您能够创建 SIZE_MAX 字节大的对象。

其背后的原因是除了size_t,实现还必须提供ptrdiff_t,它旨在(但不保证)存储指向同一个数组对象的两个指针之间的差异。由于ptrdiff_t 类型已签名,因此实现面临以下选择:

    允许大小为SIZE_MAX 的数组对象并使ptrdiff_t size_t 更宽。它必须至少宽一点。这样的ptrdiff_t 可以适应两个指向SIZE_MAX 或更小的数组的指针之间的差异。

    允许大小为SIZE_MAX 的数组对象并使用与size_t 宽度相同ptrdiff_t。接受这样一个事实,即指针减法可能溢出并导致未定义的行为,如果指针相距比SIZE_MAX / 2 元素更远。语言规范不禁止这种方法。

    使用与size_t 相同宽度的ptrdiff_t,并限制最大数组对象大小SIZE_MAX / 2。这样的ptrdiff_t 可以容纳两个指向SIZE_MAX / 2 或更小的数组的指针之间的差异。

您只是在处理决定遵循第三种方法的实现。

【讨论】:

问题中的代码并没有尝试创建任何数组对象,它只创建了一个typedef。那么实现拒绝 typedef 是不是不符合规范? 我真的认为这接近于一个规范的答案(对于所有这些 ptrdiff/size/intptr 的东西)并且会敦促你将你的其他答案合并到这个答案中,概述每个的 _MAX 和 _MIN 常量在开始解释之前输入问题(因为参考资料更容易)并将其他答案的细节带入这个答案。干得好! 实际上在***.com/questions/9386979/… 上有你的答案和答案我几乎觉得让另一个人保持开放是一种伤害。也许您可以迁移您对该问题的答案,然后完全关闭它。 不确定是否需要更新以在非常棒的细分中获得更多对称性,请随意抓住它,这样处理起来会容易得多。 (恕我直言)gist.github.com/EvanCarroll/121dfb870fd3da0dc52ba4e7edefe3ee【参考方案3】:

它看起来非常像特定于实现的行为。

我在这里运行 Mac OS,使用 gcc 6.3.0,我可以编译您的定义的最大大小是 SIZE_MAX/2;使用 SIZE_MAX/2 + 1 它不再编译。

另一边,女巫铿锵4.0.0最大的是SIZE_MAX/8,而SIZE_MAX/8 + 1休息。

【讨论】:

SIZE_MAX/8+1 有趣。那里的错误信息是什么?你能成功mallocSIZE_MAX/8+1吗? 错误信息大同小异:error: array is too large (2305843009213693952 elements) clang 4.0.0 RSIZE_MAX 的值是多少,PTRDIFF_MAX 的值是多少? SIZE_MAX: 18446744073709551615 RSIZE_MAX: 9223372036854775807 PTRDIFF_MAX: 9223372036854775807 确实如此。不,我什至不能 malloc SIZE_MAX/128/1024: blah-blah, set a breakpoint in malloc_error_break to debugSIZE_MAX/256/1024 malloc 很好。对那些不改变行为的人 +1/-1。【参考方案4】:

只是从头开始推理,size_t 是一种可以容纳任何对象大小的类型。任何对象的大小都受到地址总线宽度的限制(忽略多路复用和可以处理例如 32 位和 64 位代码的系统,称为“代码宽度”)。与最大整数值MAX_INT 类似,SIZE_MAXsize_t 的最大值。因此,大小为SIZE_MAX 的对象都是可寻址内存。实现将其标记为错误是合理的,但是,我同意仅在分配实际对象的情况下它是错误,无论是在堆栈上还是在全局内存中。 (无论如何,拨打malloc 都会失败)

【讨论】:

以上是关于为啥数组的最大大小“太大”?的主要内容,如果未能解决你的问题,请参考以下文章

当行大小太大时如何转换MYSQL UTF-8?

为啥 Java 堆的最大大小是固定的?

“RangeError:超出最大调用堆栈大小”为啥?

SugarCRM - 数据库故障 - 行大小太大?

为啥同样的文件我压缩后和原来的文件大小差不多?

为啥下载东西的时候总是出现“文件太大,无法下载啊”