无符号转有符号转换

12

考虑以下内容:

#include <iostream>

int main() {

    unsigned int x = 3;
    unsigned int y = 5;

    std::cout << "a: " << x - y        << std::endl;

    std::cout << "b: " << ((int)x) - y << std::endl;

    std::cout << "c: " << x - ((int)y) << std::endl;

    std::cout << "d: " << ((int)x) - ((int)y) << std::endl;

}

$ g++ -Wconversion -Wall uint_stackoverflow.cc -o uint_stackoverflow && ./uint_stackoverflow
a: 4294967294
b: 4294967294
c: 4294967294
d: -2

我理解为什么 "a" 没有给出预期结果。但是为什么 "b" 和 "c" 失败让我感到困惑。对于 "b",我认为在将 "x" 转换为 "int" 后,结果仍然是 "int"。

您能为我指点迷津吗?

编辑:编译器不应该发出警告吗? g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5

谢谢,

某人

3个回答

28
在算术运算中,如果任何一个操作数是unsigned类型,则另一个操作数将转换为unsigned类型(如果它是signed类型),并且操作的结果也将是unsigned类型。同时,将unsigned类型强制转换为signed类型,然后进行操作不会改变操作数的位表示。在二进制补码体系结构中(即几乎所有现代体系结构),(int)xx具有相同的位表示,只有当计算它们在十进制系统中的值时才会解释它们的含义。但重要的是,算术运算是在位表示上执行的(而不是在十进制系统中的值上执行)。由于强制转换不会改变位表示,因此结果的位表示也不会改变。C++03标准在§5/9中说:
很多期望算术或枚举类型操作数的二元操作符以类似的方式引发转换,并产生结果类型。其目的是产生一种通用类型,这种类型也是结果的类型。这种模式称为通常算术转换,定义如下: [...] 否则,如果任意一个操作数是unsigned类型,则另一个操作数将被转换为unsigned类型。

1
请注意,将大的unsigned转换为signed是明确不可移植的。C++11标准规定:“如果目标类型为有符号类型,则当源整数可以表示为目标类型时,值不会改变。否则结果是实现定义的。 - Eponymous
在C语言中,算术运算是针对而非位表示本身进行的。选择二进制补码表示法的一个优点是,有符号算术可以使用与无符号算术相同的指令来完成。但这与强制类型转换是否改变表示方式无关,实际上,在不使用二进制补码的系统上,这个过程也必须是相同的。 - M.M
1
在第一段中最好澄清你特指“unsigned int”,而不是一般的无符号类型(例如,如果操作数是“unsigned short”和“short”,则它们会提升为“int”,而不是任何无符号类型)。 - M.M

12
常规地引用标准文件:
对于C++98,§[expr]/9:

许多二元运算符期望算术类型或枚举类型的操作数并以类似的方式执行转换,并产生结果类型。目的是产生一个公共类型,该类型也是结果类型。这种模式称为通常的算术转换,其定义如下:

  • 如果任一操作数的类型为long double,则另一个操作数将被转换为long double
  • 否则,如果任一操作数为double,则另一个操作数将被转换为double
  • 否则,如果任一操作数为float,则另一个操作数将被转换为float
  • 否则,在两个操作数上执行整型提升(4.5)54)
  • 然后,如果任一操作数为unsigned long,则另一个操作数将被转换为unsigned long
  • 否则,如果一个操作数为long int,另一个操作数为unsigned int,那么如果long int能够表示所有unsigned int的值,则将unsigned int转换为long int;否则,两个操作数都将被转换为unsigned long int
  • 否则,如果任一操作数为long,则另一个操作数将被转换为long
  • 否则,如果任一操作数为unsigned,则另一个操作数将被转换为unsigned

[注:否则,仅剩下的情况是两个操作数均为带符号类型(有符号整型、字符类型和枚举类型)或者其中之一为空(即没有操作数)。—end note]

这可以简要总结为:

  • long double > double > float > unsigned long > long > unsigned > int
  • (比 int 更小的类型会被转换为 int)

C++0x 中的文本已更改(§[expr]/10),但是对 OP 的代码影响相同: int 将被转换为 unsigned


3
因为在进行隐式转换时存在数据类型的层次结构,无符号整数比有符号整数具有更高的优先级,因此b和c被转换回无符号整数,这就是你看到的结果。
如果你不确定类型但知道想要的结果类型,则应像d中一样对x和y进行转换。
这里有一个关于类型转换的非常好的解释。

http://www.learncpp.com/cpp-tutorial/44-type-conversion-and-casting/


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