在C中,我看到很多代码为size_t
变量添加或赋予整数文字。
size_t foo = 1;
foo += 1;
此处发生了什么转换,size_t
升级后是否会发生?#34;到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
将如何排名或者究竟是什么:
结果的值是实现定义的,其类型(无符号整数类型)是在头中定义的size_t。
答案 0 :(得分:5)
当前的C标准允许在执行以下代码时可能导致未定义行为的实现,但是这样的实现不存在,并且可能永远不会:
size_t foo = SIZE_MAX;
foo += 1;
类型size_t为无符号类型 1 ,最小范围: 2 [0,65535]。
类型size_t可以定义为unsigned short类型的同义词。无符号短类型可以定义为具有16个精度位,范围为:[0,65535]。在这种情况下,SIZE_MAX的值为65535。
类型int可以定义为具有16个精度位(加上一个符号位),2的补码表示,范围:[ - 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 / IEC 9899 / 201x 7.20.3其他整数类型的限制2)
size_t
的限制
SIZE_MAX 65535
3 (引自ISO / IEC 9899 / 201x 6.3.1.1布尔,字符和整数2)
无论int还是unsigned int都可以在表达式中使用以下内容
使用:
具有整数类型的对象或表达式(int或unsigned int除外)
其整数转换等级小于或等于int和的等级
unsigned int。
如果int可以表示原始类型的所有值(由宽度限制,对于a
bit-field),将值转换为int;否则,它将转换为无符号
INT。这些被称为整数促销。所有其他类型都没有改变
整数促销。
答案 1 :(得分:3)
这取决于,因为size_t
是实现定义的无符号整数类型。
涉及size_t
的操作因此会引入促销,但这些操作取决于size_t
实际上是什么,以及表达式实际涉及的其他类型。
如果size_t
等同于unsigned short
(例如16位类型),那么
size_t foo = 1;
foo += 1;
会(在语义上)将foo
提升为int
,添加1
,然后将结果转换回size_t
以存储在foo
中。 (我说"在语义上",因为这是符合标准的代码的含义。编译器可以自由地应用"好像"规则 - 即做任何它喜欢的事情,如只要它提供相同的净效果)。
另一方面,如果size_t
等同于long long unsigned
(例如64位有符号类型),则相同的代码会将1
提升为{{1}类型},将其添加到long long unsigned
的值,并将结果存储回foo
。
在这两种情况下,除非发生溢出,否则净结果是相同的。在这种情况下,没有溢出,因为foo
和int
都可以保证能够代表值size_t
和1
。
如果发生溢出(例如,添加更大的整数值),则行为可以变化。有符号整数类型(例如2
)的溢出导致未定义的行为。 int
整数类型的溢出使用模运算。
关于代码
unsigned
可以进行同样的分析。
如果size_t foo = SIZE_MAX;
foo += 1;
等同于size_t
,则unsigned short
将转换为foo
。如果int
等同于int
,则它不能代表signed short
的值,因此转换将溢出,结果是未定义的行为。如果SIZE_MAX
能够表示比int
更大的范围(例如,它等同于short int
),则long
到foo
的转换将成功,递增该值将成功,并且存储回int
将使用模运算并生成size_t
的结果。
如果0
等同于size_t
,则值unsigned long
将转换为1
,将unsigned long
添加到foo
将使用模运算(即产生零的结果,并将其存储到foo
。
假设size_t
实际上是其他无符号整数类型,可以进行类似的分析。
注意:在现代系统中,与size_t
大小相同或小于int
的{{1}}是不寻常的。但是,这样的系统已经存在(例如,Microsoft和Borland C编译器在具有80286 CPU的硬件上以16位MS-DOS为目标)。还有16位微处理器仍在生产中,主要用于具有较低功耗和低吞吐量要求的嵌入式系统,以及针对它们的C编译器(例如针对Infeon XE166微处理器系列的Keil C166编译器)。 [注意:我从来没有理由使用Keil编译器,但是,鉴于它的目标平台,如果它支持的16位size_t
大小相同或小于它,那就不足为奇了。该平台上的原生int
类型。
答案 2 :(得分:2)
foo += 1
表示foo = foo + 1
。如果size_t
比int
更窄(即int
可以代表size_t
的所有值),那么foo
将被提升为int
表达式foo + 1
。
这可能会溢出的唯一方法是INT_MAX
== SIZE_MAX
。从理论上讲,这是可能的,例如16位int和15位size_t
。 (后者可能有1个填充位)。
更有可能的是,SIZE_MAX
将小于INT_MAX
,因此由于超出范围的分配,代码将是实现定义的。通常,实现定义是"显而易见的"一,高位被丢弃,因此结果为0
。
作为一个实际的决定,我不建议修改你的代码以满足这些情况(15位size_t
,或非显而易见的实现 - 定义),这可能从未发生过,也永远不会发生。相反,你可以做一些编译时测试,如果确实发生这些情况就会给出错误。编译时断言INT_MAX < SIZE_MAX
在这个时代是实用的。