为什么printf会将整数打印为双精度浮点数?

4
printf("%f", 1.0); //prints 1.0

但是。
printf("%f", 1);  // prints 0.0

转换是如何发生的?
5个回答

3
根据下面@Eric Postpischil的评论不同,第一个双精度参数(或浮点参数,如果与函数的...部分一起使用,则将提升为双精度)放入%xmm0。第一个“正常整数类”类型的参数将进入%rdi。对于printf,指向格式字符串的指针是该类别的第一个参数,因此它会进入%rdi。这意味着传递的第一个整数参数进入该类别的第二个寄存器,即%rsi。因此,在printf(“%f”,1)中,printf会查找%xmm0中的浮点值,但调用者在%rsi中放置了整数1。

我明白你的意思,但是我认为简单就是简单,不应该像这样难。 - redkont
2
这个答案是不正确的,它断言表示 int 的字节将被 printf 解释为浮点数。在一些常见的 C 实现中,特别是使用 System V x86-64 ABI 的实现中,第一个 double 参数会被传递到 %xmm0 寄存器中,而在格式字符串后传递给 printfint 参数会被传递到 %rsi 中。在处理 %f 时,printf 将看到 %xmm0 中的字节,而不是 %rsi 中的字节。 - Eric Postpischil
@EricPostpischil,非常感谢您让我知道这个。您的意思是,“1”作为整数常量将被放置在非%xmm寄存器中,当printf扫描此%f时,它将在%xmm0中检查,并获得寄存器具有的任何值。如果我错了,请纠正我。 - Murali Krishna Bellamkonda
@MuraliKrishnaBellamkonda:是的。第一个double参数(或者float参数,如果与函数的...部分一起使用,则会提升为double)被放置在%xmm0中。第一个“普通整数类”类型的参数将进入%rdi。对于printf来说,指向格式字符串的指针是该类别的第一个参数,因此它进入%rdi。这意味着传递的第一个int参数进入该类别的第二个寄存器,即%rsi。因此,对于printf("%f", 1);printf%xmm0中查找浮点值,但调用者在%rsi中放置了一个int 1。 - Eric Postpischil
现在明白了,再次感谢。我有一个疑问,它在32位机器上的行为如何? - Murali Krishna Bellamkonda
我认为我的答案与32位机器有关,因为在32位机器上,值被推入调用函数的堆栈中(实际副本将被创建)。稍后printf将从调用函数堆栈中读取这些值。如果我错了,请纠正我。 - Murali Krishna Bellamkonda

3

printf("%f", 1); 会导致未定义的行为,因为它期望的是一个 double 类型的参数,但你传递了一个 int。由于行为是未定义的,所以没有任何解释可以说明为什么会输出 0.0


1
打印“0.0” 的原因有解释。虽然 C 标准没有给出该解释,但是编译器的源代码、系统 ABI、硬件架构文档等中都可以找到该解释。 - Eric Postpischil
@EricPostpischil,由于没有指定实现,所以问题仅涉及C编程语言。该语言根本不提供任何推理,因此答案如此。 - Ayxan Haqverdili
1
C标准将“未定义”定义为“行为……或本文档未强制要求的内容”(强调相关概念)。这意味着语句“C标准中没有解释”是正确的。从字面上理解,“没有解释”这个说法是错误的。有人可能会认为限定词应该从上下文中理解。不幸的是,这也是错误的:人们无法从上下文中理解这一点。Stack Overflow充斥着错误的概念,即C标准是理解C程序行为的全部和终极方法。 - Eric Postpischil
1
任何有能力的软件工程师都必须了解编译器的工作原理,优化对程序的影响,程序在堆栈和数据段中的组织方式等等,地址引用的工作原理,程序中可能出现的问题,可观察症状是什么错误的线索等等。这些只能通过学习比C标准更多的解释来学习。告诉人们没有C标准定义的行为的解释是有害的。 - Eric Postpischil
@EricPostpischil,虽然您提到的事情很重要,但试图解释未定义行为本质上是错误的。C是一种高级语言-它是一种抽象。告诉人们不要在这种抽象中思考是有害的。 - Ayxan Haqverdili
这并不是天生错误。这是必要的。任何称职的软件工程师都必须掌握这些知识。仅学习抽象概念没有任何好处,而且声称试图解释C标准未定义行为是错误的指责也毫无依据——没有逻辑原则可以得出这样的结论,没有现实世界的实用价值,完全就是胡说八道。 - Eric Postpischil

2
问题不在于printf函数本身,而在于编译器是否聪明。如果你的编译器不够聪明,那么它会将printf视为普通的函数调用,并不知道这个函数的参数语法。因此,它只是将一个字符串和一个整数放在堆栈上并调用函数。printf函数获取第一个参数并开始将其解析为格式化字符串。当它看到格式说明符%f时,它会尝试将堆栈上相应部分的内存解释为浮点数。它无法知道编译器在此之前推送了int值。因此,printf尽力将内存解释为浮点数。结果取决于平台,即端序和浮点/整数大小,并且包括随机性,因为您很可能会碰到一些堆栈上的垃圾。在这种情况下,printf所做的转换也可以看作是这样的:
int i = 1;               // Integer variable
int* pi = &i;            // Pointer to i 
float* pf = (float*)pi;  // Reinterpret the pointer as floating point number address
float f = *pf;           // Get the floating point from this address
printf("%f\n", f);

1
在一些常见的C语言实现中,例如使用System V x86-64 ABI的任何内容,字符串和int都不会被放入堆栈中。字符串的第一个字符的指针被放入一个寄存器中,而int则被放入另一个寄存器中。如果传递的是double而不是int 1,则会将其放入完全不同的寄存器中。这样做的结果是,当printf查找%fdouble时,它看不到int 1的字节。它看到的是另一个寄存器中的字节。 - Eric Postpischil
我同意了解底层发生了什么是很好的,但由于这确实与平台有关,你应该说明你的解释涵盖哪些平台。 - Nate Eldredge

2

并非所有编译器都像这样,有些实际上会打印1.0。但是当指示printf打印双精度值时,必须传递一个双精度值,而不是整数。您可以始终使用类型转换:

printf("%f", (double)1);

gcc和Visual Studio都会打印0.0,同时不建议使用强制类型转换。 - redkont
我认为这不是编译器特定的。它(勉强但可以想象)可能是CPU特定的。 - Jongware
1
你能找出任何一个编译器,能够在遇到printf("%f", 1);时输出“1.0”吗? - Eric Postpischil
在 macOS Catalina 上使用 clang 会产生 1.000000。 - Marc Balmer

1

在这里,printf()期望接收基于您传递的格式的浮点数,如果要将int作为float在printf()中打印,则需要进行强制转换。

printf("%f", (float)1);

or

printf("%f",(double)1);

因为C语言会根据变量的类型和内存表示来处理传递给printf()的变量,如果你传递了错误的值,那么就会导致未定义的行为。

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