来自C11 5.1.1.2翻译阶段:
第2段:
[…]非空的源文件应以新行结束字符,其前面不应紧跟反斜杠字符,然后再进行任何此类拼接。
这意味着每个源文件都必须以换行符结尾。
示例:
#include <stdio.h>
int main()
{
printf("Hello worldn");
return 0;
}
上面的示例是使用clang prog.c -Wall -Wextra -std=gnu11 -pedantic
命令在Clang上编译的。编译器生成以下警告:
prog.c:7:16: warning: no newline at end of file [-Wnewline-eof]
}
^
这是可以的,因为在源文件的末尾没有换行符。
使用gcc prog.c -Wall -Wextra -std=gnu11 -pedantic
命令,我在GCC上编译了上述程序。GCC不会生成任何警告或错误。
那么,为什么GCC不生成任何警告或错误呢?
Clang Live演示
GCC实时演示
"shall"的含义由第4节第2点定义:
如果违反了出现在约束或运行时约束之外的"暂停"或"不暂停"要求,则行为未定义。
您引用的段落不在约束部分中。因此,如果源文件没有以尾随换行结束,则程序具有未定义的行为。
不需要对未定义的行为进行诊断。编译器可以自由地做任何事情。GCC开发人员可能已经决定让程序表现得像末尾有一行换行符一样,而不是用警告来打扰用户。
标准没有指定存储在磁盘上的物理字节与组成C程序的逻辑字符之间的任何特定关系。例如,如果源字符集仅使用代码0x00-0x7E,则一致性实现可能会说,任何行的第一个字符都将具有高位集,同时放弃任何其他类型的分隔符。这样的实现将被要求表现得就像每一行后面都跟着一个换行符一样,即使源文件中不会出现换行符(我不认为实现需要允许完全空白的源行以与包含单个空白字符的行不同的方式表示)。
如果一个实现指定文本文件由换行符分隔的多行组成,并且一个N行文件将包含N-1个换行符,那么它将被要求表现为文件中最后一个字节后面有一个换行符。但是,如果实现指定所有有效的文本文件都以换行符结尾,那么当给定一个没有换行符的无效文本文件时,它就没有义务了。
请注意,在某些实现中,#include
后面的第一行可能会连接到包含文件末尾的不完整行。这种行为虽然很古怪,但在某些情况下可能很有用,也可能是某些代码依赖了它。考虑到这种拼接,如果出乎意料的话,可能会产生基本上无限制的后果,将行为保留为"未定义"比试图对可能发生的事情进行分类更简单。
我认为gcc不合格。
无论如何解释5.1.1.2中的零件不是约束,gcc也允许这种不合格程序:
#include <stdio.h> /* no new line here */
这是一个预处理指令。C17 6.10§2将预处理指令定义为(粗体强调矿):
预处理指令由满足以下约束的预处理令牌序列组成:序列中的第一个令牌是
#
预处理令牌,该令牌(在翻译阶段4开始时)是源文件中的第一字符(可选地在不包含换行符的空白之后)或在包含至少一个换行符。序列中的最后一个标记是序列中第一个标记后面的第一个换行符换行符结束预处理指令,即使它发生在调用类似函数的宏中
此外6.10§5
约束
预处理指令中预处理令牌之间应出现的唯一空白字符(从引入#
预处理令牌之后到终止新行字符之前)。。。
6.10中的形式语法支持此文本:
控制行:
# include
pp令牌新行
新行:
新行字符
在这种情况下,需要一个实现来发布诊断消息:
5.1.1.3诊断
如果预处理翻译单元或翻译单元包含违反任何语法规则或约束的内容,则符合要求的实施应至少产生一条诊断消息(以实施定义的方式识别)。。。
即使在-pedantic
模式下,在没有任何诊断的情况下安静地允许#include <stdio.h> /* no new line here */
也会使gcc不符合,因为(可以说)存在违反6.10§5的约束,并且肯定存在违反6.10中列出的语法的语法规则。
clang编译器运行正常。
有一个问题,换行没有很好的标准定义,因为不同的系统有不同的换行约定。但你是对的。。。如果标准规定编译器在这种情况下必须发出警告,而gcc没有,那么它应该作为不符合性问题提交。
但我同意@suproct的回答,因为可以假设一个没有最终n
的文件可以安全地解释为一个正确分隔的文本文件,末尾没有行结尾。。。因为n
可以被解释为行分隔符而不是行结束符。如果这种解释有效,空文件将被解析为一个空行文件,编译器解析它没有问题,在这种情况下不应该发出警告。这同样适用于任何没有最终n
的文件,而一个完成了n
的文件应该被解释为一个n + 1
行的文件,带有一个额外的空行(恐怕这对里面的C代码的含义没有任何区别)
如果你去gcc项目投诉,这可能是你会得到的回应,所以要谨慎,但不要犹豫,去做。
顺便说一句,如果你试图给编译器提供最后一个\
字符(没有n
字符),编译器可以插入最后一行来模拟正确定义的文件,但预处理器必须以特殊形式处理,以防\
字符后面跟着一行新行。在这种情况下,编译器应该发出一些东西,因为您不能继续超过文件的最后一行。Clang没有说任何话,以防最后一行终止于\
(这是不符合项),让我们看看gcc。。。。(对不起,我现在无法访问gcc)