无符号整数打印

pro*_*mer 4 c printf

为什么会打印-1

#include <stdio.h>

int main(){
    unsigned int i = -1;
    printf("%d", i);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

虽然这打印

#include <stdio.h>

int main(){
    unsigned int i = -1;
    printf("%u", i);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

最大可能的整数值

另外,为什么会这样

#include <stdio.h>

int main(){
    int c = printf("Hello");
    printf("%d", c);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

打印 hello5 而不是 5。

埃里克的反驳//

你提到了副作用,对吧?

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

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

Eri*_*hil 7

问题1

\n
\n

为什么打印 -1\xe2\x80\xa6

\n

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

\n
\n

从形式上来说,该行为不是由 C 标准定义的,因为iis an unsigned intbut%d用于 an int,并且 C 2018 7.21.6.1 9 表示 \xe2\x80\x9c 如果任何参数不是相应转换规范的正确类型,则行为为未定义。\xe2\x80\x9d

\n

然而,C 有着混淆有符号和无符号类型1的历史,并且许多编译器在这方面表现得很谨慎。2通常情况下,会发生以下情况:

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

在 中,\xe2\x88\x921根据 C 标准(6.3.1.3,见下文)中的规则unsigned int i = -1转换为最大值,其表示形式中设置了所有位: 1111\xe2\x80\xa61111 2unsigned intunsigned int

\n

在将这些位重新解释为 时int,大多数 C 实现使用两个\xe2\x80\x99s 补码。在两个\xe2\x80\x99s补码中,位1111\xe2\x80\xa61111 2代表\xe2\x88\x921。

\n

因此,printf将传递的位作为int值 \xe2\x88\x921 并打印 \xe2\x80\x9c-1\xe2\x80\x9d。

\n

问题2

\n
\n

虽然这会打印\xe2\x80\xa6

\n

unsigned int i=-1;
\nprintf ("%u",i);

\n

\xe2\x80\xa6 最大可能的整数值

\n
\n

\xe2\x88\x921 转换为的规则unsigned int在 6.3.1.3 2 中:

\n
\n

\xe2\x80\xa6 通过在新类型可以表示的最大值上反复加或减 1 来转换该值,直到该值在新类型的范围内。

\n
\n

\xe2\x80\x9c 比新类型\xe2\x80\x9d 中可以表示的最大值多了 1+ UINT_MAX。将 \xe2\x88\x921 添加到 1 +UINT_MAX会产生UINT_MAX,它可以在 中表示unsigned int,因此这就是存储在 中的值i。用 打印它会%u正常打印它的值。

\n

问题3

\n
\n

另外,为什么这个\xe2\x80\xa6

\n

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

\n

\xe2\x80\xa6 打印 hello5 而不是 5。

\n
\n

printf做了两件事:

\n
    \n
  • 它将字符写入标准输出。(C 标准将此称为副作用,因为这是函数除了返回值之外还执行的操作。)
  • \n
  • 它返回写入的字符数(如果发生错误,则返回负值)。
  • \n
\n

当您调用 时printf("Hello"),它将 \xe2\x80\x9cHello\xe2\x80\x9d 写入输出并返回 5。

\n

然后printf("%d", c)将 \xe2\x80\x9c5\xe2\x80\x9d 写入输出。

\n

这个问题表明您认为printf在赋值或初始化中使用只会导致使用其返回值,从而抑制副作用。事实并非如此。每当在 C 中计算表达式时,都会计算其副作用和主要效果。

\n

(事实上​​,赋值本身就是表达式;c = printf("Hello")是一个表达式,您可以在进一步的操作中使用它,例如x = 3 * (c = 5),它将把 15 赋值给x。我们可以采用任何表达式并在其后面加上分号来创建一个表达式语句,该语句的计算结果是表达式并丢弃主要结果。printf("%d", c);评估printf其副作用并丢弃返回值。)

\n

脚注

\n

1例如,6.5.2.2 6 允许在调用没有原型的函数时用有符号整数类型替换相应的无符号整数类型,反之亦然,但仅限于两者均可表示的值。对于具有 的原型...,7.16.1.1 允许在使用 时进行相同的替换va_arg。6.2.6.2 中的规则要求有符号和无符号类型共有的值位具有相同的值。

\n

2我不记得任何关于合并有符号和无符号类型的正式声明,但我知道编译器设计者会小心翼翼地避免破坏某些遗留用途或某些常见用例的代码,即使 C 标准不要求支持它们。在 C 中,很容易忽略并传递 anunsigned char来打印%u,这在技术上是错误的,因为unsigned charwill 被提升为int, not unsigned int

\n

  • @程序员:该循环的主体永远不会执行,因为循环条件“i++”最初为假,因为“i”从零开始)。在回答了最初的问题后,请不要添加其他问题。 (3认同)