繁体   English   中英

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

[英]Why does C++ standard specify signed integer be cast to unsigned in binary operations with mixed signedness?

C 和 C++ 标准规定,在相同等级的有符号和无符号整数之间的二元运算中,有符号整数被强制转换为无符号整数。 有很多关于 SO 的问题由此引起......让我们称之为奇怪的行为: 无符号到有符号转换C++ 隐式转换(有符号 + 无符号)警告 - 有符号和无符号整数表达式之间的比较具有混合符号的%(mod) , 等等。

但是这些都没有给出标准为什么会这样,而不是转向有符号整数的任何理由。 我确实找到了一位自称为大师的人,他说这是显而易见的正确做法,但他也没有给出理由: http : //embeddedgurus.com/stack-overflow/2009/08/a-tutorial-on-有符号和无符号整数/

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

在哪些情况下强制转换为 unsigned 是正确的做法? 为什么标准是这样的?

如果值无法表示,则从无符号转换为有符号会导致实现定义的行为。 从有符号到无符号的转换总是模 2 的无符号位大小的幂,所以它总是明确定义的。

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


笔记

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

  2. 写这篇笔记时,C++ 标准允许三种二进制表示,包括符号幅度和补码。 情况不再如此,并且有充分的理由相信,在合理的熊市未来,C 也不会出现这种情况。 我将脚注作为历史遗迹留下,但它与当前语言无关。

    有人建议标准中定义有符号到无符号转换而不是无符号到有符号转换的决定在某种程度上是任意的,并且其他可能的决定是对称的。 然而,可能的转换不是对称的。

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

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

如果选择了有符号转换,那么简单的a+1将始终产生有符号类型(除非将常量输入为1U )。

假设aunsigned int ,那么在arr[a+1]的情况下,这个看似无害的增量a+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 ,这可能令人惊讶——或者它可能正是 a知识渊博的程序员。 当然,所有这些歧义都可以通过明智地使用强制转换来避免。

和:

无符号保留规则大大增加的情况下,其中的数量unsigned int面对signed int以产生可疑地签署结果,而值保持规则最小化这样的对抗。 因此,对于新手或粗心的程序员来说,值保留规则被认为是更安全的。 经过多次讨论,委员会决定支持值保留规则,尽管事实上 UNIX C 编译器已经朝着无符号保留的方向发展。

因此,他们认为int + unsigned的情况是一种不需要的情况,并为charshort选择了尽可能少产生这些情况的转换规则,尽管当时大多数编译器采用了不同的方法。 如果我理解正确,这个选择然后迫使他们遵循当前选择的int + unsigned产生一个unsigned操作。

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

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

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

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


我知道您可能在想什么: “为什么C标准指定有符号整数...” 好吧,我也不是 C 标准的作者,但至少有一个相当广泛的文档,标题为“美国国家信息系统标准的基本原理 - 编程语言 - 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 也将被签名。 这将是非常令人惊讶的,并且会导致显示的程序由于有符号整数溢出而具有未定义的行为。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM