您的位置:首页 > 其它

由“warning: no newline at end of file”引申而来

2012-04-25 15:21 567 查看
公司有一个服务器软件,是跨平台的,一般我们在Windows下用VC6这个古老的IDE编辑,同时编译出Windows下的可执行文件。编译Linux平台下的可执行文件,则启动VMWare,通过Share Folder功能将目录共享到虚拟机中的Linux系统,运行make进行编译。

这一套流程几乎是下意识的过程了,编译过程中虚拟机的屏幕滚动很快,偶尔能看到几个warning指示,也不是太在意,今天留意了一下,发现除了几个涉及到数据类型强制转换的告警,大部分就是这个提示了:“warning: no newline at end of file”。如图。



为什么会有这个问题?google了一下,产生的原因是源文件的最后一行没有回车符造成的。看了一下源文件,果然如此。



用十六进制编辑器打开这个文件,也很容易发现,结尾就是“0x7D”,也就是“}”的ASCII代码。

解决的办法很简单: 在最后一行(106行)敲一个回车,多出一个行号为107的空行, 然后保存,再进入虚拟机重新编译,OK,没有warning了。这时十六进制方式再看文件,结尾已经是“ 0x7D 0x0D 0x0A ”。但是原因是什么呢?继续google。

先谈谈回车、换行的历史,也挺有意思的,记录在这里。

<回车>:C/C++语言里为 \r; ASCII码为0D;符号表示为CR,Carriage Return

<换行>:C/C++语言里为 \n; ASCII码为0A;符号表示为LF,Line Feed

如果用过机械打字机,就知道回车和换行的区别了。换行就是把滚筒卷一格,不改变水平位置。回车就是把水平位置复位,不卷动滚筒。

当我们在Word等字处理软件中按下回车键(Enter或Return)时,我们已经习惯于看到光标移动到下一行,我们忽视了一个已经默认的动作——光标回到最左侧。写文章自然不需要考虑太多,但是当我们进行C程序设计时,\r、\n问题困惑着我们,看看下面的这段小历史,我们就能明白\r、\n的区别了。
最早,打字机上的打字位置是固定的,回车兼换行的扳手用于将承载纸滚筒的机架(Carriage)移到最右边,以便令印字位置对准一行的开头,同时顺便转动滚筒,换至下一行。后来,当打字机的滚筒不再横向移动,改由承载印字头的字车(Carriage)移回到本行的起始位置。
在计算机出现之前,有一种叫做电传打字机的玩意,每秒钟可以打10个字符。但是它有一个问题,就是打完一行换行的时候,要用去0.2秒,正好可以打两个字符。要是在这0.2秒里面,又有新的字符传过来,那么这些字符将丢失。
于是,研制人员想了个办法解决这个问题,就是在每行后面加两个表示结束的字符。一个叫做“回车”(Carriage Return, CR),告诉打字机把打印头定位在左边界;另一个叫做“换行”(Line Feed, LF),告诉打字机把纸向上移一行(打印头相对向下移一行)。这就是“换行”和“回车”的来历,从它们的英语名字上也可以看出一二。
之后计算机发明了,这两个概念也就被迁移到计算机上。那时,存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以,于是出现了分歧。
在Unix或Unix相容系统(GNU/Linux, Mac OS X)中,每行结尾只有“<换行>”(LF),即“\n”;在老旧的Mac系统里面,每行结尾是“<回车>”(CR),即“\r”;Windows和大部分非Unix系统延续了传统的英文打字机模式,每行仍以“<换行><回车>”(CR+LF)结尾,即“\n\r”。相对专业一些的文本编辑器(例如Notepad++)通过设置(视图 > 显示符号 > 显示行尾符)就可以显示行尾符了,另外在下方的状态栏中也会显示档案格式状态,并且通过操作(编辑 > 档案格式转换)可以完成转换。

上面的引用有个简单错误,其实Windows是以“<回车><换行>”(CR+LF)结尾,即“\r\n”。可以看上面的十六进制显示也是确实如此。这个历史遗留问题,造成在不同的平台相互读入、保存文件时会出现混乱。我们可以看到,用windows上的记事本打开Linux产生的文本文件,由于只有0A,这个0A被显示成一个小黑块。所有的文本都没换行显示。但是在linux上用vi/vim打开windows产生的文本文件,则很好的处理了0D 0A,正常显示了换行,但如果是0A 0D或者只有0D,0D会被显示成“ ^M
”。

我用C写了一个简单程序,如果向文件中写入"\r\n",那么实际写入的是0x0d 0x0d 0x0a;如果写入"\n",那么写入的就是0x0d 0x0a。

(04.26补充:这个简单例子是用文本方式打开文件,又google了一下相关资料,原来这就是为什么操作文件时会有“二进制”和“文本”两种模式的原因。在文本模式下,写入时,遇到'\n'就转换成平台相关的换行符(对windows就是"\r\n");读入时,遇到平台相关的换行符(比如windows下的"\r\n"),转换成'\n';)

