我想知道GCC怎么知道错误在哪里(在源代码中),而它的预处理器已经删除了注释?我用谷歌搜索了一下,但没找到.我会解释我的意思:

我有这样的C代码:

int main(void)
{
  return /* comment */ ) /* another comment */0;
}

‘)’字符(第24个字符)的位置存在语法错误.然后我通过GCC预处理器(gcc -E main.c)对其进行过滤,结果是:

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

好吧,我还是明白这些步骤的.预处理器已删除注释.但事情是这样的.由于删除了注释,现在语法错误位于第10个字符(而不是第24个字符)的位置.那么,它如何知道语法错误的确切位置呢?(如下面的输出所示)

main.c: In function ‘main’:
main.c:3:24: error: expected expression before ‘)’ token
    3 |   return /* comment */ ) /* another comment */0;
      |                        ^
main.c:3:24: error: expected statement before ‘)’ token

我发现#line标记有一些东西,但在预处理器输出中,没有这样的#line标记.

那么,到底有什么魔力呢?

推荐答案

the Stack Overflow question you link to中描述的# line预处理器指令是一个标准的C指令,用于设置编译器对当前源文件和行号的概念.它可用于通过预处理传递此信息,以便在预处理之后,编译器仍具有有关代码行来源的信息.它还可以被其他处理或生成源代码的工具使用,如yACC或lex,以提供有关在其输出中找到的代码在其输入文件中的来源的信息.

然而,GCC用自己的非标准机制来传达这一点和额外的信息.在您显示的预处理器输出中,非标准指令# 1 "main.c"实质上等同于标准指令# line 1 "main.c";两者都表示以下行来自文件"main.c"的第1行.

因此,原点线信息在您显示的预处理器输出中完全可见.

然而,GCC的表格允许additional information.在这些行中:

# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2

尾随的"1 3 4"表示这是新文件的开始(1),它来自系统头,因此某些警告应该被取消(3),并且它应该被视为包装在extern "C"块(4)中.尾部的"2"表示它在包含另一个文件后返回到先前的文件.(显然,包含"/usr/Include/stdc-prede.h"不会产生任何代码行,这可能是因为文件完全用#if…包装未激活的#endif对.)

…当它的预处理器删除了注释时?

当GCC预处理删除注释时,它会保留换行符,因此行距保持不变.例如,在处理输入时:

abc
/* Multiple-line comment
   consisting of
   three lines */
xyz

预处理器产生:

abc



xyz

因此输出的行数与输入的行数相同.因此,行号在预处理后保持正确.但是,列信息不是以这种方式传递的.请考虑以下代码:

int foo/*comment*/(nuts);

当我用Clang 11.0.0编译它时,错误消息是:

x.c:1:20: error: a parameter list without types is only allowed in a function
      definition
int foo/*comment*/(nuts);
                   ^

正如我们所看到的,编译器知道错误从第20列开始.但是,当我使用clang -E x.c >x.i对其进行预处理,然后编译生成的x.i文件时,错误消息是:

x.c:1:10: error: a parameter list without types is only allowed in a function
      definition
int foo (nuts);
         ^

这表明列信息不包含在预处理器输出中.因此,我们可以得出结论,当编译器同时进行预处理和编译时,它在内部维护这些信息.在现代的GCC和克隆中,预处理是集成到编译中的;它实际上不是单独的处理步骤.

将预处理集成到编译中的另一种方法是编译以下代码:

int foo(nuts);
#error "Stop processing."

如果在编译之前将预处理作为单独的步骤,则#error指令将导致打印一条消息,并导致进程退出.但是,当使用Clang对其进行编译时,编译器首先打印一条关于int foo(nuts)行的消息,然后打印#error行的消息.这表明预处理与编译是交织在一起的;预处理是与编译一起逐行完成的,因此编译器在处理完前int foo(nuts);行之前不会达到#error指令.

C++相关问答推荐

错误:在.h程序中重新定义 struct

如何将FileFilter添加到FileDialog GTK 4

C/C++中的状态库

以c格式打印时间戳

如何将不同长度的位转换成字节数组?

无法用C++编译我的单元测试

如何在IF语句中正确使用0.0

将uintptr_t添加到指针是否对称?

struct -未知大小

强制转换变量以在 struct 中蚕食

为 struct 中的数组动态分配内存时出错

在C中包装两个数组?

对于STM32微控制器,全局偏移表.get和.Got.plt必须为零初始化

为什么二进制文件的大小不会随着静态数据的大小而增加?

如何使用calloc和snprintf

Dlsym()的手册页解决方法仍然容易出错?

C中的数组下标和指针算法给出了不同的结果

WSASocket在哪里定义?

memcmp 是否保证按顺序比较字节?

如何在 C 中的 Postgres 函数的表中 for 循环