根据C标准,折叠由反斜杠换行序列连接的多个物理行是比执行预处理器更早的转换阶段。
假设早期的#line
指令没有造成复杂情况,那么,__LINE__
宏的值是否反映了拼接这些线之前的物理线编号?这就是你会发现的,例如通过手动检查源,或者文本编辑器会将行#报告为什么,这可能是更有用的选择。或者它是否反映了拼接后的行#,根据标准中实际指定的翻译阶段顺序,这可能是预处理器实际看到的?
(如果我理解正确——我很可能没有理解——预处理器将无法知道给定的行是否是拼接的产物。(
编译器通过以C标准未指定的方式记住物理行号来实现__LINE__
。
C 2018 6.10.8.1 1 1告诉我们__LINE__
被替换为"当前源行的假定行数(在当前源文件中((一个整数常量(。"该规范很模糊,在严格遵守标准的情况下无法以有用的方式实现。
考虑这个代码:
#define Assert(test) do { if (!test) printf("Assertion on line %d failed.n", __LINE__); } while (0)
... Many lines of code follow, including some with line splicing.
Assert(condition);
... Many lines of code.
为了有用,此代码必须打印使用Assert
的物理行号。它需要是物理行号,以便用户可以在文本编辑器中定位该行,并且它需要是Assert
宏被替换的行,而不是定义的行,因为这是检测到问题的地方。GCC和Clang都这样做。
但是,这需要在宏替换期间提供行拼接之前的物理行号,而宏替换发生在线拼接之后。在C 2018 5.1.1.2.1中,标准规定了一个翻译模型,其中:
- 在第2阶段,"删除紧接着一个新行字符的反斜杠字符((的每个实例,拼接物理源行以形成逻辑源行,">
- 在第3阶段,"源文件被分解为预处理标记和空白字符",包括新行字符,但不包括在第2阶段删除的字符
- 在第4阶段,宏调用被扩展
因此,如果编译器在第4阶段替换__LINE__
宏,并且实际上只有预处理标记和剩余的空白字符,则它无法知道要提供的物理行号。
因此,编译器不能按照标准的翻译模型来实现。为了发挥作用,它必须将物理行号与每个预处理标记关联起来,这些标记可能是宏名称。每当宏被替换时,它必须传播关联的物理行号。然后,当__LINE__
令牌最终被替换时,编译器将具有相关的物理行号来替换它。
一个由换行符连接的多行宏被翻译成一行代码,因此它会为您提供宏出现的行。
例如,给定以下代码:
#define TEST do {
printf("line1=%dn" __LINE__);
printf("line2=%dn" __LINE__);
printf("line3=%dn" __LINE__);
} while (0)
int main(void)
{
TEST;
return 0;
}
通过gcc中的预处理器可以得到以下结果:
# 1 "x1.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "x1.c"
int main(void)
{
do { printf("line1=%dn" 9); printf("line2=%dn" 9); printf("line3=%dn" 9);} while (0);
return 0;
}