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';

四、知识体系图

下面这张图帮你理清文本模式与二进制模式的核心逻辑:

文本模式 vs 二进制模式 核心逻辑 fopen() 打开模式 文本模式 ("r", "w", "a") 二进制模式 ("rb", "wb", "ab") Windows: 自动转换 \n ↔ \r\n Linux: 无转换,与二进制模式相同 所有平台: 原样读写,不做任何转换 适合: 图片、压缩包、可执行文件等

五、实践建议

  1. 跨平台项目,统一用二进制模式。我自己写的代码,只要是跨平台的,一律"wb""rb"。这样不管在哪个系统上,行为都是一致的。
  2. 如果必须用文本模式,记得在读取后清理行尾的\r。上面那个strcspn的技巧,你可以直接抄走。
  3. 不要假设换行符的长度。比如fseek()定位到文件末尾,用ftell()获取文件大小,在文本模式下这个值可能不准确(因为转换会导致实际字节数变化)。
  4. 网络传输的文件,建议统一使用二进制模式。我曾经遇到过一个HTTP下载程序,因为文本模式把图片里的0x0A给转换了,结果图片显示不全。排查了好久才发现是这个问题。

再次强调:文本模式是给“纯文本”文件用的。如果你不确定文件内容是否包含非文本数据,就用二进制模式。安全第一。

好了,关于文本模式和二进制模式,就聊这么多。记住一句话:跨平台,用二进制;纯文本,注意清理\r。下次你在Linux上看到^M符号时,就知道是怎么回事了。


公众号:蓝海资料掘金营,微信deep3321