为什么C ++标准指定有符号整数在具有混合签名的二进制操作中被转换为无符号?

时间:2017-04-11 03:22:51

标签: c++ c casting

C和C ++标准规定,在相同等级的有符号和无符号整数之间的二进制运算中,有符号整数被转换为无符号整数。由此产生的SO有很多问题......让我们称之为奇怪的行为:unsigned to signed conversionC++ Implicit Conversion (Signed + Unsigned)A warning - comparison between signed and unsigned integer expressions% (mod) with mixed signedness等。

但这些都没有说明为什么标准会采用这种方式,而不是倾向于签署。我确实找到了一位自封的大师,他说这是明显正确的做法,但他也没有给出推理:http://embeddedgurus.com/stack-overflow/2009/08/a-tutorial-on-signed-and-unsigned-integers/

查看我自己的代码,无论我在哪里组合有符号和无符号整数,我总是需要从unsigned转换为signed。有些地方无关紧要,但我没有找到一个代码示例,将有符号整数强制转换为无符号。

在正确的事情中铸造到未签名的情况是什么?为什么标准是这样的?

4 个答案:

答案 0 :(得分:9)

如果无法表示值,则从无符号转换为已签名的结果执行定义的行为。从有符号到无符号的转换总是以无符号的比特的幂为模2,因此它总是被很好地定义。

如果在签名类型中可以表示每个可能的无符号值,则标准转换为签名类型。否则,选择无符号类型。这保证了转换始终是明确定义的。

注释

  1. 如评论中所示,C ++的转换算法是从C继承的,以保持兼容性,这在技术上是C ++的原因。

  2. 有人建议,标准中定义签名到无符号转换而不是无符号签名转换的决定在某种程度上是任意的,而另一个可能的决定是对称的。但是,可能的转换对称。

    在标准考虑的两个非2-s补码表示中, n 位符号表示只能表示2 n -1值,而 n 位无符号表示可以表示2 n 值。因此,有符号到无符号的转换是无损的,可以反转(尽管永远不会产生一个无符号值)。另一方面,无符号到符号的转换必须将两个不同的无符号值折叠到相同的有符号结果上。

    在评论中,提出了公式sint = uint > sint_max ? uint - uint_max : uint。这将合并值uint_max和0;两者都映射到0.即使对于非2s补码表示,这有点奇怪,但对于2补码,它是不必要的,更糟糕​​的是,它需要编译器发出代码费力地计算这种不必要的混淆。相比之下,标准的有符号无符号转换是无损的,在通常情况下(2&#s;补码架构),它是无操作的。

答案 1 :(得分:2)

如果选择了带符号的转换,则简单的 a+1 总是会产生单一类型({unless常数被键入为{{1 }}。

假设1Ua,那么在unsigned int的情况下,这种看似无害的增量a+1可能导致未定义的溢出或“索引超出范围”

因此,“无符号转换”似乎是一种更安全的方法,因为人们可能根本不希望在仅添加一个常量时就首先进行转换。

答案 2 :(得分:1)

这是一个半答案,因为我不太了解委员会的推理。

来自C90委员会的理由文件:https://www.lysator.liu.se/c/rat/c2.html#3-2-1-1

  

自K& R出版以来,在整体推广规则的演变中,C的实施之间出现了严重的分歧。实现分为两个主要阵营,可以表征为无符号保留值保留。这些方法之间的区别在于unsigned charunsigned short的处理,当由整体促销扩展时,但决定也会对常量的输入产生影响(见§3.1.3.2)。

...显然也是为了匹配任何运算符的两个操作数而进行的转换。它继续:

  

在绝大多数情况下,两种方案都给出了相同的答案,并且在具有二进制补码算法的实现中以及在有符号溢出上的安静环绕中,两种方案都给出了相同的有效结果 - 也就是说,在大多数当前实现中

然后指定出现歧义歧义的情况,并说明:

  

结果必须被称为有问题的签名,因为可以对签名或无签名解释进行处理。只要unsigned int在运算符中面对signed int,并且signed int具有负值,就会出现完全相同的歧义。 (在解决这种对抗的模糊性时,这两种方案都没有做得更好,或更糟糕。)突然,负面的signed int变成了一个非常大的unsigned int,这可能是令人惊讶的 - 或者它可能是正是知识渊博的程序员所期望的。当然,通过明智地使用演员阵容可以避免所有这些含糊不清。

  

无符号保留规则极大地增加了unsigned int面临signed int产生可疑签名结果的情况的数量,而值保留规则最小化了这种对抗。因此,对于新手或不警惕的程序员来说,保值规则被认为更安全。经过多次讨论后,委员会决定支持保值规则,尽管UNIX C编译器已朝着无符号保留的方向发展。

因此,他们认为int + unsigned的情况属于不受欢迎的情况,并选择charshort的转化规则尽可能少地产生这些情况,即使当时大多数编译器遵循不同的方法。如果我理解正确,那么这个选择迫使他们遵循当前选择int + unsigned产生unsigned操作。

我仍然觉得这一切真的很奇怪。

答案 3 :(得分:1)

<块引用>

为什么 C++ 标准规定在具有混合符号的二元运算中将有符号整数强制转换为无符号整数?

我想你的意思是转化而不是“演员”。强制转换是显式转换。

由于我不是作者,也没有遇到过有关此决定的文档,因此我不能保证我的解释是真实的。然而,有一个相当合理的潜在解释:因为这就是 C 的工作方式,而 C++ 是基于 C 的。除非有机会改进规则,否则没有理由改变什么工作以及程序员已经习惯的东西。我不知道委员会是否考虑过改变这一点。


我知道您可能在想什么:“为什么 C 标准指定有符号整数...”。好吧,我也不是 C 标准的作者,但至少有一个相当广泛的文档,标题为 “Rationale for 美国国家标准 对于信息系统 - 编程语言 - C"。尽管它很广泛,但不幸的是它没有涵盖这个问题(它确实涵盖了一个非常相似的问题,即如何提升比 int 更窄的整数类型,在这方面标准不同于某些早于标准的 C 实现)。

我无法访问标准前的 K&R 文档,但我确实从“专家 C 编程:Deep C Secrets”一书中找到了一段引用了标准前 K&R C 中的规则(在比较规则与标准化规则):

<块引用>

第 6.6 节算术转换

许多运算符以类似的方式导致转换和产生结果类型。这种模式将被称为“通常的算术转换”。

首先,char 或short 类型的任何操作数都转换为int,而float 类型的任何操作数都转换为double。然后,如果任一操作数为 double,则另一个将转换为 double,这就是结果的类型。否则,如果任一操作数为 long,则另一个将转换为 long,这就是结果的类型。否则,如果其中一个操作数是无符号的,则另一个将转换为无符号,这就是结果的类型。否则,两个操作数都必须是 int,这就是结果的类型。

所以,这似乎是 C 标准化之前的规则,大概是设计者自己选择的。除非有人能找到书面理由,否则我们可能永远不会知道答案。


<块引用>

在哪些情况下强制转换为 unsigned 是正确的做法?

这是一个非常简单的案例:

unsigned u = INT_MAX;
u + 42;

文字 42 的类型是有符号的,因此根据您提议的/设计者规则,u + 42 也将被签名。这将是非常令人惊讶的,并且会导致显示的程序由于有符号整数溢出而具有未定义的行为。

基本上,有符号和无符号的隐式转换各有问题。