混合有符号和无符号整数的不合理行为
Unreasonable behavior on mixing signed and unsigned integers
受到 "What happens when I mix signed and unsigned integers?" 下此 blog 的代码片段的启发,我决定 运行 它具有几个不同的有符号和无符号值整数并观察行为。
这是原始片段(略有修改,但意图仍然相同)
#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);
}
OUTPUT: <1,4294967282>
现在,当我 运行 为 a = 6
和 b = -1
使用相同的程序时
OUTPUT: <0,5>
如果我正确理解 C 语言的 Integral Promotion 规则,b=-1
应该被提升为一个无符号整数,那就是 4294967295
。就像原始示例的情况一样,我们应该得到 <1,4294967301>
。但这不是我们得到的。
我能想到的这种行为的唯一原因是这些隐式类型转换发生在算术运算之后。但是,同一博客 post 还说首先提升有符号整数,然后进行评估,这使我的推理无效。
这种行为的真正原因是什么?以及这两个例子有何不同。
P.S 我知道有很多关于 SO 的问题 similar/related 这个问题。但是我没有遇到任何特别解决这个问题或有助于理解这样的代码片段的问题。如果发现重复,我很乐意接受这个问题。
所以你应该是 4294967301
,但这不适合 32 位无符号整数,因为最大值是 2^32-1
或 4294967295
。所以 4294967295
等于 0xFFFFFFFF
。与 0xFFFFFFFF + 6
是 0x00000005
。当有符号整数从 2147483647
(0x7FFFFFFF
) 翻转到 -2147483648
(0x80000000
) 时,称为溢出。当无符号从 4294967295
(0xFFFFFFFF
) 翻转到 0
(0x00000000
) 时,它是一个环绕。
在
/*unsigned*/a+/*signed*/b
b
将转换为 unsigned
因为 usual arithmetic conversions (6.3.1.8).
转换将通过重复加或减1来完成
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!=assembly) 中未定义有符号溢出,这给出了你的编译器有很大的余地来将代码转换成你不希望的东西,如果你假设直接 C 到程序集映射的话。
受到 "What happens when I mix signed and unsigned integers?" 下此 blog 的代码片段的启发,我决定 运行 它具有几个不同的有符号和无符号值整数并观察行为。
这是原始片段(略有修改,但意图仍然相同)
#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);
}
OUTPUT: <1,4294967282>
现在,当我 运行 为 a = 6
和 b = -1
OUTPUT: <0,5>
如果我正确理解 C 语言的 Integral Promotion 规则,b=-1
应该被提升为一个无符号整数,那就是 4294967295
。就像原始示例的情况一样,我们应该得到 <1,4294967301>
。但这不是我们得到的。
我能想到的这种行为的唯一原因是这些隐式类型转换发生在算术运算之后。但是,同一博客 post 还说首先提升有符号整数,然后进行评估,这使我的推理无效。
这种行为的真正原因是什么?以及这两个例子有何不同。
P.S 我知道有很多关于 SO 的问题 similar/related 这个问题。但是我没有遇到任何特别解决这个问题或有助于理解这样的代码片段的问题。如果发现重复,我很乐意接受这个问题。
所以你应该是 4294967301
,但这不适合 32 位无符号整数,因为最大值是 2^32-1
或 4294967295
。所以 4294967295
等于 0xFFFFFFFF
。与 0xFFFFFFFF + 6
是 0x00000005
。当有符号整数从 2147483647
(0x7FFFFFFF
) 翻转到 -2147483648
(0x80000000
) 时,称为溢出。当无符号从 4294967295
(0xFFFFFFFF
) 翻转到 0
(0x00000000
) 时,它是一个环绕。
在
/*unsigned*/a+/*signed*/b
b
将转换为 unsigned
因为 usual arithmetic conversions (6.3.1.8).
转换将通过重复加或减1来完成
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!=assembly) 中未定义有符号溢出,这给出了你的编译器有很大的余地来将代码转换成你不希望的东西,如果你假设直接 C 到程序集映射的话。