在C语言中将整数打印为浮点数

7

我正在使用Visual Studio TC编译器进行小端编译。

以下是代码片段:

void main()
{    
    float c = 1.0;
    int a = 0x3F800000;
    int *ptr = (int *)&c;
    printf("\n0x%X\n", *ptr);
    printf("\na = %f", a);
    printf("\nc = %f", c);
    return;    
}

输出结果是:
0x3F800000
a = 0.000000
c = 1.000000

浮点数1.0在小端模式下的内存表示为0x3F800000,存储为00 00 80 3F。同样的值被赋给了整数a。当使用printf打印int a时,为什么会打印出0.000000,而打印float c时则会打印1.000000?我曾经看到,当使用printf中的%f打印所有整数时,它都会打印出0.000000。
另外,由于printf是变量参数函数,它如何知道在寄存器中传递的值是整数还是浮点数?

2
这是由于未定义的行为。 - Koushik Shetty
2
给定一个格式说明符为%f,并给定一个类型为int的参数。 - Koushik Shetty
4
当将任何 float 类型的参数传递给函数时,它会被提升为 double 类型。如果想要了解具体发生了什么,可以让编译器输出汇编代码,并比较每个 printf() 语句生成的代码之间的差异。 - Adam Liss
2
第7.21.6/9节规定:“如果转换说明符无效,则行为未定义。如果任何参数与相应的转换说明符不匹配,则行为未定义。”这适用于fprintf,但同样适用于printf。 - Koushik Shetty
@yoones 显然它确实检查了这个L1。我正在尝试找到它是如何做到的答案。编辑,我在这里找到了答案。 - Koushik Shetty
显示剩余5条评论
7个回答

6
我的心灵力量告诉我Adam Liss的评论是正确答案:float参数被提升为double,所以printf()函数期望它发生:它期望在栈上得到一个64位值,但却得到32位加上一些碎片数据,这些数据碰巧是零。

如果您增加显示精度,显示应该是类似于a = 0.00000000001的内容。

这也意味着这样做应该可以:

void main()
{    
    double c = 1.0;
    long long a = 0x3FF0000000000000;
    long long *ptr = (long long *)&c;
    printf("\n0x%llX\n", *ptr);
    printf("\na = %f", a);
    printf("\nc = %f", c);
    return;    
}

Medinoc 非常感谢。这解释了一切。 - bugger
1
+1. 我花了半个小时试图证明你是错的。反汇编显示double c立即在FPU寄存器中创建(与float c相反),因此我怀疑这是为了错误的原因而“修复”你的结果。但是,即使完全删除double,仍然会得到观察到的a输出。然后我怀疑GCC对64位整数的特殊处理...但是当一个简单的printf("%f\n", 0x0, 0x3ff0000)在没有任何doublelong long参与的情况下给出了1.0时,我不得不承认失败。查看FPU寄存器是一场徒劳的追逐。 - DevSolar

5
我已经使用gcc编译了您的代码,生成的代码如下:
movl    $0x3f800000, %eax
movl    %eax, -4(%ebp)
movl    $1065353216, -8(%ebp)
leal    -4(%ebp), %eax
movl    %eax, -12(%ebp)
movl    -12(%ebp), %eax
movl    (%eax), %eax
movl    %eax, 4(%esp)
movl    $LC1, (%esp)
call    _printf
movl    -8(%ebp), %eax
movl    %eax, 4(%esp)
movl    $LC2, (%esp)
call    _printf
flds    -4(%ebp)
fstpl   4(%esp)
movl    $LC3, (%esp)
call    _printf

这可能暗示了浮点参数并不来自常规堆栈,而是来自浮点数的堆栈... 我会期望有些随机性,而不是0...

你知道浮点寄存器ST0、ST1等(它们工作方式类似于栈。你可以从内存中加载一个数字并且所有的ST(x + 1) = STx,而ST0则设置为你的值)。 - V-X
抱歉删除了我的评论,让你的评论看起来像是“挂起”了。你的回答让人觉得好像有一个“常规”堆栈和一个“浮点”堆栈。这不仅是错误的,而且只适用于x86平台,我认为这对于问题并没有帮助。x86处理浮点寄存器类似于堆栈的事实并不影响观察到的行为。 - DevSolar
我认为这个问题的答案是与平台相关的。当然,行为是未定义的。特别是在x86平台上,行为是由于浮点数存储在浮点寄存器中,而其他所有内容都存储在堆栈中所致。 - V-X
我们俩都走了弯路,而Medinoc做对了:%f从栈中弹出一个(64位)double并将其打印出来。 在低字节序机器上,(32位)0x3F800000转换为(64位)double不足以显示默认(6位)精度。 尝试增加精度,使用64位类型的a,或者(这就是说服我的方法)调用printf(“%f \n”,0,a)a的值推到与%f相匹配的地方。 我们失败了的兄弟,在Medinoc的帮助下+1。(当然还是UB。) - DevSolar

