将整数文字添加或分配给 size_t

Adding or assigning an integer literal to a size_t

在 C 中,我看到很多代码将整数文字添加或分配给 size_t 变量。

size_t foo = 1;
foo += 1;

这里发生了什么转换,size_t 是 "upgraded" 到 int 然后又转换回 size_t 吗?如果我在最大值,那还会环绕吗?

size_t foo = SIZE_MAX;
foo += 1;

这是定义的行为吗?它是一个无符号类型 size_t,它添加了一个带符号的 int(可能是更大的类型?)并转换回 size_t。有符号整数溢出的风险吗?

foo + bar + (size_t)1 代替 foo + bar + 1 有意义吗?我从来没有见过这样的代码,但我想知道如果整数提升很麻烦,是否有必要。

C89 没有说明 size_t 将如何排名或具体是什么:

The value of the result is implementation-defined, and its type (an unsigned integral type) is size_t defined in the header.

视情况而定,因为 size_t 是实现定义的无符号整数类型。

因此,涉及 size_t 的操作将引入提升,但这取决于 size_t 实际是什么,以及表达式中实际涉及的其他类型。

如果 size_t 等同于 unsigned short(例如 16 位类型)则

size_t foo = 1;
foo += 1;

会(语义上)将 foo 提升为 int,添加 1,然后将结果转换回 size_t 以存储在 foo 中. (我说 "semantically",因为这是根据标准的代码的含义。编译器可以自由应用 "as if" 规则——即做任何它喜欢的事情,只要它提供相同的网络效果)。

另一方面,如果 size_t 等同于 long long unsigned(例如 64 位有符号类型),则相同的代码会将 1 提升为 long long unsigned,将其添加到 foo 的值,并将结果存储回 foo

在这两种情况下,除非发生溢出,否则最终结果是相同的。在这种情况下,没有溢出,因为 intsize_t 都保证能够表示值 12.

如果发生溢出(例如添加更大的整数值),则行为可能会有所不同。有符号整数类型的溢出(例如 int)会导致未定义的行为。 unsigned 整数类型的溢出使用模运算。

至于代码

size_t foo = SIZE_MAX;
foo += 1;

可以进行相同类型的分析。

如果 size_t 等同于 unsigned short,则 foo 将转换为 int。如果int等价于一个signed short,它不能表示SIZE_MAX的值,所以转换会溢出,结果是未定义的行为。如果 int 能够表示比 short int 更大的范围(例如相当于 long),那么 fooint 的转换就会成功,增加该值将会成功,并且存储回 size_t 将使用模运算并产生 0.

的结果

如果size_t等于unsigned long,那么值1将被转换为unsigned long,将其添加到foo将使用模运算(即产生零结果),并将存储到 foo.

假设size_t实际上是其他无符号整数类型,可以做类似的分析。

注意:在现代系统中,与 int 大小相同或更小的 size_t 是不常见的。然而,这样的系统已经存在(例如,Microsoft 和 Borland C 编译器针对具有 80286 CPU 的硬件上的 16 位 MS-DOS)。还有 16 位微处理器仍在生产中,主要用于具有较低功耗和低吞吐量要求的嵌入式系统,以及针对它们的 C 编译器(例如,针对 Infeon XE166 微处理器系列的 Keil C166 编译器)。 [注意:我从来没有理由使用 Keil 编译器,但是考虑到它的目标平台,如果它支持与本机大小相同或更小的 16 位 size_t 也就不足为奇了 int 在那个平台上输入。

foo += 1表示foo = foo + 1。如果size_tint窄(即int可以表示size_t的所有值),则foo提升为int表达式 foo + 1

唯一可能溢出的方法是 INT_MAX == SIZE_MAX。理论上这是可能的,例如16 位 int 和 15 位 size_t。 (后者可能会有 1 个填充位)。

更有可能的是,SIZE_MAX 将小于 INT_MAX,因此由于超出范围分配,代码将是 实现定义的 。通常实现定义是 "obvious" 一个,高位被丢弃,所以结果将是 0.

作为一个实际的决定,我不建议修改您的代码来满足这些情况(15 位 size_t,或不明显的实现定义),这些情况可能从未发生过,也永远不会发生。相反,您可以进行一些编译时测试,如果这些情况确实发生,则会给出错误。编译时断言 INT_MAX < SIZE_MAX 在当今时代是实用的。

当前的 C 标准允许在执行以下代码时可能会导致未定义行为的实现,但是这样的实现不存在,而且可能永远不会存在:

size_t foo = SIZE_MAX;
foo += 1;

类型size_t为无符号类型1,最小范围:2 [0,65535]。

size_t 类型可以定义为 unsigned short 类型的同义词。 unsigned short 类型可以定义为具有 16 个精度位,范围为:[0,65535]。在这种情况下,SIZE_MAX 的值为 65535。

int 类型可以定义为具有 16 个精度位(加上一个符号位)、二进制补码表示和范围:[-65536,65535]。

表达式 foo += 1 等同于 foo = foo + 1(除了 foo 只计算一次,但这里无关紧要)。变量 foo 将使用整数提升 3 进行提升。它将被提升为 int 类型,因为 int 类型可以表示 size_t 类型的所有值,而 size_t 的等级是 unsigned short 的同义词,低于 int 的等级。由于 size_t 和 int 的最大值相同,因此计算会导致有符号溢出,从而导致未定义的行为。

这适用于当前标准,它也适用于 C89,因为它对类型没有任何更严格的限制。

为任何可想象的实现避免有符号溢出的解决方案是使用无符号整型常量:

foo += 1u;

在那种情况下,如果 foo 的级别低于 int,它将使用通常的算术转换提升为 unsigned int。


1(引自ISO/IEC 9899/201x 7.19常用定义2)
size_t
sizeof运算结果的无符号整数类型;

2(引自ISO/IEC9899/201x 7.20.3 其他整数类型的极限2)
size_t
的限制 SIZE_MAX65535

3(引自ISO/IEC9899/201x 6.3.1.1布尔、字符和整数2)
以下内容可用于 int 或 unsigned int 可能出现的表达式中 被使用:
整数类型的对象或表达式(int 或 unsigned int 除外) 其整数转换秩小于或等于 int 的秩并且 无符号整数。
如果一个 int 可以表示原始类型的所有值(受宽度限制,对于 位字段),该值被转换为 int;否则,它被转换为无符号 诠释。这些称为整数促销。所有其他类型都没有改变 整数促销。