给无符号整数赋负数?

56

在 C 语言中,unsigned int 用于存储正值。然而,当我运行以下代码时:

unsigned int x = -12;
printf("%d", x);
输出仍为-12。我以为它应该输出:12,或者我是误解了什么吗?

44
你欺骗了编译器:你告诉编译器你会提供一个“int”类型的参数给“printf”函数(即“%d”),但实际上你给的是一个“unsigned”类型的参数。不要欺骗编译器 - pmg
1
@Joachim:编译器正在执行“通常的算术转换”(请参见3.2.1.5)。“……否则,如果任一操作数具有无符号整数类型,则将另一个操作数转换为无符号整数……”,而-12转换为无符号数的结果为(UINT_MAX + 1) - 12 - pmg
2
@Joachim:gcc 标识-Wsign-conversion(包含在-Wconversion中)可用于警告此问题。正如您现在所知,它不是 -Wall-Wextra 的一部分。 - pmg
3
@Joachim: 我不认为编译器应该对语言的完全合理使用发出警告。很遗憾,C语言整数语义的具体细节通常不会在书籍和课程中早期介绍,但这并不是将编译器的首选警告级别调整到不了解所编程语言的人的借口... - R.. GitHub STOP HELPING ICE
4
实际上,GCC编译器对用户使用-Wall时会欺骗他们,因为它并没有打开所有的警告。 - user541686
显示剩余7条评论
14个回答

71
你的等号右侧的-12是一个有符号整数(可能为32位),将具有十六进制值0xFFFFFFF4。编译器会生成代码,将这个有符号整数移动到你的无符号整数x中,它也是一个32位实体。编译器假设你等号右侧只有正值,因此它只是将所有32位移动到x中。现在,x的值为0xFFFFFFF4,如果作为正数解释,则为4294967284。但是printf格式化字符串中的%d表示要将32位视为有符号整数,所以结果是-12。如果使用了%u,则打印结果将为4294967284
无论哪种情况,你得不到你期望的结果,因为C语言“信任”代码编写者只会请求“合理”的事物。这在C语言中很常见。如果你想给x赋一个值,但不确定等号右侧的值是否为正数,你可以写unsigned int x = abs(-12);,强制编译器在将有符号整数移动到无符号整数之前生成代码来取有符号整数的绝对值。

16
其中很多技术上是错误的。在unsigned int x = -12;中,-12是类型为int的整数常量表达式。将其通过重复加(或减)UINT_MAX + 1的值转换为unsigned int,直到得到介于0和UINT_MAX之间(包括两端)的值,该值是转换的结果。在反码或符号-数值机器上,这会改变值的位模式;而在更常见的二进制补码机器上,它只是相同位模式的不同解释。 - Daniel Fischer
8
然而,该部分完全由标准定义,在 unsigned int x = -12; 之后,x 的值必须始终为 UINT_MAX - 11。下一行的 printf("%d", x); 调用未定义的行为,因为 %d 转换说明符需要一个有符号的 int,而 x 的值在 unsigned intint 中都 可表示。通常的行为当然是重新解释位模式,在补码机器上,在这种情况下会产生 -11,在带有32位 int 的原码和反码机器上,则为 -2147483636。在二进制补码机器上则为观察到的行为。 - Daniel Fischer
@DanielFischer 为什么在unsigned int中该值小于UINT_MAXx值无法表示? - ajay
1
@ajay 变量 x 的值可以表示为 unsigned int,但不能表示为 int(除非实现方式很奇怪,其 unsigned int 具有填充位,因此范围是 int 范围的子范围)。使用 %d 转换说明符打印 unsigned int 是未定义行为,除非该值在两种类型中都可以表示。 - Daniel Fischer
我理解。个人趣闻:我曾试图通过将整数强制转换为无符号整数并让其存储新值来去除符号。后来发现,当然,这不是C++的工作方式。 - user749127

34

