如果文件范围声明默认具有外部链接,为什么我们需要C中的’extern’关键字?

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

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

extern关键字主要用于变量声明。 当您转发声明一个函数时,该关键字是可选的。

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

 extern double xyz; // Declares xyz without defining it 

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

 double xyz; // Declares and defines xyz 

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

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

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

 extern char bogus[0x12345678] ; 

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

 char bogus[0x12345678] ="1"; 

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

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

  • 使用extern关键字,整个“虚假”的东西只是一个参考。 你对编译器说:“某处必须有一个char bogus[xxx] ,修复它!”
  • 如果没有extern关键字,你会说:“我需要空间来变量char bogus[xxx] ,给我那个空间!”

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

C99

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

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

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

令人困惑的是, extern不仅会改变链接,还会使对象声明成为定义与否。 这很重要,因为6.9 / 5“外部定义”表示只能有一个外部定义:

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

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

 translation-unit: external-declaration 

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

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

具有文件范围而没有初始化程序且没有存储类说明符或存储类说明符为静态的对象的标识符声明构成暂定定义。 如果翻译单元包含一个或多个标识符的暂定定义,并且翻译单元不包含该标识符的外部定义,那么行为就像翻译单元包含该标识符的文件范围声明一样,复合类型为翻译单元的结尾,初始化程序等于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有效的地方是块范围声明,请参阅: 本地和寄存器变量可以声明为extern吗?

如果对象声明有初始值设定项,则extern无效:

 extern int i = 0; 

等于

 int i = 0; 

两者都是定义。

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

您只能定义一次变量。

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

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

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