为什么数组的最大大小是"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_INT
,SIZE_MAX
是size_t
的最大值。因此,大小为 SIZE_MAX
的对象都是可寻址内存。实现将其标记为错误是合理的,但是,我同意只有在分配了实际对象(无论是在堆栈上还是在全局内存中)的情况下,这才是错误。 (为该金额调用 malloc
无论如何都会失败)
它看起来非常像特定于实现的行为。
我 运行 在这里 Mac OS,使用 gcc 6.3.0 我可以编译你的定义的最大尺寸是 SIZE_MAX/2
;使用 SIZE_MAX/2 + 1
它不再编译。
另一方面, clang 4.0.0 最大的是 SIZE_MAX/8
,SIZE_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
已签名,因此实现面临以下选择:
允许大小为 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
或更小的数组的两个指针之间的任何差异。
您只是在处理决定遵循第三种方法的实现。
我和 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_INT
,SIZE_MAX
是size_t
的最大值。因此,大小为 SIZE_MAX
的对象都是可寻址内存。实现将其标记为错误是合理的,但是,我同意只有在分配了实际对象(无论是在堆栈上还是在全局内存中)的情况下,这才是错误。 (为该金额调用 malloc
无论如何都会失败)
它看起来非常像特定于实现的行为。
我 运行 在这里 Mac OS,使用 gcc 6.3.0 我可以编译你的定义的最大尺寸是 SIZE_MAX/2
;使用 SIZE_MAX/2 + 1
它不再编译。
另一方面, clang 4.0.0 最大的是 SIZE_MAX/8
,SIZE_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
已签名,因此实现面临以下选择:
允许大小为
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
或更小的数组的两个指针之间的任何差异。
您只是在处理决定遵循第三种方法的实现。