反斜杠换行符组合如何影响 C 预处理器的 __LINE__ 宏的值?
How does a backslash-newline combo affect the value of the C preprocessor's __LINE__ macro?
根据 C 标准,折叠由反斜杠换行符序列连接的多条物理行是比执行预处理器更早的翻译阶段。
假设没有由于较早的 #line
指令而引起的并发症,那么 __LINE__
宏的值是否反映了 在 这些行之前的物理行号拼接了吗?那就是你会发现的,例如通过手动检查源代码或文本编辑器将 # 行报告为什么,这可能是更有用的替代方法。或者它是否反映了拼接之后的行#,这大概是预处理器在给定标准中实际指定的翻译阶段顺序的情况下实际看到的内容?
(如果我理解正确——我很可能没有理解——预处理器将无法知道给定的行是否是拼接的产物。)
由换行符连接的多行宏被翻译成单行代码,因此它为您提供宏所在的行。
例如,给定以下代码:
#define TEST do {\
printf("line1=%d\n" __LINE__);\
printf("line2=%d\n" __LINE__);\
printf("line3=%d\n" __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=%d\n" 9); printf("line2=%d\n" 9); printf("line3=%d\n" 9);} while (0);
return 0;
}
编译器通过以 C 标准未指定的方式记住物理行号来实现 __LINE__
。
C 2018 6.10.8.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.
为了有用,此代码必须打印使用 [=13=] 的物理行号。它需要是物理行号,以便用户可以在文本编辑器中找到该行,并且它需要是 Assert
宏被替换的行,未定义,因为那是问题所在检测到。 GCC 和 Clang 都这样做。
但是,这需要在宏替换时提供before行拼接的物理行号,发生after行拼接。在C 2018 5.1.1.2 1中,标准规定了一个翻译模型,其中:
- 在第 2 阶段,“每个反斜杠字符 () 后面紧跟着一个换行符的实例都被删除,拼接物理源代码行以形成逻辑源代码行,”并且,
- 在第 3 阶段,“源文件被分解为预处理标记和 white-space 字符”,包括换行符,但不包括在第 2 阶段删除的字符,
- 在第 4 阶段,扩展了宏调用。
因此,如果编译器在第 4 阶段替换了一个 __LINE__
宏并且实际上只有预处理标记和剩余的白色-space 字符,它无法知道要提供的物理行号。
因此,编译器不能按照标准的翻译模型按字面意思实现。为了有用,它必须将物理行号与每个可能是宏名称的预处理标记相关联。每当替换宏时,它都必须传播关联的物理行号。然后,当 __LINE__
标记最终被替换时,编译器将有关联的物理行号来替换它。
根据 C 标准,折叠由反斜杠换行符序列连接的多条物理行是比执行预处理器更早的翻译阶段。
假设没有由于较早的 #line
指令而引起的并发症,那么 __LINE__
宏的值是否反映了 在 这些行之前的物理行号拼接了吗?那就是你会发现的,例如通过手动检查源代码或文本编辑器将 # 行报告为什么,这可能是更有用的替代方法。或者它是否反映了拼接之后的行#,这大概是预处理器在给定标准中实际指定的翻译阶段顺序的情况下实际看到的内容?
(如果我理解正确——我很可能没有理解——预处理器将无法知道给定的行是否是拼接的产物。)
由换行符连接的多行宏被翻译成单行代码,因此它为您提供宏所在的行。
例如,给定以下代码:
#define TEST do {\
printf("line1=%d\n" __LINE__);\
printf("line2=%d\n" __LINE__);\
printf("line3=%d\n" __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=%d\n" 9); printf("line2=%d\n" 9); printf("line3=%d\n" 9);} while (0);
return 0;
}
编译器通过以 C 标准未指定的方式记住物理行号来实现 __LINE__
。
C 2018 6.10.8.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.
为了有用,此代码必须打印使用 [=13=] 的物理行号。它需要是物理行号,以便用户可以在文本编辑器中找到该行,并且它需要是 Assert
宏被替换的行,未定义,因为那是问题所在检测到。 GCC 和 Clang 都这样做。
但是,这需要在宏替换时提供before行拼接的物理行号,发生after行拼接。在C 2018 5.1.1.2 1中,标准规定了一个翻译模型,其中:
- 在第 2 阶段,“每个反斜杠字符 () 后面紧跟着一个换行符的实例都被删除,拼接物理源代码行以形成逻辑源代码行,”并且,
- 在第 3 阶段,“源文件被分解为预处理标记和 white-space 字符”,包括换行符,但不包括在第 2 阶段删除的字符,
- 在第 4 阶段,扩展了宏调用。
因此,如果编译器在第 4 阶段替换了一个 __LINE__
宏并且实际上只有预处理标记和剩余的白色-space 字符,它无法知道要提供的物理行号。
因此,编译器不能按照标准的翻译模型按字面意思实现。为了有用,它必须将物理行号与每个可能是宏名称的预处理标记相关联。每当替换宏时,它都必须传播关联的物理行号。然后,当 __LINE__
标记最终被替换时,编译器将有关联的物理行号来替换它。