5
根据-Wall的描述:warning: format ‘%f’ expects type ‘double’, but argument 2 has type ‘int’。这是未定义的行为,详细解释可以在这里找到。
如果转换说明符无效,则行为未定义。如果任何参数与相应的转换说明符的正确类型不匹配,则行为未定义。
所以你在这里看到的是编译器构建者决定发生的事情,可能是任何东西。

编译器可以特殊处理printf吗?编译器知道如何处理printf吗? - Koushik Shetty
不,它们定义了在发生无效类型转换时会发生什么。 - Bort
但是警告提到了检查参数的无效性,并将其与"格式说明符"进行比较,对吗? - Koushik Shetty
啊,是的,你说得对,我也知道原因了。+1 - Koushik Shetty

2
在任何C语言实现中,都有关于如何传递参数给函数的规则。这些规则可能会指定某些类型的参数在某些寄存器中传递(例如,整数类型在通用寄存器中传递,浮点类型在单独的浮点寄存器中传递),大型参数(例如具有多个元素的结构体)通过栈或指向结构体副本的指针来传递等等。
在被调用的函数内部,函数会按照规则指定的位置寻找它所需的参数。当您将整数作为参数传递给printf,但在格式字符串中传递了%f时,您是将一个整数放在某个位置,但告诉printf寻找一个浮点数(已晋升为双精度)。如果您的C语言实现的规则指定整数参数和双精度参数在同一位置传递,则printf将找到您整数的位,但将其解释为双精度数。另一方面,如果您的C语言实现的规则指定不同的位置传递参数,则您的整数位不在printf寻找双精度数的位置。因此,printf找到与您的整数无关的其他位。
另外,许多C语言实现都具有32位的int类型和64位的double类型。%f格式化符号用于打印双精度数,而不是浮点数,并且在调用函数之前将浮点值转换为双精度。因此,即使printf找到了您整数的位,那里只有32位,但printf使用了64位。因此,打印的double由您传递的32位和32个其他位组成,它不是您想要打印的值。
这就是为什么您使用的格式说明符必须与您传递的参数匹配的原因。

1

我遇到了类似的问题,最终我开发了一种解决方法,不确定是否符合您的要求。关键点是:您应该传递一个浮点数而不是整数。

#include <stdio.h>
void printIntAsFloat();

int main(void){
    printIntAsFloat();
}

void printIntAsFloat(){
    float c = 1.0;
    int a = 0x3F800000;
    int *ptr = (int *)&c;
    float *fp = (float*)((void*)&a); /*this is the key point*/
    printf("\n0x%X\n", *ptr);
    printf("\na = %f", a);
    printf("\nc = %f", c);
    printf("\nfp = %f", *fp);
    return; 
} 

输出结果如下:

0x3F800000

a = 0.000000
c = 1.000000
fp = 1.000000

操作系统: Fedora21 64位 GCC版本: gcc版本4.9.2 20141101 (Red Hat 4.9.2-1) (GCC)


这仍然具有未定义的行为,与早期答案和评论中讨论的问题相同。你所做的是偶然发现了一种行星排列的组合,它似乎只在一个系统上、一个编译器版本和一组设置上运行。 - M.M
我对C语言不是很熟悉,你能否请稍微解释一下哪一行代码包含未定义的行为? - Chris.Huang
第13行、第14行和第16行需要执行;而第11行和第12行可能需要执行。不过为了避免重复,建议先阅读其他评论。 - M.M
你是对的。我刚刚意识到我所做的与其他人讨论过的完全相同。 - Chris.Huang

-1

转换变量类型

printf("\na = %f", (float)a);
printf("\nc = %f", (float)c);

1
那并没有回答问题,问题是为什么它会表现出这样的行为,而不是如何改变这种行为。 - Adam Liss
-1再加上它甚至不能工作,因为它将a的值转换为浮点数。要做到这一点,他可以使用*((float*)&a) - Adriano Repetti

-1
int a= 0x3F800000;
printf("\na = %f", *(float*)&a);//1.0

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