zoukankan      html  css  js  c++  java
  • 关于printf错用格式化字符串导致double和long double输出错误的小随笔

    【题外话】

    以前用HUSTOJ给学校搭建Online Judge,所有的评测都是在Linux下进行的。后来为了好往学校服务器上部署,所以大家重新做了一套Online Judge,Web和Judge都是基于Windows和.NET平台的。这两天将学校Online Judge中以前在Linux下(GCC 4.6.3)评测的提交全部在Windows上(GCC 4.7.2 MinGW)重测一遍,结果莫名其妙发现很多以前通过的题目现在出现了结果错误的问题,其共同结果都是结果为0,查看源代码发现其都是使用printf("%ld")输出的double。原本以为是GCC的Bug,后来查找资料才发现实际上是对C语言了解不够充分加上MinGW的问题才共同导致的问题。

    【文章索引】

    1. 奇怪的问题
    2. 格式化字符串的问题
    3. MinGW的问题

    【一、奇怪的问题】

    把出错的一个提交的代码精简,然后就剩下如下的代码:

    #include <cstdio>
    using namespace std;
    int main()
    {
        double n;
        scanf("%lf",&n);
        printf("%.0lf
    ",n);
        return 0;
    }

    在Linux下结果正常:

    结果在Windows下会出现如下图的结果:

    【二、格式化字符串的问题

    接下来将上述程序的printf替换为cout,发现没有任何问题,判断是printf那行出现了问题。

    查找相关资料(如相关链接1)发现,不论输出float还是double都应该使用printf("%f"),因为不论float还是double都会作为double类型输出,确实以前没有注意到这个问题。所以在第一节给出的那个程序将“%lf”改为“%f”就正确了。但相关链接1中并没有说明%lf指的是什么。

    不过如果尝试在GCC上编译如下的代码却会给出如下图的警告:

    #include <cstdio>
    using namespace std;
    
    int main()
    {
        long double n = 1.22222222;
        printf("%f", n);
        printf("%lf", n);
        return 0;
    }

    也就是说,对于GCC而言,在printf中使用“%f”和“%lf”实际上都表示的是double类型,而要表示long double,则应该使用“%Lf”(注意大小写),而使用MSVC编译编译时并没有发生这些问题(也可能是因为MSVC认为double = long double,所以一切都一样了吧)。

    【三、MinGW的问题】

    虽然上一节找出了第一节程序的问题,可是为什么会出现这样的问题呢。

    继续查找发现了相关链接2相关链接3,发现在这两个问题的回答中都提到了MinGW在Windows上运行是需要MSVC的运行时的。以前确实也没注意到这点,于是去MinGW的官方网站,确实发现了如下两段:

    MinGW provides a complete Open Source programming tool set which is suitable for the development of native MS-Windows applications, and which do not depend on any 3rd-party C-Runtime DLLs. (It does depend on a number of DLLs provided by Microsoft themselves, as components of the operating system; most notable among these is MSVCRT.DLL, the Microsoft C runtime library. Additionally, threaded applications must ship with a freely distributable thread support DLL, provided as part of MinGW itself).

    MinGW compilers provide access to the functionality of the Microsoft C runtime and some language-specific runtimes. MinGW, being Minimalist, does not, and never will, attempt to provide a POSIX runtime environment for POSIX application deployment on MS-Windows. If you want POSIX application deployment on this platform, please consider Cygwin instead.

    果然,MinGW虽然不需要任何第三方的运行库,但是需要微软的运行库,其中包括了MSVCRT.DLL以及其他的微软C语言运行库。所以GCC编译后的程序还是运行在MSVC运行库上的程序。同时又由于32位的MSVC并不支持更高精度的double类型(在32位的MSVC中long double与double的精度均为8位,见相关链接4),而GCC在32位的long double是12字节,64位更是16字节,所以就出现了不兼容的问题。

    所以我们可以做这样一个实验,将long double类型存储的数据按字节输出、同时按不同方式输出其结果,代码如下:

     1 #include <cstdio>
     2 using namespace std;
     3 
     4 void print_bytes(const char* name, long double &n)
     5 {
     6     char* p = (char*)&n;
     7 
     8     printf("%s [%ld-%ld]
    ", name, p, p + sizeof(long double));
     9 
    10     for (int i = 0; i < sizeof(long double); i++)
    11     {
    12         printf("0x%02X ", (*p & 0xFF));
    13         p++;
    14     }
    15 
    16     printf("
    ");
    17 }
    18 
    19 int main()
    20 {
    21     long double n1 = 0;
    22     long double n2 = 0;
    23 
    24     print_bytes("inited_n1", n1);
    25     print_bytes("inited_n2", n2);
    26     printf("
    ");
    27 
    28     scanf("%lf", &n1);
    29     scanf("%Lf", &n2);
    30 
    31     print_bytes("inputed_n1", n1);
    32     print_bytes("inputed_n2", n2);
    33     printf("
    ");
    34 
    35     printf("type 		 n1 				 n2
    ");
    36     printf("%%f 		 ");
    37     printf("%f 			 ", n1);
    38     printf("%f
    ", n2);
    39 
    40     printf("%%lf 		 ");
    41     printf("%lf 			 ", n1);
    42     printf("%lf
    ", n2);
    43 
    44     printf("%%Lf 		 ");
    45     printf("%Lf 			 ", n1);
    46     printf("%Lf
    ", n2);
    47 
    48     return 0;
    49 }

    分别将这个代码在32位机器上用MSVC和GCC MinGW编译,以及在32位Linux下用GCC编译,可以得到如下的结果(从上到下分别为32位Windows下用MSVC编译、32位Windows下用GCC MinGW编译、32位Linux下用GCC编译):

    可以发现,MSVC下long double为8字节,而GCC编译后的程序不论在Linux下还是在Windows下都为12字节。不过仔细看可以发现,虽然GCC生成的程序占用了12字节,但其只用到了前10字节(后2字节不论怎样赋值其内容都不会发生改变),也就是说GCC的long double实际上是10字节(80bit)的。

    除此之外,还可以发现,当使用scanf("%lf")时,不论变量是什么类型的,都是按8字节存储的(即按double类型存储的),而使用scanf("%Lf"),则是按10字节存储的(即按long double类型存储的)。由于在MSVC下double = long double,所以不论怎么混用,结果都是正确的。而在Linux下,我们发现,当存储的long double为真正的long double时(使用scanf("%Lf")),只能使用%Lf输出结果,而long double内存储的内容为double时,只能使用输出double的格式化字符串输出。

    所以猜想在GCC MinGW下,可能就像在Linux下存储的double而强制输出long double那样会输出为0一样,存储的内容为double,而MSVC将其认定为long double输出,所以最终结果为0。

    【相关链接】

    1. 为什么printf()用%f输出double型,而scanf却用%lf呢?:http://book.51cto.com/art/200901/106880.htm
    2. printf and long double:http://stackoverflow.com/questions/4089174/printf-and-long-double
    3. gcc: printf and long double leads to wrong output:http://stackoverflow.com/questions/7134547/gcc-printf-and-long-double-leads-to-wrong-output-c-type-conversion-messes-u
    4. Long Double:http://msdn.microsoft.com/en-us/library/9cx8xs15.aspx
  • 相关阅读:
    第一次作业-准备篇
    个人作业——软件工程实践总结
    团队作业第二次—项目选题报告
    软工实践第三次作业(结对第二次作业)
    软工实践第二次作业(结对第一次作业)
    第一次作业-准备篇
    软工实践|个人作业——软件工程实践总结作业
    软工实践|团队作业第二次—项目选题报告
    软工实践|结对第二次—文献摘要热词统计及进阶需求
    软工实践|结对第一次—原型设计(文献摘要热词统计)
  • 原文地址:https://www.cnblogs.com/mayswind/p/3473353.html
Copyright © 2011-2022 走看看