【題外話】服務器
之前用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。
【相關連接】