【问题标题】:printf crashes while printing float valuesprintf 在打印浮点值时崩溃
【发布时间】:2013-09-02 19:51:55
【问题描述】:

我今天遇到了printf() 的一个奇怪问题。但是即使分析了它,我也找不到它的答案。因此在这里分享。我尝试了这三个printf() 语句:

printf("\nValue of this division is %f", (double)873/(double)65);

它按预期打印正确的输出。

printf("\nSome message with an integer here %d followed by floats %f, %f, %f", 2013, 987/432, 873/65, 983/81);

给了我错误的值(因为我没有将它们转换成双倍?)

printf("\nSome message with an integer here %d followed by floats %f, %f, %f and now string at end: %s", 2013, 987/432, 873/65, 983/81, "Some trial string here");

printf() 在这里崩溃了!这引发了我两个问题:

  1. 我在 MSDN 中看到了“FormatOutput(LPCSTR formatstring, ...)”示例,他们在其中分配固定宽度的目标缓冲区,然后用它调用 vsnprintf()。我相信printf() 也是如此。但我没有找到printf() 的内部缓冲区大小。如果它动态分配内存,那么它如何计算缓冲区大小?

  2. printf() 在上面的行崩溃,因为vsnprintf() 也在那里崩溃(是的,我尝试使用上述参数为vsnprintf() 给出的FormatOutput 示例代码)。但是为什么最终会崩溃呢?

【问题讨论】:

  • 您正在投射到double,尝试投射到float。
  • @JonathanPotter,反正他们不会升职吗?
  • @Jonathan Potter:如果我没记错的话,变量参数列表首先将floats 转换为double。
  • 好的,继续,这里没什么可看的:)
  • @Andrew:您可能需要检查编译器开关。我知道 gcc 实际上能够在编译时解析 printf (...) 格式字符串并验证您传递的参数列表,而且我相信我已经看到 Microsoft Visual C 的较新版本也这样做了。使用正确的开关,可以将它们设置为警告您执行此类操作。 (当格式需要双精度时传递整数值)

标签: c++ c printf


【解决方案1】:

您承诺打印doubles,但您传递的是ints:987/432 是int。例如,如果您希望此值为 double,则可以使用 987.0/432。

【讨论】:

    【解决方案2】:
    printf("\nSome message with an integer here %d followed by floats %f, %f, %f and now string at end: %s", 2013, 987/432, 873/65, 983/81, "Some trial string here");
    

    正如前面的答案所解释的,整数除法的结果是整数。所以987/432 会在您的系统上产生一个(很可能) 32 位int。但是%f 告诉printf 期待一个64 位的double!因此,它读取987/432 和873/65 的组合内存空间 - 给出垃圾结果。然后,另一个 64 位 double: 983/81 和指向 "Some..." 的指针 - 又是垃圾。

    接下来是另一个 64 位双精度数(第三个 %f),现在超出了范围!您的第二个示例没有在那里崩溃可能是幸运的。当它到达%s 时,printf() 会寻找一个指针……如果它没有因为越界访问而崩溃,它就会试图访问结果指针的无效内存地址。

    1. printf() 不需要分配内存。它直接写入标准输出(例如您的终端窗口)

    2. printf 系列的任何变体都会同样崩溃。

    【讨论】:

    • “它直接写入标准输出”.. 确实:-) 我错了假设 printf 在内部首先使用 vsnprintf 准备其输出。但它实际上并不需要这样做。
    【解决方案3】:

    您的参数 `2013, 987/432, 873/65, 983/81' 都是整数!

    【讨论】:

      【解决方案4】:

      在您的示例中,当它需要双精度浮点时,您将整数除法的结果提供给 printf (...)。使用单精度或双精度浮点除法或更正格式字符串。请注意,单精度浮点是 automatically promoted 到双精度,就像 char 和 short 在与变量参数列表一起使用时被提升为 int。

      作为一个思想实验,您应该考虑在假设场景中可能发生的无数事情,您将指针从 32 位数据类型转换为 64 位类型,然后尝试取消引用它。就像上面说明的指针场景一样,您正在处理由于不正确地传达数据类型而导致的未定义行为。如果您调用未定义的行为,可能会发生许多令人讨厌的事情并导致您的程序崩溃。

      尽管如此,一些编译器能够在编译时解析您的格式字符串,并根据您提供的参数列表验证它printf (...)。您可能需要查看系统上可用的编译器警告,以便将来避免这种情况。

      【讨论】:

      • 我对您的评论感到困惑 '只需考虑将指针从 32 位数据类型转换为 64 位并尝试取消引用它即可了解原因这有可能导致崩溃。' 这个问题不涉及任何指针。如果你说这样的话:'考虑一下当你告诉printf() 传递三个 4 字节 int 值时会发生什么,它将被赋予三个 8 字节 double 值,你会意识到它将访问的数据没有通过,以一种意想不到的格式,并且可能会出现奇怪的结果或崩溃”,那么我就不会感到困惑了。
      • @JonathanLeffler 我没有这么说,因为不能保证整数是 4 字节类型或字节是 8 位。这是为了暗示提问者未定义的行为。我没有说这就是正在发生的事情,只是他考虑了在这种情况下出现的情况类型。
      • 真;如果这是我的回答,我会添加关于“假设......的大小”的警告,但这并不容易融入评论。虽然您提到指针,就好像指针是问题代码中的一个因素一样,但我不能对您的答案投赞成票——即使其中还有其他一些好的材料。
      • @JonathanLeffler:很好,我正在寻找一种快速简便的方法来演示另一种情况,在这种情况下编译器无法强制甚至识别不兼容类型和指针的违规行为是我首先想到的.我过去也在没有标准库的平台上实现了printf (...),并且我使用了基于格式字符串的va_arg 列表成员的指针转换。希望编辑更清晰。
      【解决方案5】:

      无需使用(double)873/(double)65 进行投射,只需使用(double)873/65 甚至873.0/65。将int 除以int 将始终得到int 值。要打印double,您可以使用987.0/432 或简单地使用987f/432 打印float。两者都可以。

      【讨论】:

      • 理论上,987f/432 可能会给出与987.0/432 不同的答案,因为一个被计算为float,然后转换为double,另一个被计算为double .使用普通的%f 格式,您不会注意到差异;使用%.15f,您可能会注意到它。
      • @JonathanLeffler;更新了我的答案。
      猜你喜欢
      • 2016-10-28
      • 2020-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-14
      • 1970-01-01
      相关资源
      最近更新 更多