这个东西还是非常混乱的啊。我简单总结了一下:在windows下,如果以二进制方式写入,那么老老实实0x0d 0x0a,如果以文本方式写入,简单写"\n"就好了。现代的很多编辑软件也考虑了这个问题了,有的编辑软件,提供设置界面接口,当按下enter时,可选择插入的是(回车\r 或 换行\n 或 回车\r+换行\n )。这么看来,严格说,并不是操作系统有这个问题,而是操作系统上运行的编辑软件对回车换行的处理不同。

跨平台读写文件看来要注意这个,进行Linux的配置文件修改时尤其要注意这个,否则容易出现莫名其妙的问题。

有点偏题,继续说明为什么在windows下编辑,在linux下编译会出现这个告警。

CSDN博客上有篇文章我个人觉得讲得很不错,见这里

今天偶然看到了这篇" 一个细节引发的思考http://blog.iyi.cn/hily/archives/2007/09/post_19.html”,不仅解除了我平时的困惑之一,也引发了我的一点思考。
知其然还要知其所以然,我觉得作为CS的学生,这是应该具备的基本态度。
首先看一下GNU网站上对该问题给出的解释(http://blog.csdn.net/gcc.gnu.org/ml/gcc/2003-11/msg01568.html):
“
The C language standard says
A source file that is not empty shall end in a new-line character, which shall not be immediately
preceded by a backslash character.
Since this is a "shall" clause, we must emit a diagnostic message for a violation of this rule.
This is in section 2.1.1.2 of the ANSI C 1989 standard. Section 5.1.1.2 of the ISO C 1999 standard
(and probably also the ISO C 1990 standard).”
那么,标准为何特意这样规定呢,总得有个合适的理由吧?抱着这样的心态,我翻到了标准的对应段落
“  5.1.1.2 Translation phases
[...]         2.  Each instance of a backslash character (\) immediately
followed by a new-line character is deleted,  splicing
physical  source  lines  to form logical source lines.
Only the last backslash on any  physical  source  line
shall  be eligible for being part of such a splice.  A
source file that is not empty shall end in a  new-line
character,  which shall not be immediately preceded by
a backslash character before any such  splicing  takes
place."
明白了!我的理解是标准这样规定是基于C代码中常见的一种书写习惯,即对于过长、无法在一行内容纳下的字符串,通常用以下形式书写:
char str[]= " It's a lonely lonely \
night.";
这样,按照标准的规定,编译器在读取源代码时,读到上面第一行的'\'和紧跟其后的newline,就知道该physical line并不能构成一个完成的logical line,所以会继续读入下一行源代码,将两行代码拼接起来,组成一个语义完整的logical line。
为了保持一致性,标准规定源代码的每个physical line必须以newline结尾;对于非空的源代码,必须以newline作为结束符,并且newline之前不能是转义字符'\',因为这样的话就意味着会有拼接行为然而该源文件却没有更多的physical line了。
或者,还可以换一个角度,假设一个.h文件缺少了newline作为结束符,那么另一个源文件里#include了这个头文件,岂不是会出现意料之外的“拼接"?
题外话,现在多数编辑器(至少我手中的VIM)都会自动在行尾添加newline,所以一般情况下是不会碰到“warning: no newline at end of file.” 这样的警告的 :)

这么看来,这个告警“warning: no newline at end of file ”,其实是想提示开发者,注意啦,有可能你这个文件被其他文件#include,会有错误:包含文件的第一行有可能直接拼接在被包含文件的最后一行行尾,如果恰好被包含文件的行尾是“//”会怎么样?!

不过新的C++11标准,忽略了这个问题,强制编译器在文件尾加入newline字符。见这里,有较详细的讨论。

(04.26补充:为什么这里用“newline字符”这个术语,wiki百科的解释,这是因为“新行newline” 字符,不同的操作系统有不同方式,并不一定就是回车。)

换行,是在计算机领域中,换行(newline)或称为Line break或end-of-line(EOL)字符是一种加在文字最后位置的特殊字符,在换行字符的下一个字符将会出现在下一行,实际上换行字符根据不同的硬件平台或操作系统平台会有不同的编码方式。
应用软件以及操作系统对于换行字符的表示方式:
以ASCII为基础的或相容的字符集使用分别LF(Line feed, 0Ah)或CR(Carriage Return, 0Dh)或CR+LF;下面列出各系统换行字符编码的列表
LF:在Unix或Unix相容系统(GNU/Linux, AIX, Xenix, Mac OS X, ...)、BeOS、Amiga、RISC OS
CR+LF:微软视窗操作系统(Microsoft Windows)、大部分非Unix的系统
CR:Apple II家族,Mac OS至版本9

C++11是这么说的,没有以newline字符结尾,或者以newline字符结尾但该字符前有反斜线( '\' backslash)的非空代码文件,都会再自动添加一个newline字符。

A source file that is not empty and that does not end in a new-line character, or that ends in a new-line character
immediately preceded by a backslash character before any such splicing takes place, shall be processed as if an
additional new-line character were appended to the file (C++11 §2.2/1).

C11上没有看到,不知道有没有同时改变。

作为一个编程好习惯,还是老老实实在文件尾,用大力气按Enter吧。^_^
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: