回车换行详解

  • 原始的 回车(carriage return)和换行(line feed)是两个动作
    • \n:表示换到下一行
    • \r :表示移动到行首
  • 现在 在计算机文件中,编程语言中
    • \n:换行并移动到行首
    • \r:移动到行首

1. 机械打字机

“车(carriage)”是指纸车,带着纸一起左右移动的模块。

  • 回车:
    在早期的机械打字机上,打印头是固定的,只能通过左右移动纸车在一行上打字。当开始打第一个字之前,要把纸车拉到最右边,上紧弹簧,随着打字,弹簧把纸车拉回去。每当打完一行后,纸车就完全收回去了,所以叫回车。
    • 换行:
      打字机边上有个"把手",往下扳动一下,纸会上移一行。在后来的电传打字机甚至今天的终端中都沿用了这些叫法。
      YouTube上找了两个视频,一个是打印头固定的,一个是不固定的。可以看下,更容易理解:

2. 电传打字机

​ 在后来的电传打字机中,通常打印头可以左右移动,这样可以节省空间,同时也就没有纸车这个概念了,不过车这个说法依然留下来了,只不过是指打印头,回车即将打印头回到行首,换行依然是将纸上移一行。

ASR 33 Teletype Information

  • 回车(CR)和换行(LF)的顺序问题
    wiki记载,通常的电传打字机都是用CR+LF这个顺序,典型的就是上面那台Teletype Model 33 ASR

    把这一步骤拆分为回车和换行,是因为 打印头不能在一个字符的时间内从最右侧移动到下一行开始 。在CR之后打印的一个字符通常会变成污迹,由于打印头在移动到行首过程中,下个字符同时打印了。
    所以解决办法就是将换行做成两个字符:CR将打印头移动到行首,LF进纸。事实上,通常还要发送额外的字符(额外的CR或者NUL,这些字符将被忽略)以保证打印头有足够的时间移动到行首。

3. 计算机中打印字符

  • 以下参考网上很多说法
  • 等到早期的计算机发明时,很自然的这两个概念被拿了过来。但是由于那时的存储设备非常昂贵,一些人认为在每行的结尾加两个字符用于换行,实在是极大的浪费,于是各个厂商在这一点上便出现了分歧。

  • windows: 由于一些早期的微型计算机还没有用于隐藏底层硬件细节的设备驱动,所以它们直接沿用了打字机的惯例,使用不带NULCRLF作为一个EOL。而CP/M为了和这些微型计算机使用同一个终端,也采用了这种设计。所以它的克隆MS-DOS也同样使用CRLF,由于Windows又是基于MS-DOS,为保持兼容性,所以就导致了如今的Windows是采用CRLF作为EOL,即\r\n(或0x0D 0x0A)。

  • Unix: 而Multics在被设计之时就非常认真的考虑了这一问题,设计者们觉得只需一个字符便完全足够来表示EOL,这样更加合理。那么选择CR还是LF呢?本来由于那时的键盘上都有一个Return键,所以可能更好的选择是CR。但当时考虑到CR可以用来重写一行,以完成如粗体删除线等效果,所以他们选择了稍稍难以理解的LF。然后自己设计了一个设备驱动程序来将LF转换为各种打字机所需要的EOL,这个方案非常完美,当然除了LF稍微奇怪一些。随后一脉相承的UnixLinux们都继承了这个选择,于是你在这些操作系统上可以发现每一行的结尾是一个LF,即\n(或0x0A)

  • Mac: 系统的选择就更加复杂一些。Apple在设计Mac OS时,他们采用了一个最容易理解的选择:CR,即\r(或0x0D)。但这只维持到Mac OS 9,后一个版本的Mac OSX基于Mach-BSD内核,所以此后版本的Mac OSX在每行的结尾存储了与Linux一样的LF,即\n(或0x0A)。

  • 还有很多其它的操作系统采用更加不同的方案,这也导致了混乱的产生,

    因为Linux和Mac OSX上使用的是LF,而Windows上使用的是CRLF,那么Linux和Mac OSX上创建的文件在Windows上打开时,由于每一行的结尾只有一个LF,但Windows只认识CRLF,所以便不会有逻辑上的换行处理,故所有的文字被挤到了一行。
    反过来,如果Windows上的文件在Linux和Mac OSX上打开时,仅需LF便可换行,那么每一行的结尾便多了一个CR,对应的ASCII码为^M

  • 而git的安装向导会特意有一个这样的提醒页面也出于此,因为一个项目可能有多个开发者,每个开发者可能使用的是不同的系统,那么开发者checkout代码时,如果不做换行符的转换,有可能就会出现只有一行或者行尾多了^M的情况。当然,如果你有一个可以识别多种EOL的现代文本编辑器,那么不做转换也无妨(notepad不行)。

4. 编程语言中的回车换行

  • C语言,C系语言

为了避免在这些不同的实现中挣扎,高级语言给我们带来了福音,它们各自使用了统一的方式来处理EOL。在C语言中,你一定知道在字符串中如果要增加一个换行符的话,直接用\n即可,比如:

1
printf("This is the first line! \nThis is a new line!");

