问题1
为什么这个程序会输出-1…
unsigned int i=-1;
printf ("%d",i);
严格来说,这个行为未被C标准定义,因为i
是一个无符号整数,但%d
是为有符号整数设计的,而C 2018 7.21.6.1节第9款规定:“如果任何参数与相应的转换规范的正确类型不匹配,则其行为未定义。”
然而,C语言有一种将带符号和无符号类型混淆的历史记录1,因此许多编译器在这方面表现得很谨慎。2通常情况下,会出现以下情况:
- 函数调用通过将
i
的无符号整数值放入应传递无符号整数参数的位置来传递该值的位。
printf
从应传递整数参数的位置获取一个int
的位。
- 这些位置相同。
- 因此,
printf
将表示unsigned int
的位解释为int
。
在unsigned int i=-1
中,根据C标准(6.3.1.3)的规则,将-1转换为unsigned int
,结果是该类型的最大值,其表示形式中所有位都被设置为1:1111…11112。
在将这些位重新解释为int
时,大多数C实现使用二进制补码。在二进制补码中,位1111…11112表示-1。
因此,printf
将传递的位作为int值-1,并打印“-1”。
问题2
然而,这个程序会输出…
unsigned int i=-1;
printf ("%u",i);
… 最大可能的整数值
将-1转换为unsigned int
的规则在6.3.1.3第2款中说明:
…该值通过反复添加或减去可在新类型中表示的最大值加1,直到该值在新类型的范围内为止。
“新类型中可以表示的最大值加1”为 1 + UINT_MAX
。将-1添加到1 + UINT_MAX
会得到UINT_MAX
,它可以在unsigned int
中表示,因此这个值被存储在i
中。使用%u
打印它将正常输出其值。
问题3
还有,为什么这段代码…
int c=printf("Hello");
printf("%d",c);
…输出的是hello5而不是5。
printf
有两个作用:
- 它向标准输出写入字符。(C标准将此称为“副作用”,因为它是函数除返回一个值之外所做的事情。)
- 它返回写入的字符数(或者如果发生错误则返回负值)。
当你调用printf("Hello")
时,它向输出写入“Hello”并返回5。
然后printf("%d", c)
向输出写入“5”。
这个问题表明您认为在赋值或初始化中使用printf
只导致其返回值被使用,从而抑制了副作用。但事实并非如此。在C中,每当评估一个表达式时,它的副作用和主要效果都会被评估。
(实际上,赋值本身就是表达式;c = printf("Hello")
是一个表达式,您可以在进一步操作中使用它,比如x = 3 * (c = 5)
,这将把15分配给x
。我们可以在任何表达式后加上分号来制造一个表达式语句,它评估该表达式并丢弃主要结果。 printf("%d", c);
评估printf
的副作用并丢弃返回值。)
脚注
1例如,6.5.2.2 6允许在没有原型的函数调用中将有符号整数类型替换为相应的无符号整数类型,反之亦然,尽管限于可表示两者的值。对于使用...
的原型,7.16.1.1允许在使用va_arg
时进行相同的替换。在6.2.6.2中的规则要求有符号和无符号类型的通用值位具有相同的值。
2 我不记得有任何正式声明将有符号类型和无符号类型混淆,但我知道编译器设计者会尽一些努力去避免破坏某些遗留用法或某些常见用例的代码,即使C标准不要求支持它们。在C语言中,很容易出现失误,将unsigned char
传递给%u
进行打印,这在技术上是错误的,因为unsigned char
会被提升为int
而不是unsigned int
。
printf("Hello\n")
这个语句就可以实现这个功能。 - Cosinus