一个C语言printf输出的问题

int main(int argc, const char * argv[]) {
    
    printf("%g\n", (double)15/10 );//1.5
    printf("%g\n", 25/(double)10 );//2.5
    printf("%g\n", 35/10 );//2.5
    printf("%g\n", (double)(45/10) );//4
    
    return EXIT_SUCCESS;
}
int main(int argc, const char * argv[]) {
    
    printf("%g\n", 35/10 );//0
    
    return EXIT_SUCCESS;
}

问题

请帮忙解释一下 35/10 的输出?

printf("%g\n", 3 );//0
阅读 4.2k
3 个回答

前言

很高兴看到这样的问题,因为这涉及到现在的人们已经普遍不怎么关心的底层操作。
在人们看来,浮点数和整数就差了一个小数点,但在计算机看来,这里面差别缺非常之大。
这是因为他们的解析方式不同。

证明

int main(int argc, const char * argv[]) {
    float i = 3;
    printf("%08x\n", *(unsigned int*)&i);
    unsigned int j = 3;
    printf("%08x\n", *(unsigned int*)&j);

    getchar();
    return EXIT_SUCCESS;
}

这段代码用来输出内存中他们真正的值(十六进制)。
这两句的代码唯一的区别在于他们的类型不同,第一个为float,第二个为unsigned int
在人类看来,他们几乎是一样的,但在内存中,他们完全不可混为一谈。
上面的输出结果为

40400000
00000003

即,浮点型的3在内存中为40400000,整数型的在内存中为3
怎么样,他们的是不是不一样的?

延伸

int main(int argc, const char * argv[]) {
    float i = 3;
    printf("%g\n", i);

    int j = 3;
    printf("%g\n", j);
    getchar();
    return EXIT_SUCCESS;
}

那么你认为他们的结果会一致吗?当然不会。它的结果为:

3
2.55827e-303 //溢出,可能不一致

所以

在c语言中/运算符不会改变结果类型,也就是说,两个整数型相除的结果也是个整数型。

int main(int argc, const char * argv[]) {

    printf("%g\n", 35 / 10);//0
    getchar();
    return EXIT_SUCCESS;
}

你的代码中就相当于

int main(int argc, const char * argv[]) {

    printf("%g\n", 3);
    getchar();
    return EXIT_SUCCESS;
}

这当然不会是你期望的。

再次证明

要怎么证明呢?很简单,我们看下反汇编的代码就知道了

push        ebp  
 mov         ebp,esp  
 sub         esp,0C0h  
 push        ebx  
 push        esi  
 push        edi  
 lea         edi,[ebp-0C0h]  
 mov         ecx,30h  
 mov         eax,0CCCCCCCCh  
 rep stos    dword ptr es:[edi]  
 push        3  //此处传入的本来就是个常量 3
 push        offset string "%g\n" (0D23D20h)  
 call        _printf (0D01F91h)  
 add         esp,8  
 mov         esi,esp  
 call        dword ptr [__imp__getchar (0D2A3A0h)]  
 cmp         esi,esp  
 call        __RTC_CheckEsp (0D0161Dh)  
 xor         eax,eax  
 pop         edi  
 pop         esi  
 pop         ebx  
 add         esp,0C0h  
 cmp         ebp,esp  
 call        __RTC_CheckEsp (0D0161Dh)  
 mov         esp,ebp  
 pop         ebp  

修正

我们将上面的代码稍微改一下,只要让编译器认为我们输入的是个小数型就可以了。方法有很多种。

    printf("%g\n", (double)15/10 );//1.5
    printf("%g\n", 25/(double)10 );//2.5
    printf("%g\n", 35/10+0.0 );//2.5
    printf("%g\n", (double)(45/10) );//4

If a conversion specification is invalid, the behavior is undefined.
If any argument is not the correct type for the corresponding
conversion specification, the behavior is undefined.

g代表浮点,但是传入的是int型,printf的行为是不可控的。


有意思的实验

printf("%d, %f, %d, %f\n", 666.0, 555, 444, 333.0);

输出

555, 666.000000, 444, 333.000000

似乎int和float被放入两个不同的stack,根据实际类型来消费stack里的数据 (大误,瞎猜 ^ ^)

%g要求double型的数据,你传入了一个int型的。printf不是类型安全的函数。它只是按照格式串去解释压入堆栈的数据,如果类型错了,必然结果不正确,并不会自动转换。
其实不用证明,x86的浮点数在内存中是按照IEEE754浮点数规则存储的,而有符号整数则是按照补码的方式存储的。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