上面的输出将是:

1
2
This is the first line!
This is a new line!

为什么C语言选择了\n而不是\r?这绝非偶然。熟悉C语言历史的朋友可能知道当初C语言是Dennis Ritchie为开发Unix而设计,所以它沿用了Unix上EOL的惯例便很容易理解了。而我们知道Unix使用的LF的ASCII码为0x0A,转义符为\n,因此C语言中也使用\n作为换行。

Text Mode VS Binary Mode

但是,千万别简单的认为上面的\n最终写到文件中就一定是其ASCII码0x0A,或者文件中的0x0A被读到内存中就是其转义符\n。这取决于你打开文件的方式。在C语言中,在对文件进行读取操作之前,都需要先打开文件,可以使用下面的函数:

1
2
#inlcude <stdio.h>
FILE *fopen(const char *path, const char *mode);

注意看第二个参数mode,它是一个字符指针,通常可以为读®,写(w),追加(a)或者读写(r+, w+, a+),仅指定这些参数时,文件将被当成是文本文件来操作,即Text Mode,而如果在这些参数之外再指定一个额外的b时,文件便会被当成是二进制文件,即Binary Mode。这两种模式的区别在哪里呢?这里稍稍有些复杂,因为它们在不同的平台上表现不同。

Windows平台

对于Windows平台,因为其使用CRLF来表示EOL,故对于Text Mode需要做一定的转换才能够与C语言保持一致。接下来的两个图可以给出最为直观的描述。

先看二者对于读操作的区别:

读操作

Text Mode下,C语言会尝试去“理解”这些回车与换行,它会知道LFCRLF都可能是EOL,所以不管文件中是LF还是CRLF,被读进内存时都会变成LF。而Binary Mode下,C语言不会做任何的“理解”,所以这些字符在文件中什么样,读到内存中依然那样。

接下来是写操作的区别:

写操作

Text Mode下,内存中的每一个LF写入文件中时都会变为CRLF,当然,如果不幸内存中为CRLF,以此种模式写入到文件中时就会变成CRCRLF注意:这里不是CRLF。原因我想大概是如果你认为内存中的数据是文本,那么它一定是以LF作为EOLCR也一定是你有意而为之,是个有意义的字符,所以它并不会处理。)。而Binary Mode下,内存中的内容会被原封不动的写到文件中。

所以为了保证一致性,一定需要注意配套使用读和写,即读和写采用同一种模式打开文件

Linux和Mac OSX平台

因为Linux和Mac OSX平台与C语言对待EOL的方式完全一致,所以Text ModeBinary Mode在这些平台下没有任何区别,可以参考fopenman page。实际上,所有遵循POSIX的平台都忽略了b这个参数。

虽说在这些平台上处理EOL非常简单,但是如果你的程序需要移植到其它非POSIX平台上时,请务必正确对待b参数。

POSIX平台 介绍

因为Linux和Mac OSX平台与C语言对待EOL的方式完全一致,所以Text ModeBinary Mode在这些平台下没有任何区别,可以参考fopenman page。实际上,所有遵循POSIX的平台都忽略了b这个参数。

  • POSIX:可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX )

  • 历史:

    974年,贝尔实验室正式对外发布Unix。因为涉及到反垄断等各种原因,加上早期的Unix不够完善,于是贝尔实验室以慷慨的条件向学校提供源代码,所以Unix在大专院校里获得了很多支持并得以持续发展。

    于是出现了好些独立开发的与Unix基本兼容但又不完全兼容的OS,通称Unix-like OS.包括:

    1. 美国加州大学伯克利分校的Unix4.xBSD(Berkeley Software Distribution)。
    2. 贝尔实验室发布的自己的版本,称为System V Unix。
    3. 其他厂商的版本,比如Sun Microsystems的Solaris系统,则是从这些原始的BSD和System V版本中衍生而来。

    20世纪80年代中期,Unix厂商试图通过加入新的、往往不兼容的特性来使它们的程序与众不同。局面非常混乱,麻烦也就随之而来了。

    为了提高兼容性和应用程序的可移植性,阻止这种趋势, IEEE(电气和电子工程师协会)开始努力标准化Unix的开发,后来由 Richard Stallman命名为“Posix”。

这套标准涵盖了很多方面,比如Unix系统调用的C语言接口、shell程序和工具、线程及网络编程。

  • 支持POSIX

    Unix和Linux

    苹果的操作系统也是Unix-based的

    Windows从WinNT开始就有兼容POSIX的考虑。这是因为当年在要求严格的领域,Unix地位比Windows高。为了把Unix用户拉到Windows阵营,被迫支持POSIX。

    现在Win10对 Linux/POSIX 支持好,则是因为Linux已经统治了廉价服务器市场。为了提高Windows的竞争力搞的。

参考

  1. 回车键 - 维基百科,自由的百科全书 (wikipedia.org)
  2. Newline - Wikipedia
  3. 回车与换行的一些历史 - 这破站 (racecoder.com)
  4. 知无涯之回车换行的故事 (feihu.me)
  5. posix是什么都不知道,还好意思说你懂Linux? - 知乎 (zhihu.com)