无符号整数打印。

4
为什么会输出-1?
#include <stdio.h>

int main(){
    unsigned int i = -1;
    printf("%d", i);
    return 0;
}

当这个被打印出来时

#include <stdio.h>

int main(){
    unsigned int i = -1;
    printf("%u", i);
    return 0;
}

最大可能的整数值
此外,为什么会这样
#include <stdio.h>

int main(){
    int c = printf("Hello");
    printf("%d", c);
    return 0;
}

输出hello5而不是5。

反驳声明//给Eric

你提到了副作用,对吧?

#include <stdio.h>
int main(){
int i = 0;
for (; i++; printf("%d", i));
printf("%d", i);
return 0; 
}

为什么在这段代码中,for循环内部的 printf("%d",i) 没有产生副作用并打印出值?

2
C语言对初学者不太友好。你正在做一些通常不应该做的事情:将有符号值存储在无符号变量中。另外,printf函数默认写入到stdout,那么为什么你不期望打印出“Hello”部分呢?花点时间来尝试这些东西,直到你熟悉它们为止。你需要开始感觉到自己在做什么。例如,理解内存中的值和其他地方表示该值之间的区别。 - Cheatah
printf("Hello\n") 这个语句就可以实现这个功能。 - Cosinus
第三个问题与前两个问题无关。请不要在同一个问题中问无关的问题,最好将它们分成几个问题。 - Lundin
7个回答

7

问题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


请查看修改后的问题,我还有另一个反例需要澄清。 - programmer
请确认您是否看到了我的编辑后的问题。 - programmer
3
@程序员:那个循环体从未被执行,因为循环条件“i++”最初为false,因为“i”从零开始。请不要在初始问题回答后添加其他问题。 - Eric Postpischil

2
在一个32位整数系统中,无符号整数-1和有符号整数2147483647在内存中具有相同的二进制表示形式(参见二进制补码)。
11111111 11111111 11111111 11111111

当将值传递给printf时,数据类型信息丢失。因此,printf依靠格式说明符(%d%u)来确定数据是有符号整数还是无符号整数。


关于您的第二个问题:printf会打印输出,无论您是否忽略返回值。因此,您方法中的第一行打印了Hello,第二行打印了5


你如何编写无符号整数i=-1的二进制表示? - programmer
通常情况下,我们建议不要在Stack Overflow上进行后续问题的追问。这可以作为一个新问题来提出。正如@lundin所指出的那样,你原来的问题已经足够复杂了——因为它涉及到两个相关的问题。 - costaparas

2
第一个问题:在二进制中表示相同,但无符号值不会使用最后一位作为符号位。@Heinzi的答案更全面地涵盖了这个问题。
关于
int c=printf ("Hello");
printf ("%d",c);

它打印出Hello5,因为你只是简单地调用了printf两次:

  1. 打印hello并将打印的字符数(5)存储在c中。
  2. 打印c的值,即5。

2
当您将-1分配给无符号整数时,它会使用取模运算转换为可表示的无符号值。
这个...
unsigned int i = -1;

等同于

unsigned int i = UINT_MAX;

当你使用%d打印i时,由于格式不匹配,实际上会产生未定义行为
此段文字的意思是:“此外,为什么这个程序会输出 hello5 而不是 5 呢?第一个 printf 语句 int c=printf ("Hello"); 仍然输出 Hello,在第二个语句中你打印了前一个 printf 的返回值。因此输出结果为 Hello5。”

这不是未定义行为,因为二进制表示是明确定义的。 - Cosinus
2
@Cosinus 对于 %d,期望的参数是 int - 否则,它是未定义的。UINT_MAX 无法用 int 表示,因此肯定是 UB。 - P.P
1
printf不会因为标准第7章中的某行指出UB(未定义行为)而放弃,而是会尝试将传递的数据转换为指定类型。 intunsigned int是别名,使用一个来访问另一个不会引起任何问题,除非您正在使用高度奇特/虚构的系统,该系统没有2's补码但具有负零等的陷阱表示。 - Lundin
1
即使应用了多少个限定符,它仍然保持未定义状态。 未定义意味着未定义 - 它不意味着“一定会观察到一些糟糕的东西。如果没有,那就没事”。 - P.P

1
"printf不知道你定义的类型,它只能通过格式说明符来解释值。所以在第一个例子中,你使用了%d,这告诉printf将该值读取为有符号整数并相应地打印出来。在第二种情况下,你使用了%u,这告诉printf将其解释为无符号整数。"
"对于相同的数据类型,最大可能的无符号值具有与有符号值-1相同的二进制表示,因为最高位被解释为符号。"

1
您的第一个问题的答案很简单:printf 使用格式说明符来决定如何打印传递给它的参数,正如其他人所述。
对于无符号整数,格式为%u,而对于有符号整数,格式为%d
有关不同格式说明符的更多信息,请参见docs

d,i

将int参数转换为带符号的十进制表示。

o,u,x,X

将无符号整数参数转换为无符号八进制(o),无符号十进制(u)或无符号十六进制(x和X)表示。

对于您的第二个问题:

此外,为什么会打印出hello5而不是5?

因为你命令它这么做。代码里有两个 printf 语句。将printf的结果赋值并不会“丢弃”printf本身的输出——它仍然按照指示打印出来。这(即像printf这样的函数所产生的I/O)被称为副作用
你的第一个 printf 的确打印出了"Hello",而你的第二个则打印出了第一个 printf 的返回值(打印出的字符数):
引用块:

https://man7.org/linux/man-pages/man3/printf.3.html#RETURN_VALUE

成功返回后,这些函数返回打印出的字符数(不包括用于结束字符串输出的空字节)。


-1

首先,这就是整数的工作原理。

signed |             | unsigned
...    |  ...        | ...
 4     | 0000'0100b  | 4
 3     | 0000'0011b  | 3
 2     | 0000'0010b  | 2
 1     | 0000'0001b  | 1
 0     | 0000'0000b  | 0
-1     | 1111'1111b  | 255
-2     | 1111'1110b  | 254
-3     | 1111'1101b  | 253
-4     | 1111'1100b  | 252
...    | ...         | ...

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