【问题标题】:Unreasonable behavior on mixing signed and unsigned integers混合有符号和无符号整数的不合理行为
【发布时间】:2017-12-02 21:15:29
【问题描述】:

灵感来自 “当我混合有符号和无符号整数时会发生什么?”下的 blog 上的代码 sn-p,我决定使用几个不同的有符号和无符号整数值来运行它观察行为。

这里是原始的 sn-p(略有修改,但意图仍然相同)

#include <stdio.h>

int main(void)
{
    unsigned int a = 6;
    int b = -20;

    int c = (a+b > 6);
    unsigned int d = a+b;

    printf("<%d,%u>", c, d);
}

输出:

现在,当我为 a = 6b = -1 运行相同的程序时

输出:

如果我正确理解了 C 语言的 Integral Promotion 规则,b=-1 应该被提升为无符号整数,那将是 4294967295。就像原始示例一样,我们应该得到&lt;1,4294967301&gt;。但这不是我们得到的。

我能想到这种行为的唯一原因是这些隐式类型转换发生在算术运算之后。但是,同一篇博文还说,首先提升有符号整数,然后进行评估,这使我的推理无效。

这种行为的真正原因是什么?以及这两个示例有何不同。


P.S 我知道有很多关于 SO 的问题与此问题相似/相关。但是我没有遇到任何特别解决这个问题或有助于理解此类代码 sn-p 的问题。如果发现是重复的,我很乐意接受这个问题被关闭。

【问题讨论】:

  • 我不明白问题出在哪里。这一切对我来说似乎都是合理的行为。是不是c在6、-20的情况下应该是0
  • 第一个输出基本是“是4294.....在6之上,然后是1,然后是数字4294....”。第二个输出是“是 5 高于 6,然后是 1,否则为 0,然后是数字 5”。 您的问题是什么?
  • 让我总结一下。是的,4294967282 > 6,所以是 1。不,5 不是 > 6,所以是 0。你的问题是什么?
  • 请记住,如果您将两个数字 a+b 相加,其中 a 是无符号的,b 是有符号的,那么如果您简单地将带符号的值重新解释为就位的含义而言,是一个无符号值。
  • ? 1 : 0 是不必要的。 &gt; 运算符产生类型为int 的结果,如果条件为真,则为1,如果为假,则为0

标签: c unsigned-integer integer-promotion signed-integer


【解决方案1】:

 /*unsigned*/a+/*signed*/b

b 将转换为 unsigned,因为 usual arithmetic conversions (6.3.1.8)

转换将由repeatedly adding or subtracting one more than UINT_MAX (6.3.1.3p2) 完成,对于unsigned == uint32_t 意味着添加2^32(一次)

供参考unsigned == uint32_t:

UINT_MAX+1 == 2^32 == 4294967296

对于(unsiged)a==6(signed)b==-20,你会得到:

2^32-20 + 6 == 4294967282  (<= UINT_MAX)

对于(unsiged)a==6 (signed)b==-1,你得到:

2^32-1 + 6 == 4294967301 (>UINT_MAX)

现在因为这个结果大于UINT_MAX,它将环绕到5(你可以通过减去UINT_MAX+1得到,它是如何定义环绕发生的(6.3.1.3p2))。

在二进制补码体系结构中,这些规则基本上转换为一个简单的add,这意味着您也可以使用签名表示 (6-20==-14; 6-1==5) 进行操作,然后将结果重新解释为 unsigned(看起来像在第二种情况下获得 5 的更直接的方法,不是吗),但了解规则仍然很好,因为在 C 中未定义有符号溢出(C!=汇编),这为您的编译器提供了很大的转换余地如果您假设直接的 C 到程序集映射,则将代码转换成您意想不到的内容。

【讨论】:

  • 'Wrap-around' 是解决这个问题的关键。我猜这个可怕的数字 4294967301 让我很难看到它超出了范围。谢谢你的最后一段,这对我提出的怀疑推理是有意义的。
  • 顺便说一句,您是否可以分享一个简单的示例,在该示例中我们“减去”比 UINT_MAX 多一个来进行类型转换?我的意思是我们什么时候真正减去类型转换?
  • @Prateek 例如,最后一个 sn-p,其中 4294967301 被转换为 5。
  • 但这只是一个环绕对吧?超出范围并被包装的未签名添加。我认为那里没有发生任何数据类型转换。
  • @Prateek 它的工作原理相同。 uint64_t a = 4294967301UL; uint32_t b = a; 在这里你得到了一个真正的 64 位 4294967301 通过减去 UINT32_MAX + 1 转换为 (uint32_t)5
【解决方案2】:

所以你应该是4294967301,但这不适合32位无符号整数,因为最大值是2^32-14294967295。所以4294967295 等于0xFFFFFFFF0xFFFFFFFF + 60x00000005。当有符号整数从2147483647 (0x7FFFFFFF) 翻转到-2147483648 (0x80000000) 时,称为溢出。当未签名从 4294967295 (0xFFFFFFFF) 滚动到 0 (0x00000000) 时,它是一个环绕。

【讨论】:

  • 技术上(如根据 C 标准),这不称为溢出。溢出只会发生在有符号类型上。
  • 谢谢!我正在疯狂地谷歌搜索以确认它是否确实被称为溢出。
  • 对。当无符号整数溢出时,它只是称为进位。虽然我不知道进位位是否在 C 中暴露。
  • 修正了评论以解释差异!
  • @TinyTheBrontosaurus:它不叫“carry”,至少在 C 标准中不叫。常用术语是“环绕”。
猜你喜欢
  • 2013-10-02
  • 2015-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-02
相关资源
最近更新 更多