为什么数组的最大大小是"too large"?

Why is the maximum size of an array "too large"?

我和 this answer 的印象相同,即 size_t 总是由标准保证足够大以容纳给定系统的最大可能类型。

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

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

typedef uint8_t array_t [SIZE_MAX];

error: size of array 'array_t' is too large

我是不是对标准有什么误解?对于给定的实现,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_t是一种可以容纳任何对象大小的类型。任何对象的大小都受地址总线宽度的限制(忽略多路复用和可以处理例如 32 位和 64 位代码的系统,称之为 "code width")。类似于最大整数值MAX_INTSIZE_MAXsize_t的最大值。因此,大小为 SIZE_MAX 的对象都是可寻址内存。实现将其标记为错误是合理的,但是,我同意只有在分配了实际对象(无论是在堆栈上还是在全局内存中)的情况下,这才是错误。 (为该金额调用 malloc 无论如何都会失败)

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

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

另一方面, clang 4.0.0 最大的是 SIZE_MAX/8SIZE_MAX/8 + 1 中断。

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

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

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

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

Standard 提供关于主题的这些3 条评论4


(引自ISO/IEC 9899:201x)

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

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

3(K.3.4 整数类型 3)
非常大的 object 大小通常是计算 object 大小的标志 不正确。例如,负数在以下情况下显示为非常大的正数 转换为无符号类型,如 size_t。此外,某些实现不支持 objects 与类型 size_t.

可以表示的最大值一样大

4(K.3.4 整数类型 4)
由于这些原因,有时限制 object 大小的范围以检测 编程错误。对于针对具有大地址空间的机器的实现, 建议将RSIZE_MAX定义为最大尺寸中的较小者 object 支持或 (SIZE_MAX >> 1),即使这个限制小于 一些合法但非常大的 objects。针对小型机器的实现 地址空间可能希望将 RSIZE_MAX 定义为 SIZE_MAX,这意味着没有 object 大小被视为 runtime-constraint 违规。

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

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

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

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

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

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

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