为什么这个C程序可以编译通过?

9

我是C语言的初学者,正在尝试写一些C代码。我写了如下的一段C代码:

#include <stdio.h>
int main()
{
    printf("hello world\n"); 
    \
    return 0;
}

尽管我有意使用了 \,但C编译器没有报错。在C语言中,这个符号有什么用途呢?
编辑:
甚至下面的代码也能正常运行:
"\n";

"\n";它是一个没有任何效果的语句。编译器完全会忽略它并生成警告。尝试使用gcc -Wall标志进行编译,以启用编译警告。 - dAm2K
3
您的主要问题已经多次得到解答。关于"\n";的部分,C程序是一系列语句的集合。字面值(例如3"\n"或者"hamburgers")是有效的语句,即使它们不会执行任何操作。 - jpm
5个回答

11
“\n”序列会在翻译过程的非常早期(第二阶段)被去除。在字符串连接出现之前,它是创建长字符串字面量的方法,也是将宏扩展到多行的方法。
请参见C99标准的§5.1.1.2翻译阶段。
翻译如下:
翻译的语法规则优先级由以下阶段指定。5)
  1. 如果必要,将物理源文件多字节字符以实现定义的方式映射到源字符集(引入换行符作为行末标志)。三字符序列将被替换为相应的单字符内部表示。
  2. 删除紧随新行字符后面的反斜杠字符(\),将物理源行拼接成逻辑源行。任何物理源行上的最后一个反斜杠只有在成为这样的拼接的一部分时才有资格。在进行这种拼接之前,不为空的源文件必须以新行字符结尾,该新行字符不得立即在任何这样的拼接之前由反斜杠字符前导。
  3. 将源文件分解为预处理令牌6)和一系列空白字符序列(包括注释)。源文件不得以部分预处理令牌或部分注释结束。每个注释都将替换为一个空格字符。保留换行符。是否保留除换行符以外的每个非空空白字符序列取决于实现定义。
  4. 执行预处理指令,展开宏调用并执行_Pragma一元运算符表达式。如果通过令牌串联(6.10.3.3)产生与通用字符名称语法匹配的字符序列,则行为是未定义的。 #include预处理指令导致从阶段1到阶段4递归地处理命名的标头或源文件。然后删除所有预处理指令。
  5. 将字符常量和字符串字面值中的每个源字符集成员和转义序列转换为执行字符集的相应成员;如果没有相应的成员,则将其转换为实现定义的成员,而不是空(宽)字符。7)
  6. 相邻的字符串字面值标记将被连接。
  7. 分隔令牌的空格字符不再重要。将每个预处理令牌转换为一个令牌。所得到的令牌将被作为翻译单元进行语法和语义分析并翻译。
  8. 解析所有外部对象和函数引用。链接库组件以满足当前翻译中未定义的函数和对象的外部引用。所有此类翻译器输出都被收集到一个程序映像中,该映像包含在其执行环境中执行所需的信息。
5) 实现应当表现出这些单独的阶段发生的方式,即使在实践中许多阶段通常被折叠在一起。 6) 如6.4所述,将源文件的字符分成预处理令牌的过程是上下文相关的。例如,请参见在#include预处理指令中处理<的方式。 7) 实现不需要将所有非对应源字符转换为相同的执行字符。
如果在你的转义反斜杠之后有空格或其他字符,那么会导致编译错误。我们可以看出你没有在它后面添加任何内容,因为你没有编译错误。
另一个关于你问题的部分是:
"\n";

这是一段英文文本,大意为:“这很不同。它是一个简单的表达式,没有副作用,因此对程序没有影响。优化器将完全丢弃它。当你写下这样的代码时:”。
i = 1;

你有一个值被丢弃的表达式;它被评估是为了修改i的副作用。
有时,你会发现这样的代码:
*ptr++;

编译器会警告你表达式的结果被丢弃了;这个表达式可以简化为:
ptr++;

并且在程序中会达到相同的效果。

