当 C 或 C++ 源文件中存在语法错误时,GCC 和 Clang 都会报告错误所在的确切行和列。例如,如果我们尝试编译此代码片段:
#include <stdio.h>
static void f(char *arg, int ignored) {
printf("%s\n", arg);
}
#define FUNCTION(x) f(x)
#define ARG "test"
int main() {
FUNCTION(ARG);
return 0;
}
—那么 GCC 将在
7:21
(宏体)处报告错误:
a.c: In function ‘main’:
a.c:7:21: error: too few arguments to function ‘f’
7 | #define FUNCTION(x) f(x)
| ^
a.c:12:9: note: in expansion of macro ‘FUNCTION’
12 | FUNCTION(ARG);
| ^~~~~~~~
a.c:3:13: note: declared here
3 | static void f(char *arg, int ignored) {
|
而 Clang 将指向宏调用站点 (
12:2
):
a.c:12:2: error: too few arguments to function call, expected 2, have 1
FUNCTION(ARG);
^~~~~~~~~~~~~
a.c:7:24: note: expanded from macro 'FUNCTION'
#define FUNCTION(x) f(x)
~ ^
a.c:3:13: note: 'f' declared here
static void f(char *arg, int ignored) {
不同编译器风格和版本的输出可能略有不同,但 GCC 和 Clang 或多或少是基本一致的。
现在,据我了解,每当调用gcc
或
clang++
等编译器驱动程序时,首先运行预处理器(
gcc -E
,或clang -E
,或cpp
),然后运行特定于语言的编译器前端处理已经预处理过的代码。
如果我们查看预处理后的代码,它确实会被大量注释,但原始文件中只有行号,而列号信息完全缺失。示例:
# 885 "/usr/include/stdio.h" 3 4
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 909 "/usr/include/stdio.h" 3 4
# 2 "a.c" 2
# 3 "a.c"
static void f(char *arg, int ignored) {
printf("%s\n", arg);
}
因此,看上面的内容,我们可以推断出
__uflow(...)
实际上是在 /usr/include/stdio.h:885
处声明的(这也是事实),并且 f(...)
的定义是从 a.c:3
开始的(这也是事实)。您还可以看到代码已重新格式化(缩进级别已更改)。 这里更详细地描述了预处理器输出的格式。
现在的问题是:考虑到预处理器输出显然包含不足的信息,编译器前端在向用户报告反馈时究竟如何在预处理代码和原始代码之间执行位置转换?
如果第 3 方工具(例如:静态分析器)只能访问原始代码和预处理代码(或者预处理代码的 AST),它如何能做到同样的事情?
可以使用Libclang的API吗?
原始文件中只有行号,列号信息完全丢失
行号是预处理步骤中丢失的唯一需要的信息。 当编译器尝试编译一行时,它本身会看到哪一列违反了某些内容,以防要打印出诊断信息。
因此,所使用的是预处理器留下的行面包屑+编译器自己关于该列的知识。