AFAIK,文件范围内的任何变量或函数声明都默认具有外部链接。static
意思是“它有内部联系”,extern
--“它可能在别处定义”,而不是“它有外部联系”。
如果是这样,为什么我们需要extern
关键字?换句话说,int foo;
和extern int foo;
(文件范围)有什么区别?
AFAIK,文件范围内的任何变量或函数声明都默认具有外部链接。static
意思是“它有内部联系”,extern
--“它可能在别处定义”,而不是“它有外部联系”。
如果是这样,为什么我们需要extern
关键字?换句话说,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]
地方,把它修好!”char bogus[xxx]
,给我那个空间!”令人困惑的是,对象的实际内存分配被推迟到链接时:编译器只是向对象添加一条记录,通知链接器应该(或不应该)分配一个对象。在所有情况下,编译器至少会添加对象的名称(和大小),以便链接器/加载器可以修复它。
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 函数的影响,因为没有类似的暂定定义概念。
您只能定义一个变量一次。
如果多个文件使用相同的变量,则必须在每个文件中冗余声明该变量。如果你做一个简单的“int foo;” 你会得到一个重复的定义错误。使用“extern”来避免重复定义错误。Extern 就像对编译器说“嘿,这个变量存在但不要创建它。它是在其他地方定义的”。
C 中的构建过程并不“智能”。它不会搜索所有文件以查看变量是否存在。您必须明确指出该变量存在于当前文件中,但同时避免创建两次。
即使在同一个文件中,构建过程也不是很聪明。它从上到下,如果在使用点下方定义函数名称,它将无法识别,因此您必须在更高的位置声明它。