17、文本模式与二进制模式:Windows下的\r\n转换、Linux下的无差别、文本模式的跨平台问题
文件读写,看起来很简单对吧?
但如果你在Windows上写了一个文件,拿到Linux上读,或者反过来——嗯,你可能会遇到一些“惊喜”。
这个问题的根源,就在于文本模式和二进制模式的区别。
我个人习惯在跨平台项目里,一律用二进制模式。为什么?往下看你就明白了。
一、换行符的“历史恩怨”
先说个背景知识。早期的打字机,换行需要两个动作:
- 回车(CR,\r):把打印头移回行首。
- 换行(LF,\n):把纸往上滚一行。
后来计算机继承了这套逻辑,但不同操作系统做了不同的取舍:
| 操作系统 | 换行符 | 说明 |
|---|---|---|
| Windows | \r\n(CR+LF) |
保留了两个字符,兼容老传统 |
| Linux / macOS | \n(LF) |
只用一个字符,简洁 |
| 经典 Mac OS | \r(CR) |
现在已经很少见了 |
你想想看,如果我在Windows上写了一个文本文件,每行结尾都是\r\n,然后把这个文件拿到Linux上打开——Linux不认识\r,于是每行末尾会多出一个^M符号。反过来,Linux的文件拿到Windows上,所有行会连成一片,因为Windows找不到\r\n组合。
核心矛盾:不同系统对“换行”的编码不同,导致文本文件在跨平台时出现格式错乱。
二、C语言中的文本模式 vs 二进制模式
C标准库的fopen()函数,第二个参数决定了文件的打开模式:
"r"、"w"、"a"—— 文本模式"rb"、"wb"、"ab"—— 二进制模式
区别在哪?说白了就是:文本模式下,系统会自动帮你做换行符转换;二进制模式下,系统什么都不做,原样读写。
2.1 Windows下的“隐形翻译官”
在Windows上,如果你用文本模式写文件:
FILE *fp = fopen("test.txt", "w");
fputs("Hello\nWorld", fp);
fclose(fp);
你写的是\n,但实际写入磁盘的是\r\n。反过来,读文件时,磁盘上的\r\n会被自动转换成\n。
听起来挺贴心对吧?但我在项目中遇到过一个问题:用文本模式读写二进制文件。比如图片、压缩包、可执行文件。这些文件里可能恰好包含0x0D(即\r)或0x0A(即\n)这样的字节。文本模式一转换,数据就坏了。
警告:永远不要用文本模式打开二进制文件!否则数据会被“篡改”。
2.2 Linux下的“无差别对待”
Linux下呢?文本模式和二进制模式没有任何区别。
因为Linux的换行符就是\n,不需要转换。所以fopen("test.txt", "w")和fopen("test.txt", "wb")效果完全一样。
我曾经在Linux上写代码,习惯性地用了"wb"模式,同事问我为什么加个b?我说“跨平台考虑”。他笑了笑说:“在Linux上,加不加都一样。” 嗯,确实如此。
小提示:为了代码的可移植性,我建议你在所有平台上都显式指定二进制模式(加b),除非你明确需要文本模式的换行转换。
三、文本模式的跨平台问题
假设你写了一个跨平台程序,需要在Windows上生成一个配置文件,然后在Linux上读取。如果你用文本模式写:
// Windows上运行
FILE *fp = fopen("config.txt", "w");
fprintf(fp, "name=Alice\nage=30\n");
fclose(fp);
实际写入的文件内容是:
name=Alice\r\nage=30\r\n
把这个文件拷贝到Linux上,用文本模式读:
// Linux上运行
FILE *fp = fopen("config.txt", "r");
char line[256];
while (fgets(line, sizeof(line), fp)) {
// 每行末尾会多一个 '\r'
// 比如 "name=Alice\r\n" 被读成 "name=Alice\r"
}
fclose(fp);
看到了吗?fgets()读到的字符串末尾会多一个\r。如果你不做处理,字符串比较就会失败。
避坑指南:我曾经在解析一个跨平台配置文件时,花了整整半天排查为什么字符串总是匹配不上。最后发现是\r在作怪。从那以后,我处理文本行时都会加一句:
// 去掉行尾的 '\r' 和 '\n'
line[strcspn(line, "\r\n")] = '\0';
四、知识体系图
下面这张图帮你理清文本模式与二进制模式的核心逻辑:
五、实践建议
- 跨平台项目,统一用二进制模式。我自己写的代码,只要是跨平台的,一律
"wb"和"rb"。这样不管在哪个系统上,行为都是一致的。 - 如果必须用文本模式,记得在读取后清理行尾的
\r。上面那个strcspn的技巧,你可以直接抄走。 - 不要假设换行符的长度。比如
fseek()定位到文件末尾,用ftell()获取文件大小,在文本模式下这个值可能不准确(因为转换会导致实际字节数变化)。 - 网络传输的文件,建议统一使用二进制模式。我曾经遇到过一个HTTP下载程序,因为文本模式把图片里的
0x0A给转换了,结果图片显示不全。排查了好久才发现是这个问题。
再次强调:文本模式是给“纯文本”文件用的。如果你不确定文件内容是否包含非文本数据,就用二进制模式。安全第一。
好了,关于文本模式和二进制模式,就聊这么多。记住一句话:跨平台,用二进制;纯文本,注意清理\r。下次你在Linux上看到^M符号时,就知道是怎么回事了。
公众号:蓝海资料掘金营,微信deep3321