【问题标题】:different printf behavior with float * and int *?float * 和 int * 的不同 printf 行为?
【发布时间】:2013-10-04 21:16:58
【问题描述】:

首先,让我澄清一下,我知道将指针作为参数传递给这些 printf 说明符是不正确的。但是,我很想知道printf 完成后发生了什么。

使用 normal 打印说明符,%d 用于int%f 用于float,为什么会打印int *,而float * 不会?

例如,给定这些变量(未初始化):

int a, *pA;
float b, *pB;

pA = &a;  
pB = &b;

当这样调用时:

void printVar(int *a, float *b)
{
    printf("%d\n", a);//why does "a" print at all?
    printf("%d  %p\n",   a,  b);// when "b" prints only using %p
    //printf("%d  %f", a,  b);// but fails on parameter mismatch using %f
    printf("%d  %f\n"  ,  *a, *b);// prints normally (as expected)
}

为什么我会得到这个?:(“a”打印正常,但“b”仅使用 %p 或通过 *b 打印)

[edit] 整个代码以澄清和解决一些评论问题:

#include <ansi_c.h>

void printVar(int *a, float *b)
{
    printf("%d\n", a);//why does "a" print at all?
    printf("%d  %p\n",   a,  b);// when "b" prints only using %p
    //printf("%d  %f", a,  b);// but fails on parameter mismatch using %f
    printf("%d  %f\n"  ,  *a, *b);// prints normally (as expected)
}

int main()
{
    int a, *pA;
    float b, *pB;
    char s[100], *pS;

    pA = &a;
    pB = &b;
    pS = &s[0];

    printVar(pA, pB);

    getchar();

    return 0;
}

***[edit 2] 如果取消注释 3rd printf,则解决一些关于实际内容的 cmets

我收到以下两个运行时通知,然后在第 3 行没有输出 printf:

【问题讨论】:

  • 这些变量与函数中的变量不同。只需发布一个简短、独立、正确的示例。
  • @self,我会编辑。错误地留下了字符。
  • 您在完整程序中将ab 设置为什么值? (不是pApB。)
  • "当 "b" 仅使用 %p 打印时" 你是什么意思?您的屏幕截图显示您获得了输出。
  • 对所有评论者来说,为了清楚起见,我将发布整个代码。变量的传递与现有代码中显示的一样,这是现在所见的真实输出。

标签: c printf format-specifiers


【解决方案1】:

你有这些参数:

int *a, float *b

这个:

printf("%d\n", a);

很可能将a(类型为int*)占用的内存空间视为它是int 类型的对象。在许多系统上,这会给你一个几乎有意义的结果。 (当然如果你真的想打印一个指针值,你需要将它转换为void*并使用%p,但你问的是你的错误代码的行为,而不是如何修复它。)

如果intint* 的大小不同,或者ints 和指针作为参数传递的方式不同(某些 CPU 具有专用地址和/或浮点寄存器,例如示例)。

printf("%d  %p\n",   a,  b);

void*float* 很可能具有相同的表示形式,尽管语言不能保证这一点。如果intint* 恰好大小相同,并且使用相同的参数传递约定进行传递,则很可能会打印指针a 的内容,就好像它是int 对象一样,并且然后将b 的值打印为指针。

//printf("%d  %f", a,  b);// but fails on parameter mismatch using %f

"%f" 需要 double 类型的参数,not 类型为 floatfloat 参数被提升为 double,用于像 printf 这样的可变参数函数)。 如果 intint* 大小相同, doublefloat* 大小相同, 所有这些类型是使用相同的参数传递约定传递的,那么这很可能会打印指针对象 a 的值,就像它是 int 一样,并且指针对象 b 的值就像它是double 对象。后者很可能会为您提供完全垃圾的浮点值,甚至可能是 NaN 或 Infinity。但是,如果这些假设中的任何一个失败,printf 可能会以 一些 顺序从堆栈(或寄存器)中获取数据,这些数据可能与参数值匹配,也可能不匹配。

上述每个printf 调用都有未定义的行为,这意味着,严格来说,您不应该对将会发生的事情抱有任何期望——即使行为在任何方面都是一致的。了解幕后发生的事情对于识别错误的原因很有用(啊,那个垃圾浮点值看起来类似于我上个月弄乱格式字符串时得到的值),但对于其他任何事情都没有。

printf("%d  %f\n"  ,  *a, *b);// prints normally (as expected)

啊,这更像是——但如果这是在之前的调用之后执行的,它们未定义的行为可能会把事情搞得一团糟,以至于即使这样也不起作用。此外,查看您的整个程序,main(其地址传递给printVar)中的变量ab 未初始化,因此即使在最好的情况下*a*b 也是垃圾.感谢黑客在评论中指出这一点。

【讨论】:

  • 首先,感谢您认识到我对[我的]不正确代码的行为感兴趣,而不是如何修复它。您的观察:float 参数被提升为像 printf 这样的可变参数函数加倍可能是我得到我观察到的行为的原因。谢谢。
  • printf("%d %f\n" , *a, *b); 未定义行为的另一个原因是a 的值和b 未在main 中初始化。
【解决方案2】:

