【题外话】服务器
之前用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的问题才共同致使的问题。app
【文章索引】ide
把出错的一个提交的代码精简,而后就剩下以下的代码:ui
#include <cstdio> using namespace std; int main() { double n; scanf("%lf",&n); printf("%.0lf\n",n); return 0; }
在Linux下结果正常:this
结果在Windows下会出现以下图的结果:spa
接下来将上述程序的printf替换为cout,发现没有任何问题,判断是printf那行出现了问题。code
查找相关资料(如相关连接1)发现,不论输出float仍是double都应该使用printf("%f"),由于不论float仍是double都会做为double类型输出,确实之前没有注意到这个问题。因此在第一节给出的那个程序将“%lf”改成“%f”就正确了。但相关连接1中并无说明%lf指的是什么。component
不过若是尝试在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,因此一切都同样了吧)。
虽然上一节找出了第一节程序的问题,但是为何会出现这样的问题呢。
继续查找发现了相关连接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]\n", 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("\n"); 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("\n"); 27 28 scanf("%lf", &n1); 29 scanf("%Lf", &n2); 30 31 print_bytes("inputed_n1", n1); 32 print_bytes("inputed_n2", n2); 33 printf("\n"); 34 35 printf("type \t\t n1 \t\t\t\t n2\n"); 36 printf("%%f \t\t "); 37 printf("%f \t\t\t ", n1); 38 printf("%f\n", n2); 39 40 printf("%%lf \t\t "); 41 printf("%lf \t\t\t ", n1); 42 printf("%lf\n", n2); 43 44 printf("%%Lf \t\t "); 45 printf("%Lf \t\t\t ", n1); 46 printf("%Lf\n", 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。
【相关连接】