在第一阶段,“行尾指示符”是实现定义的。尾随空格可以被视为行尾指示符的一部分,因此在第二阶段,即使原始输入中有空格,反斜杠也会紧接着换行符后面。 - Jerry Coffin
@JerryCoffin:有传言称,在使用“打孔卡片”类型记录的IBM大型机上,80列(72列?)图像上存在隐含的尾随空格,并且可能会被消除。在这个距离上很难确定这些空格是否“真实存在”。但这大概是我所知道的唯一可能存在问题的背景了。 - Jonathan Leffler
总的来说,大型机通常都是面向Hollerith卡片的,这意味着尾随空格相当普遍(在我工作的Control Data机器上绝对是如此)。虽然这不是我所想的,但我认为它可能与此有关。我(模糊地)记得的是在MS-DOS或CP/M上,而不是在大型机上,但我记不起更多了。 - Jerry Coffin
@JonathanLeffler:+1 很好的答案。你在回答中说,*ptr++; 这个语句将被丢弃,我们应该使用 ptr++;。但是为什么这个语句 *ptr++; 应该被丢弃呢? - Ant's
*ptr++; 中的 * 被丢弃了。它在增量之前计算指针,获取引用值,然后丢弃该值(但增加指针)。该语句的目的是增加指针,但某人在没有思考的情况下添加了星号。这与表达式 *tgt++ = *src++; 完全不同,其中 * 明确需要两次,甚至 (*ptr)++ 做了不同的工作,即递增指针所指向的内容。上下文是一个完整的表达式,仅由 *ptr++ 组成,在其中 * 是多余的。 - Jonathan Leffler
显示剩余2条评论

4
< p > 当< code >\紧接着一个换行符时,它会被预处理程序消耗,并将下一行物理行连接到当前逻辑行。这对于编写长的预处理指令非常重要,因为它们必须全部位于一个逻辑行上:< /p >
#define SHORT very log macro \
   consisting of lots and \
   lots of preprocessor \
   tokens

如果你删除反斜杠-换行符序列,则不再正确。来自Unix文化的一些其他语言具有类似的反斜杠行继续语法:从Bourne shell派生的POSIX shell语言,以及Makefile。

$ this is \
one shell command
"\n"是一个原始表达式,用于形成表达式语句。在C语言中,表达式可以用作语句,这种用法经常被利用。例如,您的printf调用就是一个表达式语句。 printf("hello world\n")是一个后缀表达式,它调用一个函数并获取返回值。由于您将此表达式用作语句,因此返回值被丢弃了。 printf的返回值表示打印了多少个字符,或者是否成功,所以通过丢弃它,您的程序变得无视printf调用是否实际工作。
由于表达式语句的值被丢弃,如果这样的语句也没有副作用,则是一个无用的语句,什么也不做(例如您的"\n")。但是这些无用的表达式语句并不是错误的。如果您在编译器命令行中添加警告选项,您可能会收到警告消息,例如“无效语句”之类的内容。

0

反斜杠\会被C预处理器解释。它保护其后面的字符(在您的情况下是换行符)。


1
实际上,它与“保护”相反;它确保删除反斜杠和换行符。 - Jonathan Leffler

0

反斜杠只是转义下一个字符。在这种情况下,可能是一个换行符(CR)字符。非常合理。


在C语言中,转义字符只能在字符串内部使用。 - Matteo Italia
实际上,预处理器只能转义行尾字符。对于误导之处,我们深感抱歉。上面dAm2k的回答更加准确。 - rainecc
@MatteoItalia:翻译的第二阶段是:“删除每个反斜杠字符(\)后紧跟换行符的实例,将物理源行拼接成逻辑源行。”(§5.1.1.2/1.1)。 - Jerry Coffin
1
@JerryCoffin:这不叫转义,在标准中也不是这样说的(标准只是指“转义序列”仅用于字符常量/字符串字面量),在普通语言中也不是这样说的(我从未听说过转义字符被删除)。在这种情况下,\是换行符,而不是转义字符。 - Matteo Italia
我的重点更多是关于正在发生的事情,而不是人们可能选择如何称呼它。 - Jerry Coffin

0

反斜杠加上其后的内容是转义序列;"\n"代表换行符(打印一个新行)。另一个重要的是"\t",表示制表符。


转换序列仅出现在字符串和字符字面量中。问题中的反斜杠不在字符串或字符字面量内。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接