这个整数是无符号的,但你告诉printf把它当作有符号整数来查看。

尝试使用

unsigned int x = -12; printf("%u", x);
它不会打印出"12",但会打印出无符号整数的最大值减去11。
读者需要自行找出原因 :)

4
“11或12”是错误的。结果已经明确定义,并且为UINT_MAX-11,而不是UINT_MAX-12 - R.. GitHub STOP HELPING ICE
4
我记不清是max-11还是max-12,所以我说“减去11或12”(从技术上讲这是正确的,因为它确实是其中之一)。感谢您指出是-11,我会更改答案以反映这一点,谢谢。 - Binary Worrier
1
最终,它是数字的二进制补码。 - niyasc

21

将%d传递给printf告诉printf将参数视为有符号整数,而不管您实际传递什么。使用%u以打印无符号整数。


1
简单易懂,最佳答案! - EugenSunic
我更欣赏这个答案,它直截了当地表达了观点。谢谢。 - rayryeng

7
这一切都与值的解释有关。
如果假设16位有符号和无符号整数,则以下是一些示例,它们不完全正确,但演示了概念。
0000 0000 0000 1100 为无符号整数,有符号整数值为12
1000 0000 0000 1100 为有符号整数值-12,以及一个大的无符号整数。
对于有符号整数,左边的位是符号位。 0 = 正数 1 = 负数
对于无符号整数,没有符号位。 左边的位可以让您存储更大的数字。
所以你没有看到你期望的结果是因为。 unsigned int x = -12,将-12作为整数存储在x中。 x是无符号的,因此 曾经的符号位现在是值的一部分。 printf允许您告诉编译器您想要如何显示值。
%d表示按照有符号int的方式显示它。 %u表示按照无符号int的方式显示它。
c让您做这种事情。 程序员掌握控制权。
就像枪支一样。 这是一个工具。 您可以正确使用它来处理某些情况, 或者错误地削减您的一个脚趾。
一种可能有用的情况是 unsigned int allBitsOn = -1;
该特定值将所有位设置为1
1111 1111 1111 1111
有时这可能很有用。

4
printf('%d', x); 

意思是打印有符号整数。你需要写成这样:
printf('%u', x);

此外,它仍然不会打印出“12”,而是会显示“4294967284”。

3

它们确实存储正值。但是,由于您正在将(非常高的)正值输出为有符号整数,因此它会再次被重新解释(我可以补充说明,这是以一种实现定义的方式进行的)。

请改用格式标志"%u"


3

你的程序存在未定义行为,因为你向 printf 函数传递了错误的类型(你告诉它你要传递一个 int 类型,但实际上你传递了一个 unsigned int 类型)。幸运的是,实现中“最简单”的事情就是悄悄地打印错误的值而不跳转到某些有害的代码...


2
如果传入一个无符号整数,而期望相应有符号类型的可变参数是很好定义的,只要该值在两种类型中都可以表示(参见C99 7.15.1.1 §2);然而,在这种情况下并非如此,但是任何合理的C语言实现都将通过“类型转换”来转换该值(请参见第73页的82脚注)。 - Christoph

2
你所忽略的是printf("%d",x)期望x为有符号数,因此尽管你将-12赋值给x,但它被解释为2的补码,这将是一个非常大的数字。 然而,当你将这个非常大的数字传递给printf时,它会将其解释为有符号数,因此正确地将其翻译回-12。
在print f中打印无符号数的正确语法是"%u" - 尝试一下并看看它做了什么!

1
“-12”以16位二进制补码格式表示。因此,可以这样做: 如果 (x & 0x8000) { x = ~x+1; } 这将把二进制补码负数转换为相应的正数。祝好运。

1

int和unsigned int用于分配一定数量的字节以存储一个值,仅此而已。

编译器应该会给出有关有符号不匹配的警告,但实际上并不影响表示值-12的内存中的位。

%x,%d,%u等告诉编译器在打印它们时如何解释一些位。


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