【问题标题】:Include loop - linker or compiler error?包括循环 - 链接器或编译器错误?
【发布时间】:2013-08-09 14:08:01
【问题描述】:

我想知道包含文件的无限循环是否会导致编译器问题或链接器问题。 我试过这个:

/* file : try.c */
#include "try1.c"
int main(void) {}

/* file : try1.c */
#include "try.c"
int a(void) { return 0; }

编译命令是:

gcc -Wall try.c -o try

这显然会导致很长的输出(像这样开始):

try.c:5:1: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
In file included from try.c:1:0,
                 from try1.c:1,
                 from try.c:1:
try1.c:4:1: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
In file included from try1.c:1:0,
                 from try.c:1:
try.c:5:1: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
In file included from try.c:1:0:
try1.c:4:1: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
try.c:5:1: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
In file included from try.c:2:0,
                 from try1.c:1,
                 from try.c:1,
                 from try1.c:1,
                 from try.c:1,
                 from try1.c:1,
                 from try.c:1,
                 from try1.c:1,
                 from try.c:1,
                 from try1.c:1,
                   .
                   .
                   etc...

嗯,显然这里有一个无限循环。但它什么时候发生?在编译过程或链接器一个?我想你会在编译过程中告诉我,因为它将在这里定义多个具有相同名称的函数(因为循环),但不是在链接器进程中发生联合文件的部分(然后有只有一个文件没有编译问题)?

谢谢!

【问题讨论】:

  • 这就是 .h 文件的用途。我们通常不会#include .c 文件。
  • 我知道。但是您也可以包含 .c 文件,我不想在此消息中添加 4 个文件(2 个 .h 和 2 个 .c)。只是想让事情变得更简短。

标签: c loops compiler-construction linker


【解决方案1】:

实际上,#include- 类型语句的扩展称为“预处理”步骤。我曾经认为这些步骤都是在发生任何“编译”之前作为一个单独的步骤处理的,但是@EricPostpischil 在 cmets 中指出(并举了一个例子来证明)这两件事 -预处理和编译 - 似乎同时发生(如源文件中的行顺序所示)。换句话说,# 命令(“预处理器指令”)的扩展是“在编译发生时”完成的。从这个意义上说,该错误是“编译”错误;但在我看来,说“#include 由预处理器处理”是有效的。当编译器处理“预处理器步骤”时,这条线是模糊的。绝对不是链接器导致问题 - 编译器会在您到达该步骤之前很久就放弃了。

作为一般性评论,将#include 一个.c 文件放在另一个文件中并不是一个好习惯——这就是链接器的用途。而且为了防止“递归包含”,你会经常在编译器附带的.h 文件中看到这样的结构:

#ifndef __MYINCLUDEFILE
#define __MYINCLUDEFILE
... put the body of the include file here
#endif

这确保了一个包含文件只会被包含一次,即使它是从多个地方调用的(第一次被包含时,__MYINCLUDEFILE 变量将被定义;下次被包含时,函数的整个主体被跳过)。一旦你对每个包含文件都这样做了,你陷入的那种“递归陷阱”就不会再发生了。

正如@wildplasser 所指出的,_NAME__NAME 的使用是为语言和实现保留的——我使用它作为示例是因为你会在发布的头文件中看到这样的结构用你的编译器。当您创建自己的 .h 文件时,您必须考虑另一种创建唯一标识符的约定。

【讨论】:

  • 几年前,预处理器被集成到 GCC 的编译器中(尽管它仍然可以作为一个单独的阶段显式调用)。正如 C 标准中所定义的,预处理是翻译的一部分。 C 实现可以单独实现预处理,也可以与翻译的其余部分统一实现。
  • @EricPostpischil - 我想这是语义问题。 “编译过程”(全部由 gcc 处理)由多个步骤组成:预处理、编译、组装、链接、加载。您只需一步调用它的事实并没有消除存在多个阶段的概念。我相信在第一行代码“编译”之前,所有# 指令都被解释/扩展等是事实。示例(非权威)链接:c-links.blogspot.com/2008/09/…。你有反例吗?
  • @Floris :带有一个或两个前导下划线的预处理器符号保留用于语言和实现。最好避开它们。
  • @Floris:这是准确的,但省略预处理与编译的讨论可能会更清楚,因为它与问题并不真正相关。无论何时进行预处理,递归包含文件(没有条件限制)都是错误的。
  • @Floris:恕我直言,事件的顺序没有定义。 (显然,在预处理器发出令牌之前,编译器无法看到令牌)。该规则可能应该被视为as if 规则:令牌流不应与隔离预处理程序生成的流不同。 “流水线”实现(例如 GCC)实际上可以在预处理器在所有(包含)文件上看到 EOF 之前将令牌输入编译器。 Abort-on-first-error 可能会中止管道,不允许它读取到 EOF。没必要。
【解决方案2】:

代码更改的不同阶段。

包含在预处理阶段扩展。因此,当您尝试进行无限循环时,它实际上只是在任何编译或链接发生之​​前的预处理阶段出错。

【讨论】:

    【解决方案3】:

    每个以“#”开头的“东西”都由预处理器处理。这就是为什么包含守卫也以“#”开头的原因。必须同时处理。

    【讨论】:

      【解决方案4】:

      这是已经失败的预处理。为了演示,使用

      预处理源
      gcc -E try.c
      

      然后看到它失败了。

      【讨论】:

        猜你喜欢
        • 2014-02-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-28
        • 2017-05-15
        • 1970-01-01
        相关资源
        最近更新 更多