仅仅因为b 不是浮点数,只有*b 是。指针实际上是整数(它们可能不是!)只是一个幸运的巧合,因此使用 %d 说明符打印它们是有效的(并导致一些不太有意义的东西)。

【讨论】:

    【解决方案3】:

    这很简单——因为参数类型与格式字符串中的预期类型不匹配,你会得到未定义的行为。任何事情都有可能发生,所以没什么大惊小怪的。

    一些调用约定不在堆栈上传递浮点参数;相反,它们通过特殊的浮点寄存器传递它们,例如x87 堆栈或 SSE 寄存器。因此,当printf 看到%f 格式说明符时,它会尝试从这些位置读取一个浮点值,但是当它看到%d 格式说明符时,它会尝试从堆栈中读取一个整数。相反,当您传入一个指针时,编译器会将其传递到堆栈上。

    例如,这里有一个简单的函数:

    void printVars(float a, float *p)
    {
      printf("%f\n", a);
      printf("%p\n", p);
    }
    

    这是在我的 x86-64 系统上反汇编的注释版本,它使用 SSE 寄存器 %xmm0 等传递浮点值:

    00000000004004f4 <printVars>:
      ;;; Function prologue
      4004f4:   55                      push   %rbp
      4004f5:   48 89 e5                mov    %rsp,%rbp
      4004f8:   48 83 ec 10             sub    $0x10,%rsp
    
      ;;; Set up floating-point argument in %xmm0 register
      4004fc:   f3 0f 11 45 fc          movss  %xmm0,-0x4(%rbp)
      400501:   48 89 7d f0             mov    %rdi,-0x10(%rbp)
      400505:   f3 0f 10 45 fc          movss  -0x4(%rbp),%xmm0
      40050a:   0f 5a c0                cvtps2pd %xmm0,%xmm0
      ;;; Set up format string argument (%rdi) and call printf
      40050d:   b8 3c 06 40 00          mov    $0x40063c,%eax
      400512:   48 89 c7                mov    %rax,%rdi
      400515:   b8 01 00 00 00          mov    $0x1,%eax
      40051a:   e8 d1 fe ff ff          callq  4003f0 <printf@plt>
    
      ;;; Set up format string argument (%rdi) and pointer argument (%rsi) amd
      ;;; call printf
      40051f:   b8 40 06 40 00          mov    $0x400640,%eax
      400524:   48 8b 55 f0             mov    -0x10(%rbp),%rdx
      400528:   48 89 d6                mov    %rdx,%rsi
      40052b:   48 89 c7                mov    %rax,%rdi
      40052e:   b8 00 00 00 00          mov    $0x0,%eax
      400533:   e8 b8 fe ff ff          callq  4003f0 <printf@plt>
    
      ;;; Function epilogue
      400538:   c9                      leaveq 
      400539:   c3                      retq 
    

    【讨论】:

    • Adam - 参考 Keith Thompson 的回答,他指出浮点数在 veriadic 函数中被提升为双精度,在你的答案中的哪一行可以看到这个提升(我不熟悉使用,或如何解释反汇编程序)
    • @ryyker:它发生在cvtps2pd instruction,它将两个单精度值转换为两个双精度值。
    【解决方案4】:

    让我们逐行检查代码

    void printVar(int *a, float *b, char *s)
    {
        printf("%d\n", a);            // here, printf print address of a as an int
        printf("%d  %p\n",   a,  b);  // address of b is not a float number
        printf("%d  %f\n"  ,  *a, *b);// *a is a int, *b is a float number
    }
    

    【讨论】:

    • 技术上 1/ 和 2/ 是未定义的行为,为 a 传入的类型与格式字符串不匹配。
    • 你是对的,但许多编译器对用户来说太友好了:)
    【解决方案5】:

    对您的程序进行这种修改的行为可能很有启发性。

    #include <stdio.h>
    void printVar(int *a, float *b)
    {
        printf("%d %e %p\n", a, a, a);
        printf("%d %e %p\n", b, b, b);
        printf("%d %e %p\n", *a, *a, *a);
        printf("%d %e %p\n", *b, *b, *b);
    }
    int
    main(void)
    {
      int a = 0x44444444;
      float b = 5.019220152e+33;
      printVar(&a, &b);
      return 0;
    }
    

    【讨论】:

    • 您的运行不会导致使用此确切代码的运行时错误吗? - 我在您的第 1、2、3 行、第二个参数和第 4 行、第一个参数上收到相同的错误(参数不匹配)。
    • 不知道为什么有人投了反对票 - 它确实为我所看到的提供了一些见解。
    • 不,我根本没有遇到运行时错误(编译器确实会抱怨每个不正确的参数,但这些都是警告)。不过,我使用的是不同的操作系统(MacOS)。我预计运行时错误会像您从特殊的调试工具 C 库中获得的 only 错误;它一定会增加大量开销。
    猜你喜欢
    • 2012-01-02
    • 2013-06-08
    • 1970-01-01
    • 1970-01-01
    • 2012-09-18
    • 2023-02-02
    • 2011-10-11
    • 2011-01-24
    • 1970-01-01
    相关资源
    最近更新 更多