【C++】小心使用文件讀寫模式:回車('\r') 換行('\n')問題的一次糾結經歷


原來沒有仔細注意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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM