【问题标题】:Strange behaviour of two successive printf() calls in CC 中两个连续 printf() 调用的奇怪行为
【发布时间】:2017-07-02 14:42:40
【问题描述】:

我在玩 C;看看这个:

#include <stdio.h>
#include <stdlib.h>

void main() {
    printf("%d\n", 1.5);
    printf("%f", 0);
}

我期望的输出:

0
0.000000

但它会打印:

0
1.500000

第一个printf() 是否将1.5 传递给第二个printf()

PS:我知道(%d 用于整数,%f 用于浮点数)。正如我所提到的,我只是在弄乱代码。

PS2:我正在使用 DevC++ 和 Code::Blocks。

【问题讨论】:

  • void main()非法签名! --> int main(void)。其余的也会调用未定义的行为。阅读手册页和一本好的 C 书,这是绝对基础的东西。
  • 您没有使用在&lt;stdlib.h&gt; 中声明的任何函数(或任何标识符)。包括它是多余的。

标签: c printf


【解决方案1】:

根据 C 标准未定义该行为,以下是您的系统上可能发生的情况:

  • 对于第一个调用printf("%d\n", 1.5);main,将浮点值1.5 作为双精度值传递到第一个XMM 寄存器并调用printf()
  • printf() 不修改 XMM 寄存器,因为 if 不执行任何浮点运算来处理格式 "%d"。它从不同的地方检索要打印的值:寄存器或堆栈,而这个值恰好是 0
  • 对于第二次调用 printf("%f", 0);main 也不会更改 XMM,因为它通过另一个位置(寄存器或堆栈)传递了 int0
  • 第二个printf() 最终从先前存储1.5 的XMM 寄存器中获得%f 格式的double 值。因此输出1.500000

以上都不能保证,但这可能是您感兴趣的解释。不同的系统可能以不同的方式处理参数传递,它们是 ABI(应用程序二进制接口)的一部分。

只是为了好玩,您可能想试试这个变体:

printf("first %d, second %f\n", 1.5, 42);

在我的系统上输出first 42, second 1.500000

【讨论】:

  • 您确定在所有情况下都使用 XMM(32 位和 64 位系统)吗?
  • @BasileStarynkevitch:不,我不确定,因此您的系统上可能会发生什么...在 64 位系统中似乎是这种情况,可能不在 32 位系统上。我无法测试 OP 使用的 Windows 系统。
  • 对 64 位编译的直觉很好(一个非常混乱的 ABI)。在 32 位系统上,使用高级优化也可能发生这种情况,出于性能原因,这些优化也应该使用 XMM。无论如何,我希望用户收到大量警告,或者编译器真的很差。
【解决方案2】:

你有一些undefined behavior(所以任意坏事情可能happen,你不应该期待任何好的)。对于%fprintf 函数需要double(请注意,当作为参数传递时,float 会提升为double)但0int 类型的文字。此外,不同的编译器(甚至同一编译器的不同版本)或不同的优化标志可能会产生不同的不良影响。

请阅读 Lattner 在What Every C programmer should know about undefined behavior 上的博客。

(对未定义行为的良好态度是努力始终避免它;不要浪费时间试图理解具体发生的事情;但将 UB 视为非常肮脏的或者你总是避免的“sick”)

要解释观察到的行为,您需要深入了解特定实现的细节,特别是 ABIcalling conventions(对于可变参数函数 à la printf)。另外,查看生成的汇编代码(用GCC,用gcc -fverbose-asm -S -O1编译);有可能在寄存器(或某些call stack 插槽)中传递了一个双参数,而不是int 参数(因此printf 函数正在让垃圾发生在该位置或寄存器中);另请注意,sizeof(int) 通常可能是 4,但 sizeof(double) 可能是 8(因此数据量甚至不正确)。

为避免此类错误,请养成使用良好编译器编译的习惯(例如free software 领域中的GCCClang/LLVM)并启用所有警告和调试信息(例如使用gcc -Wall -Wextra -g 和@ 进行编译987654332@)。编译器会警告你的。

顺便说一句,void main() 是非法的。它至少应该是int main(void),最好是int main(int argc, char**argv),并且您应该注意这些参数。

在您的示例中,gcc -Wall -Wextra(使用 GCC 7)告诉(对于您的源文件 april.c):

april.c:4:10: warning: return type of ‘main’ is not ‘int’ [-Wmain]
     void main() {
          ^~~~
april.c: In function ‘main’:
april.c:5:14: warning: format ‘%d’ expects argument of type ‘int’,
                       but argument 2 has type ‘double’ [-Wformat=]
     printf("%d\n", 1.5);
             ~^
             %f
april.c:6:14: warning: format ‘%f’ expects argument of type ‘double’, 
                       but argument 2 has type ‘int’ [-Wformat=]
     printf("%f", 0);
             ~^
             %d

注意:Dev-C++CodeBlocks不是编译器,而是IDEs。它们都运行一些外部compiler(可能GCC 在您的系统上作为MinGW)。

【讨论】:

  • OP 显然很清楚他做了一些无效的事情。所以它归结为“如果我不遵守语言规则,为什么语言不能做我想做的事?”空参数列表是定义的过时功能(声明已经是错误的)。所以应该是int main(void)
  • @Olaf 这就是我的意思,我只想在不遵守规则的情况下检查输出。 “艰难的道路往往通向美丽的目的地”我试图发现一些令人兴奋的新方法来做这类事情,显然它会导致一种未定义的行为,但却得到了我正在寻找的东西。
  • @AprilJohnson:undefined 这个词是故意选择的。没有“预期”的结果。不同的编译器/(版本/选项、一天中不同的时间、不同的气候、不同的环境等)可能会产生不同的结果,包括格式化您的硬盘驱动器或鼻部守护程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-23
相关资源
最近更新 更多