在 C 中的宏之外使用反斜杠换行符

Dan*_*Dan 6 c c-preprocessor

int main(){\
 int a = 5;\
 return a;\
}
Run Code Online (Sandbox Code Playgroud)

以上编译正常。我假设 C 预处理器在编译之前删除了反斜杠?

gcc -E 的输出

int main(){
 int a = 5;
 return a;}
Run Code Online (Sandbox Code Playgroud)

似乎并非所有\n(新行)字符都像使用宏一样被删除,它只是主要删除了反斜杠。

我已经在多行宏中看到过这种用法,例如:

#define TEST(in)\
 int a = in;    \
 int b = 6;

int main(){
 TEST(5)
 return 0;
}
Run Code Online (Sandbox Code Playgroud)

gcc -E 的输出:

int main(){
 int a = 5; int b = 6;
 return 0;
}
Run Code Online (Sandbox Code Playgroud)

预处理将删除反斜杠以及\n上面示例中的字符,但为什么它没有删除我第一个示例中的所有换行符?

ric*_*ici 3

“拼接”——反斜杠换行序列——在预处理器处理程序文本之前被删除。至少理论上是这样,请记住 C 标准实际上并未定义称为“预处理器”的过程。

\n

它定义的是将程序文本转换为可解析的标记流,然后将其转换为可执行文件的过程。该过程由八个翻译阶段组成,编译器必须产生与一次执行一个阶段(每个阶段都将前一阶段的输出作为输入)所产生的结果相同的结果。(大多数输入和输出都是标记流,而不是字符串。因此,使用该-E标志运行时 GCC 生成的输出与标准中的任何内容都不对应,从而允许 GCC 基本上生成它认为方便的任何输出。或者它的作者认为你会觉得很方便。)

\n

“as if”子句意味着特定的编译器可以组合阶段或分段执行它们,只要它不改变结果。因此,您实际上只能将过程视为算法的抽象描述。尽管如此,理解还是很有用的。全文可在标准的\xc2\xa75.1.1.2中找到。

\n

对阶段的高度浓缩和注释描述,其细节不完整且有些不精确,希望它比标准中的语言更容易理解。但一定要读原文。

\n
    \n
  1. 删除三字母(现已弃用,因此如果您不知道它们是什么,请不要担心),并在必要时将程序文本转换为编译器所需的任何字符编码。

    \n
  2. \n
  3. 移除接头。所有反斜杠换行序列都将从程序文本中删除,不留下任何内容。(好吧,这就是理论。实际上,大多数编译器仍然知道每一位文本的原始源代码行号。但此信息仅用于生成诊断。)

    \n
  4. \n
  5. 将文本拆分为标记和空白序列,并将所有注释替换为单个空格字符。

    \n
  6. \n
  7. “执行预处理指令,扩展宏调用,并_Pragma执行一元运算符表达式”。这与定义预处理器的标准非常接近,因此可以合理地说“预处理器”是第 4 阶段的执行。#include指令是预处理器指令,处理 include 指令首先将包含的文件传递给阶段 1-3,然后将其插入令牌流以进行进一步预处理。

    \n
  8. \n
  9. 将字符和字符串文字中的所有转义序列替换为执行期间将使用的实际字符(可能是宽字符)。

    \n
  10. \n
  11. 连接相邻的字符串文字。

    \n
  12. \n
  13. 删除所有空格,只留下标记。将预处理标记转换为语法标记。解析生成的令牌流并将其转换为“翻译单元”。或者,换句话说,将程序编译成目标文件(尽管这比标准中的语言更具体)。

    \n
  14. \n
  15. 将所有翻译单元和必要的库模块组合成一个可执行映像。通俗地说,这是链接阶段,您可以将结果交给操作系统来执行。

    \n
  16. \n
\n

这就是标准的要求。但现实世界的编译器还会做很多其他事情,比如生成或多或少可读的错误消息;以可能使其执行速度更快和/或占用更少空间的方式重新排列代码;将调试信息插入可执行文件;并生成用户要求的任何附加分析和报告(这些都不是标准化的)。例如,这包括-E和/或-S输出。编译器做这些事情是为了给你一个好处,它们有助于理解你的程序的编译方式。但您不应该认真地对待它们,因为编译过程的正式结果是实际的可执行文件。

\n

大多数编译工具链也可以生成库,因此并不是所有程序都立即完全处理为可执行映像。但这是唯一标准化的结果。尽管该标准引用了库,特别是标准库,但它并没有对库如何存在做出任何假设。

\n

标准库(和头文件)甚至不必存在于文件系统中;编译器能够识别它们的名称并做出适当的响应就足够了。标准库必须实现的一些内容无法用可移植的 C 语言编写,因此标准库源代码(如果存在)很可能并不全部采用标准 C 程序的形式。标准库头可能包含由编译器接受特殊处理的构造,因此不能被其他编译器使用或直接复制到您的程序中。

\n

这一切看起来似乎太过遥远,但其目的是让 C 实现能够在极其有限的处理器上运行,包括根本没有任何外部存储的处理器。(针对嵌入式系统仍然很常见,这些系统可能会丢失许多您通常认为理所当然的东西。)而且,总的来说,多年来它为我们提供了很好的服务。

\n