【问题标题】:Unsigned integer print无符号整数打印
【发布时间】:2021-05-12 23:32:37
【问题描述】:

为什么会打印 -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。

反陈述//为埃里克

你提到了副作用,对吧?

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

为什么 for 循环中的 printf ("%d",i) 不打印此代码中的值作为副作用?

【问题讨论】:

  • C 对初学者不友好。您正在做通常不应该做的事情:将有符号值存储在无符号变量中。另外:printf 函数默认写入stdout,那么为什么不会您希望打印“Hello”部分?花一些时间来玩弄这些东西,直到你熟悉它们。你需要开始了解你在做什么。例如,要了解内存中的值与该值在其他地方的表示之间的区别。
  • printf("Hello\n") 成功了
  • 第3题与其他2题无关。请不要在同一个问题中问无关的问题,最好将它们分成几个。

标签: c printf


【解决方案1】:

问题 1

为什么会打印 -1…

unsigned int i=-1;
printf ("%d",i);

正式地,行为不是由 C 标准定义的,因为 iunsigned int,但 %dint,并且 C 2018 7.21.6.1 9 表示“如果任何参数不是对应转换规范的正确类型,行为未定义。”

但是,C 有将有符号和无符号类型混为一谈的历史1,许多编译器在这方面做得很轻。2 通常,会发生以下情况:

  • 函数调用通过将i 的位放在应该传递unsigned int 参数的位置来传递unsigned int 的值。
  • printf 从应该传递 int 参数的位置获取 int 的位。
  • 这些是同一个地方。
  • 因此,printf 重新解释表示 unsigned int 的位,就好像它们是 int

unsigned int i = -1 中,根据C 标准(6.3.1.3,见下文)中的规则将-1 转换为unsigned int,从而产生最大的unsigned int 值,在其表示中设置了所有位: 1111…11112.

在将这些位重新解释为 int 时,大多数 C 实现使用二进制补码。在two’s complement 中,位 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+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:该循环的主体永远不会执行,因为循环条件 i++ 最初为 false,因为 i 从零开始)。请不要在回答最初的问题后添加其他问题。
【解决方案2】:

您的第一个难题,答案很简单:printf 使用格式说明符来决定如何打印传递给它的参数,正如其他人所说的那样。

对于unsigned int,格式为%u,而对于signed int,格式为%d

docs中查看有关不同格式说明符的更多信息:

d, 我

int 参数被转换为有符号十进制表示法。

o, u, x, X

无符号整数参数被转换为无符号八进制 (o)、无符号十进制 (u) 或无符号十六进制 (x 和 X) 符号。

第二个困境:

还有,为什么会这样

打印 hello5 而不是 5。

因为你告诉它。那里有两个printf 语句。分配printf 的结果不会“丢弃”printf 本身的输出——它仍然按照指示打印出来。这(即像 printf 一样工作的 I/O)称为 side effect

您的第一个printf 确实打印"Hello",而您的第二个打印出您的第一个printf 的返回值(打印的字符数):

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

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

【讨论】:

    【解决方案3】:

    当您将 -1 分配给无符号整数时,它会转换为使用 reduction modulo 以无符号值表示的值。

    这个

    unsigned int i = -1;
    

    等价于

    unsigned int i = UINT_MAX;
    

    当你用%d打印i时,由于格式不匹配,它实际上是undefined behaviour


    另外,为什么这个 [..] 打印的是 hello5 而不是 5。

    第一个 printf 调用,int c=printf ("Hello");,仍然打印 Hello,而在第二个中,您打印了前一个 printf 返回值的返回值。 因此Hello5 输出。

    【讨论】:

    • 这不是未定义的行为,因为二进制表示已明确定义。
    • @Cosinus 对于%d,预期的参数是int - 否则,它是未定义的。 UINT_MAX 不能用 int 表示,所以肯定是 UB。
    • printf 不会因为标准第 7 章中的某行说 UB 而躺下死去。而是尝试将传递的数据转换为指定的类型。 intunsigned int 别名并与另一个一起访问其中一个不会导致任何问题,除非您使用的是没有 2 的补码但带有负零等陷阱表示的高度奇特/虚构系统。
    • 不管应用了多少个限定符,它仍然是未定义的。未定义的意思是未定义——这并不意味着“你肯定会观察到不好的事情。如果没有,没关系”。
    【解决方案4】:

    printf 不知道您定义的类型。它只知道如何通过格式说明符来解释值。因此,在您的第一个示例中,您使用 %d 告诉 printf 将此值读取为 signed int 并相应地打印它。在第二种情况下,您使用%u sou 它告诉printf 将其解释为unsigned int

    对于与signed 相同的数据类型,最大可能的无符号值具有与-1 相同的二进制表示,因为最高位被解释为符号。

    【讨论】:

      【解决方案5】:

      对于初学者来说,这就是整数的工作原理。

      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
      ...    | ...         | ...
      

      【讨论】:

        【解决方案6】:

        关于第一个问题:二进制表示相同,但​​无符号值不会使用最后一位作为符号位。 @Heinzi 的回答更全面地涵盖了这一点。

        关于

        int c=printf ("Hello");
        printf ("%d",c);
        

        它打印 Hello5 因为你只是调用了 printf 两次:

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

        【讨论】:

          【解决方案7】:

          在具有 32 位整数的系统上,无符号整数 -1 和有符号整数 2147483647 在内存中恰好具有相同的二进制表示(请参阅Two's complement):

          11111111 11111111 11111111 11111111
          

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


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

          【讨论】:

          • 如何写出 unsigned int i=-1 的二进制表示?
          • 一般来说,我们在 Stack Overflow 上advise against follow up questions。这可以改为new question。正如上面@lundin 所指出的,您最初的问题已经足够复杂了——因为它问了两件事(尽管相关)。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-04-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-13
          相关资源
          最近更新 更多