原來沒有仔細注意C++讀寫文件的二進制模式和文本模式,這次吃了大虧。(平台:windows VS2012)
BUG出現:
寫了一個程序A,生成一個文本文件F保存在本地,然后用程序B讀取此文件計算MD5值。
將該文件上傳到服務器,再用程序B將文件從服務器上下載下來計算MD5值,神奇的發現兩次計算的MD5值不一樣,文件被誰改了??
排除問題:
1.首先對比了生成文件F和上傳到服務器的文件,發現文件復制過程無差錯,是同一個文件。
2.用程序B下載文件F后,保存在本地,發現文件與原文件F不一致,對比二進制發現每行多了一個\r。
3.懷疑服務器傳輸前對文件格式進行了更改,用wireshark抓包,發現文件內容與服務器上文件一致。那么這個多出來的\r從何而來呢,行結尾變成了\r\r\n。
4.查看文件F,行結尾是\r\n,而我記得當初生成文件的時候是以\n作為換行符的,糾結一番后想起來了文件讀寫的模式,只記得是文本與二進制的區別,沒有想起來換行符的問題。
5.幾經糾結,查閱C++ primer plus后恍然大悟,都是默認使用文本模式讀寫文件惹的禍:windows下,文本模式會將\n輸出成\r\n,讀取時也會將\r\n變成一個\n;所以開始程序B讀取文件F並且計算MD5時,是以\n來計算的。然而當從服務器上下載下來時,文件是以\r\n作為行結尾的,直接計算MD5會導致值不一樣。而將下載下來的文件保存時,由於仍然使用的文本模式,將\r\n變成了\r\r\n,導致了當初匪夷所思的結果。
總結:
這BUG從出現到調查各方面的原因排除花費了大量的時間,說到底還是因為基礎不扎實,這里講《C++ primer plus》的關鍵一段話抄下來作為提醒。
“使用二進制文件模式時,程序將數據從內存傳遞給文件(反之亦然)時,將不會發生任何隱藏的轉換,而默認的文本模式並非如此。例如,對於Windows文本文件,他們使用兩個字符的組合吧(回車和換行)表示換行符;Mac文本文件使用回車表示換行符;而UNIX和Linux文件使用換行來表示換行符。C++是從UNIX系統上發展而來的,因此也使用換行來表示換行符。為增加可移植性,Windows C++程序在寫文本模式文件時,自動將C++換行符轉換為回車和換行;Mac C++程序在寫文件時,將換行符轉換為回車。在讀取文本文件時,這些程序將本地換行符轉換為C++模式。對於二進制數據,文本格式會引起問題,因為double值中間的字節可能與換行符的ASCII碼有相同的位模式。另外,在文件末尾的檢測方式也有區別。因此以二進制格式保存數據時,應使用二進制文件模式。”
后續驗證:
后來寫了一個小程序驗證了一下所知,不懂的話可以復制下來跑一下,注意是Windows平台,生成的文件可以用wxHexEditor來查看以二進制形式查看。另外再說一點題外的,不用語言的字符串類型編碼可能會不同,例如JavaScript里是UTF-16,而C++默認的是ANSI,下載下來同一個文件計算MD5值的話可能會有問題。
1 #include <iostream> 2 #include <fstream> 3 #include <string> 4 using namespace std; 5 int main() 6 { 7 8 string str1 = "hello!\n"; 9 ofstream fout("file1");//默認文本模式 10 fout << str1; 11 fout.close(); 12 13 ifstream fin("file1"); 14 char ch = 0; 15 string temp; 16 if (fin) { 17 while (fin.get(ch)) 18 temp += ch; 19 cout << "讀入file1長度:"<<temp.length() <<endl; 20 fin.close(); 21 } 22 23 string temp2; 24 fin.open("file1", ios::binary);//以\n作為換行 25 getline(fin, temp2); 26 cout << "二進制模式getline讀入file1的長度(結尾包含了\\r):" << temp2.length() << endl; 27 fin.close(); 28 29 ofstream fout2("file2"); 30 fout2 << "hello!\r\n"; 31 fout2.close(); 32 33 string temp3; 34 fin.open("file2"); 35 if (fin) { 36 getline(fin, temp3); 37 cout << "文本模式getline讀入file2的長度(同樣多了一個\\r):" << temp2.length() << endl; 38 } 39 40 return 0; 41 }
by ascii0x03, 2015.9.25
相關文章:http://blog.csdn.net/xiaofei2010/article/details/8458605/