c-向size_t添加或分配一个整数文本



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

size_t foo = 1;
foo += 1;

这里发生了什么转换,size_t是否会"升级"为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。

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

size_t foo = SIZE_MAX;
foo += 1;

类型size_t为无符号类型1,最小范围为:2

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

类型int可以定义为具有16个精度位(加上一个符号位(、2的补码表示和范围:[-6553665535]。

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

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

对于任何可以想象的实现,避免有符号溢出的解决方案是使用无符号整数常量:

foo += 1u;

在这种情况下,如果foo的秩低于int,则会使用常用的算术转换将其提升为无符号int。


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

2(引用自ISO/IEC 9899/201x 7.20.3其他整数类型的极限2(
大小限制_t
SIZE_MAX 65535

3(引用自ISO/IEC 9899/201x 6.3.1.1布尔值、字符和整数2(
以下内容可用于表达式中,只要int或unsigned int可以使用:
具有整数类型(int或无符号int除外(的对象或表达式其整数转换秩小于或等于int的秩,并且无符号整数
如果int可以表示原始类型的所有值(受宽度限制,对于位字段(,则该值被转换为int;否则,它将转换为无符号整数。这些被称为整数提升。所有其他类型由整数促销。

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

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

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

更有可能的是,SIZE_MAX将小于INT_MAX,因此由于超出范围的分配,代码将是实现定义的。通常,实现定义是"显而易见"的,高位被丢弃,因此结果将是0

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

相关内容

  • 没有找到相关文章

最新更新