如果我给一个无符号变量赋一个负值会发生什么?

93

我很好奇如果将负值赋给无符号变量会发生什么。

代码大致如下。

unsigned int nVal = 0;
nVal = -5;

编译没有出错。但是当我运行程序时,nVal被赋予了一个奇怪的值!是否可能将一些二进制补码值分配给nVal


1
我的直觉(尚未在标准中找到)是该行为在技术上是未定义的。此外,我怀疑你会在任何你能找到的编译器上看到你期望的结果。因此,虽然你通常会看到这种行为,但依赖它可能不是一个好主意。 - sblom
6
它不是未定义的(见§4.7/2),但标准没有强制规定其表示方式(例如2s补码)。 - Georg Fritzsche
3
第二行代码等同于 nVal = (unsigned int) -5;。将 -5 强制转换为 unsigned int 在 6.3.1.3 中有定义。2的补码表示在标准中没有强制规定,但是将其转换为无符号数的算法是:“通过反复加上或减去新类型中可以表示的最大值再加一来将该值转换为新类型范围内的值。” - Pascal Cuoq
1
@Pascal:您似乎在提到C99,但问题标记为C++。 - Georg Fritzsche
二进制补码是C++20C23允许的唯一有符号整数表示。 - Steve Ward
显示剩余3条评论
7个回答

78

对于官方答案,请参考第4.7节 conv.integral

如果目标类型是无符号的,那么结果值就是源整数模2n的最小无符号整数余数(其中 n 是用于表示无符号类型的位数)。[注:在二进制补码表示中,这种转换是概念性的,并且如果没有截断,则位模式不会改变。——末尾注释]

这意味着,如果底层架构使用的方法不是二进制补码(例如有符号幅度或反码),则转换为无符号类型时必须表现得好像它是二进制补码。


42
“the least unsigned integer congruent to the source integer” 的意思是“与原整数同余且最小的非负整数”。 - David Rodríguez - dribeas
12
例如,数字5和3在模2下“同余”,因为它们除以2的余数都是1。 - JoeQuery
它涉及哪些版本的C++标准?全部吗? - Alexey Kruglov
那么(uint)((int)a + (int)b)可能是未定义的,因为a和b可能会溢出,但(uint)a + (uint)b是被定义良好的,因为对于uint允许溢出。对于所有定义良好的总和,两者都将给出相同的结果,这是真的吗? - Eyal

46

这将把表示-5的二进制补码赋给无符号整数(unsigned int),它将成为一个较大的无符号值。对于32位整数而言,这将是2的32次方减去5,即4294967291。


2
位与此无关。 - GManNickG
1
@BenVoigt:好的,我是指它与位的解释方式无关。(也就是说,引用部分中的“位”只是ceil(log_2(x))的简写。) - GManNickG
1
@GManNickG Bit's(即属于位)?2的补码?天啊! - NullUserException
1
@NullUserException:哈哈,我知道。在“s”之前写“的”是我长期以来养成的一个可怕习惯。至于用“compliment”代替“complement”,那只是纯粹的愚弄。 :) - GManNickG
3
简洁明了是关键。这个答案做到了。比起引用文档,使用(2^32 - 5)更好地解释了这种行为。 - dystopiandev

5

你说得对,有符号整数以二进制补码的形式存储,而无符号整数则以无符号二进制表示的形式存储。C(和C++)不区分这两者,因此最终得到的值就是二进制补码表示的无符号二进制值。


16
可能不是以2的补码形式存储。 - GManNickG
如果某物被“存储在2的位置”,这意味着什么? - JeremyF
5
@JeremyF:不是“2的”,而是“二进制补码”。这是一个可以在谷歌上查到的术语,并且是表示有符号整数的一种方式。 - GManNickG
二进制补码是C++20C23允许的唯一有符号整数表示。 - Steve Ward

4

它会显示为一个正整数,其值为最大无符号整数 - 4(值取决于计算机架构和编译器)。

顺便说一下, 您可以通过编写一个简单的C++“hello world”类型程序来检查并亲自验证。


我写了代码并进行了检查,所以我才问这个问题,但我不知道编译器是如何得出那个正值的。谢谢。 - ckv
7
不幸的是,在C++中编写测试行为的程序并不总是一个好主意。例如,如果尝试测试_signed_溢出的情况,这将导致未定义的行为,而这在每台机器/编译器上都不能保证相同。 - Ben Jones

2

是的,你说得对。实际分配的值类似于除了第三位之外所有位都设置为1。-1表示所有位都设置为1(十六进制:0xFFFFFFFF),-2表示除了第一位之外的所有位等等。你可能会看到的是十六进制值0xFFFFFFFB,它在十进制中对应4294967291。


3
位元与此无关,整数表示并没有被指定。 - GManNickG
2
你的答案是正确、严谨、简明扼要的,但这不是我在课堂上会使用的。 - Martin
看一下我对-5的二进制补码。我认为你在这里的二进制值上算错了。 - cynistersix

2
当您将负值分配给无符号变量时,它使用二进制补码方法进行处理,在此方法中,它会将所有0翻转为1,并将所有1翻转为0,然后加上1。在您的情况下,您正在处理32位(4字节)的int类型,因此它尝试在32位数字上使用二进制补码方法,导致更高位翻转。例如:
┌─[student@pc]─[~]
└──╼ $pcalc 0y00000000000000000000000000000101      # 5 in binary
        5                       0x5                     0y101
┌─[student@pc]─[~]
└──╼ $pcalc 0y11111111111111111111111111111010      # flip all bits  
      4294967290      0xfffffffa      0y11111111111111111111111111111010
┌─[student@pc]─[~]
└──╼ $pcalc 0y11111111111111111111111111111010 + 1  # add 1 to that flipped binarry
      4294967291      0xfffffffb      0y11111111111111111111111111111011

0

在我检查过的Windows和Ubuntu Linux中,将任何负数(不仅仅是-1)分配给C和C++中的无符号整数会导致将值UINT_MAX分配给该无符号整数。

编译示例link


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接