58

我与这个答案的印象相同,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 中的一个错误,因为基于有符号整数类型设置最大允许大小没有任何意义。

4

4 回答 4

67

限制 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 类型更大或相等的正范围。

标准提供了关于该主题的这3 条评论4 。


(引自 ISO/IEC 9899:201x)

1(7.19常用定义2)
类型为
ptrdiff_t
,即两个指针相减结果的有符号整数类型;
size_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,这意味着没有对象大小被视为违反运行时约束。

于 2017-03-03T10:01:47.817 回答
22

范围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和使用相同宽度ptrdiff_t的数组对象。接受这样一个事实,即指针减法可能会溢出并导致未定义的行为,如果指针比元素相距更远。语言规范不禁止这种方法。size_tSIZE_MAX / 2

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

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

于 2017-03-04T09:31:46.177 回答
5

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

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

另一边,witch clang 4.0.0 最大的是SIZE_MAX/8, 并且SIZE_MAX/8 + 1中断了。

于 2017-03-03T09:47:24.697 回答
0

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

于 2017-03-03T09:38:56.647 回答