乐于分享
好东西不私藏

C++编程实践—extern的应用说明

C++编程实践—extern的应用说明

一、extern

extern关键字无论是在C语言还是在C++语言中都非常重要,作为一种重要的存储类说明符,主要用来声明一个变量或函数具有外部链接属性。即它可以在其本编译单元中进行使用,但定义不在本编译单元。其基本的定义形式如下:

externint g_init;
externintfunc();

需要说明的是,函数天然具有extern属性,所以一般不需要显式的声明为extern。

二、主要技术点

在C/C++中,extern的主要应用包括以下几种情况:

  1. extern和变量
    即定义不同的变量(全局、线程局部等)是非本编译单元原始定义。这个比较简单,看一个线程局部变量定义的例子:

    //.h
    externthread_localint tls_var;
    //.cpp 可以跨编译单元共享名字,但每个线程都有自己的副本
    thread_localint tls_var;

    不过需要说明的是,较老的编译器可能对tls的extern应用支持不足。

  2. extern和函数
    声明一个函数是外部定义,并且在C/C++混合编程中,提醒编译器不进行C++的编译改名机制。在前面已经说过了,全局函数天然具有extern属性,所以一般不用书写。但extern应用的一个重要的方面即extern “C”是在C/C++混合编程中的函数调用中处理改名机制(name mangling):

    //c  .h 调用一个C库的头文件
    // xxxso.h
    #pragma once

    #ifdef __cplusplus
    extern"C" {
    #endif

    intadd(int a, int b);
    voidloadData(void);

    #ifdef __cplusplus
    }
    #endif
    //c++ .cpp
    #include"xxxso.h"
    intmain(){
    return add(101);
    }
  3. extern和模板
    由于模板的分离编译问题,extern一般无法在模板应用中使用。但外部模板,extern template是一种特殊应用。这个在前面有过详细说明,此处不再赘述,请参看相关文档

  4. 其它
    包括一些不常见的应用,比如名空间、嵌套等。如:

    namespace A {
    namespace B {
    int d = 0;                
    externint temp;      
        }
    }
    int A::B::temp = 1;          

一般来说,掌握好前三点,基本就不会有什么大问题的出现了。一些细节可以在后期的应用中遇到再进行完善和掌握即可。

三、细节和陷阱

  1. extern与static
    这是一个典型的冲突,前者代表有外部属性而后者代表是内部使用。即不允许将二者同时应用到一个变量或函数中
  2. extern与const
    前面提到的const在C和C++中对链接属性的控制不同的问题(细节参看前文“C和C++中const的不同”)
  3. extern与新标准inline
    其实在新标准下,如果想直接在头文件中定义全局变量,提供了inline即内联变量。在前面也分析过,应用起来更简单方法
  4. extern与重载
    正如前面所分析,extern “C”就是处理改名保留原始名称,如果有重载,就会生成多个同名函数,编译无法通过。
    另外extern “C”这个机制无法在C语言中使用,看名字应该也明白。自己用自己,没意思。
  5. extern与初始化
    这点很重要,extern并不影响全局变量的初始化顺序,这也是前面反复提到的初始化顺序影响软件安全的一个基本技术点
  6. extern与动态库的导出机制
    这个不要误解,extern中是声明外部链接,和导出机制不是一回事

其实还有一些不太常用的细节和陷阱,比如混合头文件的处理等,这都需要开发者自己到时候要认真把控,此处就不一一展开说明。

四、例程

看一个常见的全局变量声明应用的方法:

//全局变量头文件:一般在头文件进行extern声明
// externGlobal.h
#pragma once
externint g_var;
externchar g_char;
//全局变量定义文件:在cpp文件中进行定义
// externGlobal.cpp
externint g_var = 0;
externchar g_char = 0;
//so头文件 xxxso.h
#pragma once
//对于C++调用C库函数需要显示的使用extern "C"防止改名
#ifdef __cplusplus
extern"C" {
#endif

intadd(int a, int b);
voidloadData(void);

#ifdef __cplusplus
}
#endif
//c++ .cpp
#include"xxxso.h"
#include"externGlobal.h"
intmain(){
return add(10, g_var);
}

上面的示例非常简单,但也提供了一个全局extern和extern “C”的很常见的用法。模式都是一样,只是复杂程度有所不同罢了。

五、总结

对于一些基础的关键字的用法,很多开发者看上去对其很了解,而且确实在开发不会出现问题。但这未必代表真正理解了它们的含意。在变更应用场景或相关应用机制后,有可能就会卡一下。不过,这都不是重点,这些细节是可以在真正需要时再补齐的。也就是前面提到的,“重视细节但不陷入细节”!