#include <stdio.h>
#include <stdlib.h>
int main(void)
{
unsigned int i;
i = -12;
printf("%d\n" , i);
system("pause");
return 0;
}
我在Visual Studio 2012中运行上面的代码。因为我知道unsigned
指的是非负数,我希望程序报告错误。为什么它仍能顺利运行并打印输出?
答案 0 :(得分:3)
正如200_success所提到的,尽管存在混合无符号和有符号整数值的明显问题,但这里有两件事正在组合以产生正确的输出。
首先,行i = -12
隐式将(已签名)int
字面值-12
转换为unsigned int
。存储在存储器中的位不会改变。它仍然是0xfffffff4
,这是-12
的二进制补码表示。但是,unsigned int
忽略符号位(最高位),而是将其视为值的一部分,因此unsigned int
将此值(0xfffffff4
)解释为1号}}。这里的底线是C对有符号值和无符号值之间的隐式转换有非常松散的规则,特别是在相同大小的整数之间。您可以通过执行以下操作来验证这一点:
4294967284
这将打印printf("%u\n", i);
。
这里发生的第二件事是4294967284
除了通过格式字符串告诉之外,对你传递的参数一无所知。对于C中用变量参数列表定义的所有函数(例如printf
),这基本上都是正确的。这是因为编译器不可能确切地知道可以将哪些类型的参数传递给此函数,所以当编译器为调用这样的函数生成汇编代码,它不能进行类型检查。它所能做的就是确定每个参数的 size ,并将适当的字节数推入堆栈。因此,当您执行int printf(const char *fmt, ...);
时,编译器只是将printf("%d\n", i);
个字节推送到堆栈上。它不能进行类型检查,因为sizeof(unsigned int)
的函数原型没有任何关于任何参数类型的信息,除了第一个参数(printf
),它知道它是一个fmt
。任何后续参数都只是复制为一定数量字节的通用blob。
然后,当调用const char *
时,它只查看堆栈中的第一个printf
字节,并将它们解释为你告诉它的方式。即,作为签名的sizeof(unsigned int)
值。由于存储在这些字节中的值仍然只是int
,因此它会打印0xfffffff4
。
编辑请注意,通过声明内存中的值为-12
,我假设您机器上的0xfffffff4
为4字节。 sizeof(unsigned int)
可能被定义为您计算机上的其他大小。但是,相同的原则仍然适用,无论值是unsigned int
还是0xfff4
,还是它可能是什么。
答案 1 :(得分:1)
此问题与此Objective C question类似。简短的回答是,两个错误是正确的。
i = -12
错误,因为您试图在unsigned int
中存储一个负数。
printf("%d\n", i)
错误,因为您要求printf
将unsigned int
解释为签名int
。
这两个语句都应该导致编译器警告。但是,C很乐意让你滥用unsigned int
作为存储一些内容的地方,这就是你所做的。
答案 2 :(得分:1)
i = -12;
定义明确。当您为unsigned int
分配超出范围的值时,该值会以UINT_MAX + 1
模式调整,直到它位于unsigned int
的范围内。
例如,如果UINT_MAX
为65535
,则i = -12
会导致i
的{{1}}值为65536 - 12
。
将参数类型与65524
不匹配是未定义的行为。当你说printf
时,你必须提供%d
(或者在默认参数促销下提升为int的较小类型)。
实际上通常发生的是系统解释用于表示unsigned int的位,就像它们是用于表示signed int的位一样;当然,因为它是UB,所以不保证可以工作甚至尝试。
答案 3 :(得分:0)
您确实将-12保存为整数并告诉printf(通过使用%d)它是一个普通的int,因此它将所述变量的内容解释为int并打印-12。
如果你在printf中使用了%u,你会在将内容解释为unsigned int时看到你真正存储的是什么。
答案 4 :(得分:0)
打印可能有效(如上面许多解释的那样),但避免在代码中使用i进行进一步计算。它不会保留标志。