extern关键字

全局变量处理不当会出现重复定义的原因是,普通全局变量是跨文件可见的,而C/C++仅允许一个单元有相同符号。常量全局默认仅当前可见,就不容易出现重复定义的问题。

一、extern的定义及其含义

extern在中文里代表外部的、对外的。该关键字常常被用在全局变量、函数或者模板声明中,表示该符号具有外部链接属性。

1.1 翻译单元

翻译单元是由源文件及头文件(间接或者直接包含)组成,每一个翻译单元都将会被独立编译,当编译完成后,由连接器合并这些独立的已编译的翻译单元,所有违背仅一次定义规则(One Definition Rule,ODR)的都将抛出连接错误。个人觉得,编译单元在编译时不需要知道所有的实现,链接才会最终查找具体实现。

翻译单元规则不允许我们重复定义同一对象,否则将会出现链接错误。比如两个cpp文件都定义了int a=444;编译时,不是所有编译器都会报告发现这样的错误,但是在链接时这些错误总会被暴露出来。

1.2 全局变量

为什么我们需要用到全局变量?一个最常见的用途就是实现源文件间数据共享,使用全局变量要特别小心:

  • 不正确的修改导致其他使用者数据脏读
  • 不应修改的却被使用者修改

对于后者,我们常常会采用const关键字进行避免,前者是数据共享必然会出现的问题,这是程序设计上应该多加考虑的。如果可能,尽可以避免使用全局变量,因为不好管理而且会出现命名空间污染。

1.3 编译器默认全局链接属性

对于普通的变量,默认全部文件可见;对于常量变量,默认当前文件可见。注意,编译器认为定义在全局的“声明”是定义:

//a.cpp
int a;//是定义,而不是声明
void fun()
{
}

于是区分,这个全局变量是声明还是定义的“重任”就交给了extern关键字。

//a.cpp
extern int a;//是声明,而不是定义
void fun()
{
}

除此,extern关键字还具备改变常量对象的链接属性的功能。

const int pi=3.14159265;//默认当前文件可见
extern const int pi=3.14159265;//所有文件均可见
1.4 extern关键字含义

一个extern关键字的含义取决于具体的语境:

  • 全局作用域中的非常量声明,extern用于指示该常量在其他翻译单元(translation unit)定义,而不是在此文件中定义。(普通全局仅在当前可见)
  • 全局作用域中的常量声明,表示这个文件不局限于当前文件可见,其他文件也可见
  • extern "C"表示这个函数定义在其他地方并且使用C语言的调用规范。这个extern "C"可以在一个块中定义多个函数。
  • 模板声明中,extern表示这个模板已经在其他地方实例化了,编译器可以直接复用这个实例,而不是创建一个新的实例。
1.5 最佳实践(头文件声明extern,仅有一个源文件定义实现)

通常而言,实现变量在多个文件共享的最佳方法是在头文件声明这个变量,同时在另一个源文件中定义这个变量。每当一个源文件需要使用这个变量时只需要引用这个头文件即可,为了避免重复包含,需要用宏定义处理这个声明。

二、具体例子

普通的全局变量默认是所有文件可见的,声明处和使用处都应该用extern表示它们属于同一个对象;常量修饰的全局变量是仅当前文件可见,假如确实我们需要多文件共享一个不变的变量,必须使用extern表示它属于同一变量。

2.1 改变const变量的默认链接属性

如果没有特殊声明,对于一个全局变量用const约束,编译器将会自动将其设置为当前文件可见,如果你要让所有文件可见,那么就可以用到extern修饰以修改默认连接属性。

//fileA.cpp
extern const int i = 42; // extern const definition

//fileB.cpp
extern const int i;  // declaration only. same as i in FileA

这样一来,const变量的链接属性是文件作用域,也就是所有文件均可看见这个全局常量。

2.2 改变非const变量默认链接属性

一个非常量默认的连接属性是外部的(external),但是仍然要符合ODR规则。使用extern修饰变量声明才不会被认为是一个定义。

//fileA.cpp
int i = 42; // declaration and definition

//fileB.cpp
extern int i;  // declaration only. same as i in FileA

//fileC.cpp
extern int i;  // declaration only. same as i in FileA

//fileD.cpp
int i = 43; // LNK2005! 'i' already has a definition.
extern int i = 43; // same error (extern is ignored on definitions)
2.3 extern constexpr 链接属性取决于编译器

在Visual Studio 2017 V15.3之前编译器默认将constexpr理解为内部链接,尽管显示用extern标记,在Visual Studio 2017 V15.3之后,extern表现正常。

2.4 extern "C"和extern "C++"函数声明
// Declare printf with C linkage.
extern "C" int printf(const char *fmt, ...);

//  Cause everything in the specified
//  header files to have C linkage.
extern "C" {
    // add your #include statements here
#include <stdio.h>
}

//  Declare the two functions ShowChar
//  and GetChar with C linkage.
extern "C" {
    char ShowChar(char ch);
    char GetChar(void);
}

//  Define the two functions
//  ShowChar and GetChar with C linkage.
extern "C" char ShowChar(char ch) {
    putchar(ch);
    return ch;
}

extern "C" char GetChar(void) {
    char ch;
    ch = getchar();
    return ch;
}

// Declare a global variable, errno, with C linkage.
extern "C" int errno;

默认情况下,以下对象具有内部链接:

  • const对象
  • constexpr对象
  • typedef对象
  • static 命名空间范围中的 对象

[1] https://docs.microsoft.com/en-us/cpp/cpp/extern-cpp?view=msvc-170
[2] https://docs.microsoft.com/zh-cn/cpp/cpp/program-and-linkage-cpp?view=msvc-170

【20220623】翻译了英文部分描述。

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值