为什么要定义“ extern_”而不是使用“ extern”?

问题描述

我试图制作一个简单的词法分析器,我发现了这个github页面https://github.com/DoctorWkt/acwj/tree/master/01_Scanner 在他的源代码中,我看到了:

data.h:

...
#ifndef extern_
 #define extern_ extern
#endif

extern_ int Line;
extern_ int Putback;
extern_ FILE *Infile;
...

main.c:

...
#define extern_
#include "data.h"
#undef extern_
...

如果我仅使用不起作用的extern关键字,但它与extern_一起使用,那有什么区别?

解决方法

extern_宏的直接功能是控制extern关键字是否出现在变量声明中。原则上,它也可以用来替代其他关键字或添加限定词,但这似乎是偶然的。

在这一点上,重要的是要指出问题中显示的代码不代表所引用的GitHub项目中的代码。在GitHub上,变量声明出现在标头中,而不出现在main.c中。这与为什么使用这种功能直接相关。

然后,特别要考虑整个项目中执行的两种选择之间的区别:

  • 项目#include中大多数C源文件的头文件都没有定义宏extern_。在这种情况下,标头本身会定义宏extern_以扩展为关键字extern,从而在这些翻译单元中产生以下声明:

     extern int Line;
     extern int Putback;
     extern FILE *Infile;
    
  • 文件main.c是特殊的。它定义了宏extern_以扩展为空,并且在该定义的范围内包括了标头。然后,标头依赖于提供的宏定义,这样, 仅在该翻译单元中 ,结果声明就是

      int Line;
      int Putback;
      FILE *Infile;
    

前者与后者的区别在于前者是纯声明,而后者是临时定义。这很重要,因为必须使用一个转换单元来精确定义程序访问的每个具有外部链接的对象。包含给定对象的暂定定义的翻译单元肯定包含该对象的定义(比听起来听起来要复杂一些)。

总的来说,结果是同一标头可以以两种不同的角色使用:一方面,默认情况下,声明外部对象的标识符,以便可以从其他翻译单元访问它们;以及另一只手将它们定义在一个选定的翻译单元中,以便它们实际存在于程序中。

如果我仅使用不起作用的extern关键字,但它与extern_一起使用,那有什么区别?

在不提供初始化程序的情况下声明变量extern意味着可以在程序中的某个位置定义该变量,但本身不会 导致要定义的变量。如果在程序中的任何地方都没有以其他任何方式声明给定变量,则所有这些承诺都将无法实现,并且所产生的行为也是不确定的。通常,这将以链接故障的形式表现出来。

只要我提到初始化程序,就应该谨慎地注意将初始化程序写入标头中的声明不是可行的解决方案,因为那时每个包含标头的翻译单元都将具有所有变量的定义,而必须在整个程序中,每个定义不得超过一个。该行为将再次是未定义的。在实践中有更好的机会接受该程序,但这仍然是错误的。

最后,我注意到整个带有extern_宏的业务有些黑,因此不应视为常规业务。规范的做法是让标头简单地声明所有变量extern,并在选定的C源文件中对每个变量进行单独的定义(不一定是所有相同的文件)。示例:


data.h

extern int Line;
extern int Putback;
extern FILE *Infile;

main.c

#include "data.h"

int Line /* optionally with an initializer here */;
int Putback  /* optionally with an initializer here */;
FILE *Infile  /* optionally with an initializer here */;

// ...

other.c

#include "data.h"

// no (additional) declarations or definitions of the variables declared in data.h

,

#define extern_告诉预处理器在看到extern_时一无所有。

因此,在这种情况下,extern_毫无意义。

但是我打赌其他文件中它们不使用#define extern_。在那种情况下,头文件中的#define extern_ extern被激活,因为#ifndef extern_为真(extern_尚未定义)。这告诉预处理器将extern_替换为extern。因此,在一个文件中定义的变量没有extern,在其他所有文件中都有extern。 (为什么这样做有用?如果您知道extern的工作原理,那么您会知道为什么)