【问题标题】:c programming printf format searches expressionsc编程printf格式搜索表达式
【发布时间】:2015-08-01 09:34:39
【问题描述】:

我有一个小问题。 我有这段代码:

#include <stdio.h>

int main(){
    printf("%d, %f, %d\n", 0.9, 10, 'C');
}

输出是这样的:

10, 0.900000, 67

但我早就料到了:

0, 10.0, 67

但是看起来printf在表达式中搜索对应的类型(int和float转了) 谁能帮我解决这个问题? 非常感谢!

【问题讨论】:

    标签: c format printf


    【解决方案1】:

    但我早就料到了:……

    当格​​式字符串与参数类型的顺序不匹配时,行为未定义。任何事情都有可能发生。你不能期待任何东西(我不明白为什么你会期待 0。也许你希望 printf 使用格式字符串在浮点和整数之间转换参数。它只是没有。它是一个像你一样的可变参数函数可以自己编写,格式字符串对尾随参数的类型进行编码这一事实不用于转换它们)。

    现代调用约定对前几个参数使用寄存器,甚至可以对可变参数函数使用寄存器。例如,在 x86-64 上,约定可以是可变参数函数的第一个浮点参数总是可以在浮点寄存器 xmm0 中预期,而整数参数在通用寄存器 %rdi 中传递, %rsi, %rdx, ... 这会产生printf("%f %d", 1, 1.0) 打印浮点参数后跟整数参数的效果。

    作为说明,这里有一个简短的程序:

    #include <stdio.h>
    
    int main(){
      printf("%d, %f, %d\n", 0.9, 10, 'C');
    
      printf("%d, %f, %d\n", 10, 0.9, 'C');
    }
    

    这是我的编译器(Mac OS X 10.6 上的 Clang)编译程序的方式:

    leaq <memory location of format string>, %rbx
    
    movq    %rbx, %rdi
    movsd   <memory location of 0.9 constant>, %xmm0
    movl    $10, %esi
    movl    $67, %edx
    movb    $1, %al
    callq   _printf
    
    movq    %rbx, %rdi
    movl    $10, %esi
    movsd   <memory location of 0.9 constant>, %xmm0
    movl    $67, %edx
    movb    $1, %al
    callq   _printf
    
    …
    

    很明显,这两个调用产生了相同的结果。但就其中一种情况而言,它是偶然的,仅适用于这个特定的编译器版本和 ABI,而另一种则尊重标准,必须在任何地方工作。

    同样,printf("%d %f", 0.9, 10) 是未定义的行为,您不应在任何情况下使用它。

    【讨论】:

      【解决方案2】:

      这是未定义的行为,所以任何事情都可能发生。

      %d 需要int,但您传递的是double(不是float),因此printf 占用double 值的4 个字节并将其解释为int

      %f 需要 double,但您传递的是 int。所以它会从int 中取出 4 个字节,并从下一个内存中取出 4 个字节,并将其解释为 double

      你很幸运能传递 16 个字节,而 printf 需要 16 个字节,所以最后一个值是正确的。

      【讨论】:

      • 此答案假定printf 的参数在堆栈上传递,这不太可能产生问题中显示的结果。
      【解决方案3】:

      唯一正确的期望是获得67 用于使用%d 格式说明符* 打印字符。其他两个打印输出是未定义的行为。

      看起来 printf 在表达式中搜索相应的类型

      这只是巧合。 printf 不知道您传递的实际参数的类型。它信任格式字符串,并按顺序解释数据。您可以通过提供不同的数字并观察输出的变化来判断发生了什么。

      您看到的数字是垃圾 - double 被重新解释为 intint 被重新解释为 double。此外,如果doubleint 的大小不同,则前两个参数会相互交叉。

      要生成您想要的输出,请将转换添加到前两个参数表达式:

      printf("%d, %f, %d\n", (int)0.9, (double)10, 'C');
      

      请注意,您不需要强制转换最后一个参数,因为 char 被提升为 int 作为处理 printf 的可变长度参数列表的一部分。

      * 只有当其他参数和格式说明符之间没有不匹配时,才会产生正确的行为;你的程序有 UB,即使是最后一个参数,如果单独使用它也是正确的。

      【讨论】:

      • "double 被重新解释为 intint 被重新解释为 double" - 不,这不是正在发生。 intdouble 值在不同的位置/寄存器中传递。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-15
      • 1970-01-01
      相关资源
      最近更新 更多