【问题标题】:what happens when i mix signed and unsigned types ?当我混合有符号和无符号类型时会发生什么?
【发布时间】:2014-09-01 15:32:30
【问题描述】:

我正在学习 C++ 语言,我对类型转换有些疑问,你能解释一下在这样的表达式中会发生什么吗:

unsigned int u = 10; 
int a = -42; 
std::cout << u - a << std::endl;

在这里我知道,如果我在有两个数学运算符时应用规则,结果将是 52。但我想知道当编译器将 a 转换为无符号值时会创建一个无符号类型的临时值时会发生什么,之​​后会发生什么?现在的表达式应该是 10 -4294967254。

【问题讨论】:

  • @dandan78 我想了解编译器是如何在底层工作的
  • @dandan78:有时这可能很幼稚;特别是如果计算的任何方面未定义。
  • 第 1 步:获得一份 C++ 或 C 标准(最新草案免费)并检查。第 2 步:您决定永远无法记住规则并避免将来发生此类事情。
  • @gnasher729:+1,确实是个好建议。
  • @dandan78:C/C++ 用户的每 10 个误解中,大约有 9 个源于尝试、看到然后误解所看到的内容。

标签: c++ expression unsigned built-in-types


【解决方案1】:

简单来说,如果你混合相同等级的类型(按intlong intlong long int 的顺序),无符号类型“获胜”,并且在该无符号类型中执行计算。结果是相同的无符号类型。

如果您混合不同等级的类型,则较高等级的类型“获胜”,如果它可以代表较低等级类型的所有值。计算是在该类型中执行的。结果就是那种类型。

最后,如果高位类型不能代表低位类型的所有值,则使用高位类型的无符号版本。结果就是那种类型。

在您的情况下,您混合了相同等级的类型(intunsigned int),这意味着整个表达式在 unsigned int 类型内进行评估。正如您正确陈述的那样,表达式现在是 10 - 4294967254(对于 32 位 int)。无符号类型遵循以2^32 (4294967296) 作为模数的模运算规则。如果你仔细计算结果(可以算术表示为10 - 4294967254 + 4294967296),它会变成预期的52

【讨论】:

  • 对不起,我迷失了自己,当表达式变为:unsigned inttemporary = 10 - 4294967254(好的,我明白了)但我不明白为什么表达式变为 10 - 4294967254 + 4294967296(为什么您将模算术添加到表达式中?)。
  • @Piero Borrelli:计算modulo N 等效于负值V 的一种方法是根据需要多次添加NV + NV + 2NV + 3N 等等),直到你达到第一个非负值。在 C++ 加法运算的情况下,数学上的负数结果只需将模值相加一次即可得到正确的无符号结果。
  • @Piero Borrelli:当然,这是一个纯粹的算术规则。编译器不必做这样的事情。它根本不必担心。如果负值通过 2 的补码表示,则将该表示简单地重新解释为无符号表示立即提供正确的结果。
  • 你能定义你所说的“排名”吗? C++ 不以这种方式使用排名,这使得这个答案充其量是模棱两可的,最坏的情况是荒谬的。
  • @Adrian:实际上,确实如此。我指的是 整数转换等级 的概念,因为它用于描述通常的算术转换。我的回答中的描述不是标准的确切引用,因为它旨在针对原始问题中u - a 的特定情况进行定制。
【解决方案2】:

1) 由于标准提升规则,signed 类型 a 在减法之前被提升为 unsigned 类型。该提升是根据此规则(C++ 标准 4.7/2)进行的:

如果目标类型是无符号的,则结果值是最小的 与源整数一致的无符号整数(模 2n,其中 n 为 用于表示无符号类型的位数)。

代数上a 成为一个非常大的正数并且肯定大于u

2) u - a 是一个匿名临时变量,将是一个 unsigned 类型。 (您可以通过编写auto t = u - a 并在调试器中检查t 的类型来验证这一点。)从数学上讲,这将是一个负数,但在隐式转换为无符号类型时,会调用类似于上述的环绕规则。

简而言之,这两个转换操作的效果相等且相反,结果为 52。在实践中,编译器可能会优化所有这些转换。

【讨论】:

    【解决方案3】:

    下面是反汇编代码:

    它首先将-42 设置为其补码并执行子操作。所以结果是10 + 42 0x0000000000400835 <+8>: movl $0xa,-0xc(%rbp) 0x000000000040083c <+15>: movl $0xffffffd6,-0x8(%rbp) 0x0000000000400843 <+22>: mov -0x8(%rbp),%eax 0x0000000000400846 <+25>: mov -0xc(%rbp),%edx 0x0000000000400849 <+28>: sub %eax,%edx 0x000000000040084b <+30>: mov %edx,%eax

    【讨论】:

    • 在一般情况下,反汇编代码不能作为理解语言级语义的有意义的来源。代码生成是单向功能。不可能“追溯”。即通过查看生成的代码来弄清楚编译器实际上试图做什么。
    • 感谢您的评论。
    猜你喜欢
    • 2011-07-16
    • 1970-01-01
    • 2017-05-15
    • 2018-04-06
    • 2017-01-29
    • 2015-05-28
    • 2012-01-03
    • 2019-06-10
    相关资源
    最近更新 更多