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

Using Backslash-Newline outside of a macro in C

int main(){\
 int a = 5;\
 return a;\
}

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

output of gcc -E:

int main(){
 int a = 5;
 return a;}

似乎并非所有 \n(新行)字符都被删除,类似于宏的处理方式,主要是删除了反斜杠。

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

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

int main(){
 TEST(5)
 return 0;
}

output of gcc -E:

int main(){
 int a = 5; int b = 6;
 return 0;
}

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

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

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

“as if”子句意味着特定的编译器可以组合阶段或分段执行它们,只要它不改变结果。所以你真的只能把过程看成是一个算法的抽象描述。尽管如此,了解它还是很有用的。全文见标准§5.1.1.2

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

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

  2. 删除拼接。所有反斜杠换行符序列都从程序文本中简单地删除,不留下任何东西。 (好的,这就是理论。在实践中,大多数编译器仍然知道每一位文本的原始源行号。但此信息仅用于生成诊断。)

  3. 将文本拆分为标记和白色space序列,并用单个space字符替换所有注释。

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

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

  6. 连接相邻的字符串文字。

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

  8. 将所有翻译单元和必要的库模块合并到一个可执行映像中。非正式地,这是链接阶段,结果是您可以交给操作系统执行的东西。

这是标准要求的。但是现实世界的编译器会做 很多 其他事情,比如生成或多或少可读的错误消息;以可能使其执行速度更快的方式重新排列代码 and/or 占用更少 space;将调试信息插入可执行文件;并生成用户要求的任何其他分析和报告(none 其中已标准化)。例如,这包括 -E and/or -S 输出。编译器做这些事是为了帮助你,它们有助于理解你的程序是如何编译的。但是你不应该把它们当回事,因为编译过程的官方结果是实际的可执行文件。

大多数编译工具链也可以生成库,所以并不是所有的程序都立即完全处理成可执行映像。但这是唯一标准化的结果。尽管该标准指的是库,尤其是标准库,但它并未对库的产生方式做出任何假设。

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

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

C 标准没有像 gcc -E 那样指定如何对文本输出执行预处理。编译器尝试生成可以反馈给编译器以生成相同程序的文本输出。只要生成相同的令牌,白色 space 细节在很大程度上与此无关。在您的示例中,gcc 输出可读文本,其中转义的换行符被输出为换行符,只要它们被白色包围 space。这个可选的:转义的换行符实际上应该在早期阶段从输入中完全删除,允许重新构造在不同行上断开的标记。

这是一个病态的例子:

#inc\
lude\
 <st\
dio.\
h>
int \
main\
() {\
retu\
rn 0\
; }

关于定义跨越多行并带有转义换行符的宏,它们的扩展由 C 标准精确描述:白色序列 space 和注释必须替换为单个 space.

这是一个说明性的例子:

int main(){\
    int a = 5;\
    return a;\
}

#define TEST() int main(){\
    int a = 5;\
    return a;\
}

#define XSTR(x) #x
#define STR(x) XSTR(x)

TEST()

STR(TEST())

gcc -E的输出:

# 1 "prepmain.c"
# 1 "<built-in>"                                                                                                                  # 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "prepmain.c"                                                                                                                  int main(){
    int a = 5;
    return a;}                                                                                                                    # 14 "prepmain.c"
int main(){ int a = 5; return a;}                                                                                                                                                                                                                                   "int main(){ int a = 5; return a;}"

gcc -E -P 的输出:

int main(){ int a = 5; return a;}
int main(){ int a = 5; return a;}
"int main(){ int a = 5; return a;}"