C语言中的无符号整数溢出问题

4
考虑以下 C 代码片段:
#include <stdint.h>

uint32_t inc(uint16_t x) {
 return x+1;
}

当在纯x86_64系统上使用gcc-4.4.3编译并使用标志-std=c99 -march=core2 -msse4.1 -O2 -pipe -Wall时,它会产生以下结果:

movzwl %di,%eax
inc    %eax
retq

现在,在C语言中,预测会发生无符号溢出。我对x86_64汇编不太了解,但据我所见,16位参数寄存器被移动到32位寄存器中,并进行递增并返回。我的问题是,如果x == UINT16_MAX,则会发生溢出,标准规定x + 1 == 0,对吗?然而,由于%eax是一个32位寄存器,它现在包含UINT16_MAX + 1,这是不正确的。
这让我想到一个问题:是否有一种便携式的方法来禁用C中的无符号溢出,以便编译器可以假设存储在大型寄存器中的小变量的上位比特始终为0(因此它们不需要清除)?如果没有(或者解决方案在语法上很糟糕),那么至少在GCC中有没有办法实现呢?
非常感谢您的时间。
4个回答

6
不,C类型会受到默认的提升。假设uint16_t的转换等级低于int,它将被提升为int,然后加法将作为int执行,最后返回时转换为uint32_t
至于您在结尾处的相关问题,我不太明白您想要什么。

@R.: 难道不是应该升级为 unsigned int 吗? - Jens Gustedt
@Jens:不会。uint16_t的范围适合于int,因此它将被提升为int。对于unsigned char也是一样。 - R.. GitHub STOP HELPING ICE
抱歉,我没有考虑到那个。不过,当我将代码更改为“return x +(uint16_t)1”时,它给出了相同的结果。我相信提升规则规定两个uint16_t的和为uint16_t,或者它们会给出一个uint32_t? - Luís Fernando Schultz Xavier
@Luis:不,C语言中不存在int类型更小的算术运算。任何算术表达式的结果都具有int类型或更大类型。如果int类型的转换级别高于uint16_t类型,则两个uint16_t变量的和具有int类型;如果int类型的转换级别低于uint16_t类型,则它们的和具有uint16_t类型。具体情况取决于您的实现中uint16_t类型的级别,但是有规则将类型范围的相对大小与其级别相关联。 - R.. GitHub STOP HELPING ICE
@R:对我来说,标准不太连贯,更别提简单了。:-) 除此之外,在x86_64上使用gcc,sizeof(int)==4,但sizeof(size_t)==sizeof(void*)==8。因此,这个问题可以扩展到类型uint64_t和uint32_t,它们的等级大于或等于int。这样我们就可以绕过那个奇怪的限制,再次是我的意见。 - Luís Fernando Schultz Xavier
你也可以在普通的32位x86上使用这些类型,结果应该是相同的。问题在于使用了一个太小的类型。 - R.. GitHub STOP HELPING ICE

3

使用不使用编译器中间件进行计算的编码风格,请注意,(1)将具有数据类型int

uint32_t inc(uint16_t x) {
 uint16_t y = x + 1;
 return y;
}

那样可以得到正确的行为。谢谢。不过,我想要的是一个关于如何让编译器假设不会发生这种溢出的想法,这样它就不需要擦除代表 y 的寄存器中的额外位。 - Luís Fernando Schultz Xavier
1
你将不得不使用另一种语言,这是C语言的一个非常基本的构件。使用示例包括用于数字信号处理的伽罗瓦域算术。 - Steve-o

0
标准描述整数溢出的一个奇特之处是允许编译器假设不会发生溢出。在您展示的情况下,编译器不需要保留溢出的行为,因为假设x+1可能取的所有可能值范围(假定不存在溢出)都适合返回类型。

真实但不相关。这个表达式不会溢出。 - R.. GitHub STOP HELPING ICE

-1
关于您的第二个问题,在C语言中,无符号类型没有溢出这一说法,适用的术语是“环绕”。按照定义,无符号类型计算时模2^width。每当您将一个更宽的无符号类型转换为一个较窄的类型时,上位比特将被简单地丢弃。所有的C编译器都应该像这样实现,您不必担心任何问题。
本质上,无符号类型非常简单,只有对于有符号类型才会出现麻烦的事情。

正确但无关紧要。此代码不包含任何溢出、有符号或无符号。 - R.. GitHub STOP HELPING ICE
这实际上是相关的。编译器会为我完成,但它会消耗运行时性能。我想禁用它。 - Luís Fernando Schultz Xavier
@R:确保代码中不包含它。我只是回答了问题,所以它与第二个问题相关。 - Jens Gustedt
@Luís Fernando Schultz Xavier:我认为这对性能没有影响。只要始终使用正确的无符号类型,任何看起来复杂的屏蔽操作或其他操作都只会转化为简单的汇编指令。如果你真的担心,可以查看生成的汇编代码,如果还是很担心,可以进行基准测试。 - Jens Gustedt
1
@Jens Gustedt:这正是我试图避免的情况。生成的汇编代码会随着编译器版本、平台和其他一些因素而改变。我正在寻找一种改变无符号类型操作语义的方法。 - Luís Fernando Schultz Xavier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接