【问题标题】:function return changed as directly written in printf statement函数返回更改为直接写在 printf 语句中
【发布时间】:2020-07-20 08:59:21
【问题描述】:
#include <stdio.h>
#include <math.h>

int main() {
    printf("%d\n", pow(3546, 0));   
    return 0;
}

上面的代码打印值0

虽然下面的代码打印值1

#include <stdio.h>
#include <math.h>

int main() {
    int a  = pow(3546, 0);
    printf("%d\n", a);
    return 0;
}

为什么会这样?尽管它们是等价的。

【问题讨论】:

  • 第二个代码因为一个小错字而无法编译,否则我们需要更多关于编译器和编译器标志的信息来帮助你。我们可以得到的唯一提示是 pow 的结果隐式类型转换变成了哪种类型的整数。即使这样,大多数优化最少的编译器也会看到 pow 表达式是常量,并在编译过程中对其进行转换。
  • 第一个是未定义的行为,这意味着任何事情都可能发生(并且没有一致性要求)

标签: c function printf pow


【解决方案1】:

pow 返回double 类型的结果,而%d 不是用于格式化它的正确转换说明符。产生的行为未由 C 标准定义。

要打印double,您可以使用%g%f,以及其他选项:

printf("%g\n", pow(3456, 0));
printf("%f\n", pow(3456, 0));

int a = pow(3546,0); 中,double 值会自动转换为int 用于初始化int a,然后用%d 打印int 是正确的。

【讨论】:

  • @chqrlieforyellowblockquotes:我怀疑这是一个心怀不满的参与者的报复投票。我不知道其他的。我一般不担心。我没有为积分做出贡献,并且有足够的现实世界积分,以至于想象中的互联网积分不是问题。我致力于分享好的信息并专注于此。
  • 是的,我在同一页上......顺便说一句,你在 1975 年用 Fortran 编写了什么样的计算机?我是从那年九月开始的 :)
  • @chqrlieforyellowblockquotes:一些 IBM 机器,但我不确定是哪一个。系统 360?
【解决方案2】:

没有等价物。后一个函数在打印之前将数字显式转换为int,而前者通过尝试使用错误的格式说明符打印浮点数导致UB——你很幸运得到0,你可能得到了任意号码。

请打开编译器上的所有警告,它应该会抱怨对错误类型的数据使用错误的格式。

【讨论】:

  • 后面的代码既不包含任何强制转换,也不显式转换。 cast(type) operand 形式的运算符。在int a = pow(3456, 0); 中,没有强制转换,也没有显式转换。由于 C 标准中的规则,转换是隐式的;代码中没有明确说明。 (显式的int 声明了a 的类型;隐式用于转换。)
【解决方案3】:

正如this answer 中提到的,printf 使用格式说明符来知道要从堆栈中弹出什么(%dsizeof(int),在我的系统中是 4 个字节)。

(编辑: 正如 cmets 中的 rici 所指出的,它不能保证是堆栈(在 x86 上,它大部分时间都在 CPU 寄存器中)。标准从不谈论实现,这就是这个问题的全部内容。)

一个有趣的小事实:在我的 Windows 10 框中,它是 0 和 1。在我的 Xubuntu VBox 中,它是 1 和 -2132712864。因此,printf 仅弹出 4 个字节,而您的机器的字节序会破坏结果。来自pow(3) manual

#include <math.h>

double pow(double x, double y);
float powf(float x, float y);
long double powl(long double x, long double y);

所以,为了在这两种情况下都得到正确的结果,请使用correct printf format specifier

double a = pow(3546, 0);

还有:

printf("%lf\n", pow(3546, 0));

如果您想了解更多可变参数函数(像printf 这样的函数,它接受可变数量的参数),请从阅读API manual page 开始。

#include <stdarg.h>

void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);

【讨论】:

  • 引用的答案并不普遍正确,因为函数调用不需要使用堆栈来传递参数。事实上,在 x86 上,只有在有很多参数时才会使用堆栈。参数通常在寄存器中传递,这里是关键:整数和浮点数进入不同的寄存器
  • @rici 是的,没错。但是,为了简单起见,我认为这是回答简单问题的最佳方式。恕我直言,好奇的头脑有足够的链接来扩展自己。您对如何同时保持简单性有什么建议吗?
  • 实际上他们都坚持使用堆栈。但是 C 标准本身从未提及堆栈。我知道这听起来像是一种技术性,但知道它仍然有些有用。老实说,我不知道好奇的人会在哪里找到那个链接,尽管我过去曾尝试过。
  • 如何简单地引入一个答案不需要的概念?核心概念是%d 打印powdouble 结果是错误的,%lf 是正确的。这么说,并说第二个版本有效,因为int a = pow(3456, 0);powdouble 结果转换为int,然后printf 正确使用%d 打印int。并且不要将初学者扔到可变参数功能中;这些会让他们感到困惑。
  • @EricPostpischil 错了。编译时转换比printf 转换做得更好,因为实现细节,而不是因为转换。两者都是double -> int。在这两种情况下它都是 UB,因为你依赖于实现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-18
  • 2011-10-26
相关资源
最近更新 更多