16

AFAIK,文件范围内的任何变量或函数声明都默认具有外部链接。static意思是“它有内部联系”,extern--“它可能在别处定义”,而不是“它有外部联系”。

如果是这样,为什么我们需要extern关键字?换句话说,int foo;extern int foo;(文件范围)有什么区别?

4

4 回答 4

32

extern关键字主要用于变量声明。前向声明函数时,关键字是可选的。

该关键字使编译器可以区分全局变量的前向声明和变量的定义

extern double xyz; // Declares xyz without defining it

如果您自己保留此声明,然后xyz在您的代码中使用,您将在链接阶段触发“未定义符号”错误。

double xyz; // Declares and defines xyz

如果将此声明保存在头文件中并从多个 C/C++ 文件中使用它,则会在链接阶段触发“多重定义”错误。

解决方案是extern在标头中使用,而不是在一个 C 或 C++ 文件中使用 extern。

于 2012-11-08T19:23:54.917 回答
4

作为说明,编译以下程序:(使用 cc -c program.c 或等效程序)

extern char bogus[0x12345678] ;

现在删除“extern”关键字,然后再次编译:

char bogus[0x12345678] ="1";

在这两个对象上运行 objdump(或等效的)。

你会发现没有extern关键字空间实际上是分配的。

  • 使用extern关键字,整个“伪造”的东西只是一个参考。你是在对编译器说:“一定有一个char bogus[xxx]地方,把它修好!”
  • 如果没有 extern 关键字,您会说:“我需要一个变量的空间char bogus[xxx],给我那个空间!”

令人困惑的是,对象的实际内存分配被推迟到链接时:编译器只是向对象添加一条记录,通知链接器应该(或不应该)分配一个对象。在所有情况下,编译器至少会添加对象的名称(和大小),以便链接器/加载器可以修复它。

于 2012-11-09T00:47:30.793 回答
3

C99标准

我将通过引用和解释C99 N1256 草案来重复其他人所说的话。

首先,我确认您的断言,即外部链接是文件范围 6.2.2/5“标识符链接”的默认设置:

如果对象标识符的声明具有文件范围且没有存储类说明符,则其链接是外部的。

令人困惑的是,extern它不仅会改变链接,而且还会影响对象声明是否是定义。这很重要,因为 6.9/5“外部定义”说只能有一个外部定义:

外部定义是一个外部声明,它也是函数(内联定义除外)或对象的定义。如果用外部链接声明的标识符在表达式中使用(而不是作为结果为整数常量的 sizeof 运算符的操作数的一部分),则在整个程序的某处,该标识符应有一个外部定义;否则,不得超过一个。

其中“外部定义”由语法片段定义:

translation-unit:
  external-declaration

所以它意味着一个“文件范围”的顶级声明。

然后 6.9.2/2“外部对象定义”说(对象表示“变量的数据”):

具有文件范围的对象的标识符声明没有初始化程序,并且没有存储类说明符或具有存储类说明符 static,构成暂定定义。如果翻译单元包含一个或多个标识符的暂定定义,并且翻译单元不包含该标识符的外部定义,则行为与翻译单元包含该标识符的文件范围声明完全相同,复合类型为翻译单元的末尾,初始化器等于 0。

所以:

extern int i;

不是一个定义,因为它确实一个存储类说明符:.extern

然而:

int i;

没有存储类说明符,因此它是一个暂定定义。如果没有更多的外部声明,那么我们可以隐式i添加初始化器等于 0 := 0

int i = 0;

因此,如果我们int i;在不同的文件中有多个,则链接器理论上应该会出现多个定义。

但是,GCC 4.8 不符合要求,并且作为扩展允许int i;跨不同文件的多个文件,如下所述:https ://stackoverflow.com/a/3692486/895245 。

这是在 ELF 中用一个通用符号实现的,这个扩展非常常见,以至于在 J.5.11/5 的标准中提到了通用扩展 > 多个外部定义:

一个对象的标识符可能有多个外部定义,无论是否显式使用关键字 extern;如果定义不一致,或者不止一个被初始化,则行为未定义(6.9.2)。

extern另一个有影响的地方是块范围声明,请参阅: Can local and register variables are declared extern?

如果对象声明有初始化器,extern则无效:

extern int i = 0;

等于

int i = 0;

两者都是定义。

对于函数,extern似乎没有效果:extern 关键字对 C 函数的影响,因为没有类似的暂定定义概念。

于 2015-06-19T06:35:12.540 回答
1

您只能定义一个变量一次。

如果多个文件使用相同的变量,则必须在每个文件中冗余声明该变量。如果你做一个简单的“int foo;” 你会得到一个重复的定义错误。使用“extern”来避免重复定义错误。Extern 就像对编译器说“嘿,这个变量存在但不要创建它。它是在其他地方定义的”。

C 中的构建过程并不“智能”。它不会搜索所有文件以查看变量是否存在。您必须明确指出该变量存在于当前文件中,但同时避免创建两次。

即使在同一个文件中,构建过程也不是很聪明。它从上到下,如果在使用点下方定义函数名称,它将无法识别,因此您必须在更高的位置声明它。

于 2012-11-08T21:52:11.193 回答