extern

我们知道,程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束

然而,如果全局变量不在文件的开头定义,有效的作用范围将只限于其定义处到文件结束

如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern
对该变量作“外部变量声明”
,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。

#include <stdio.h>
int max(int x,int y);
int main(void)
{
    int result;
    /*外部变量声明*/
    extern int g_X;
    extern int g_Y;
    result = max(g_X,g_Y);
    printf("the max value is %d\n",result);
    return 0;
}
/*定义两个全局变量*/
int g_X = 10;
int g_Y = 20;
int max(int x, int y)
{
    return (x>y ? x : y);
}

代码中,全局变量 g_X 与 g_Y 是在 main 函数之后声明的,因此它的作用范围不在 main 函数中。如果我们需要在 main 函数中调用它们,就必须使用 extern 来对变量 g_X 与 g_Y 作“外部变量声明”,以扩展全局变量的作用域。也就是说,如果在变量定义之前要使用该变量,则应在使用之前加 extern 声明变量,使作用域扩展到从声明开始到本文件结束。

如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,同样只需在引用变量的文件中用 extern 关键字加以声明即可

下面就来看一个多文件的示例:

/****max.c****/
#include <stdio.h>
/*外部变量声明*/
extern int g_X ;
extern int g_Y ;
int max()
{
    return (g_X > g_Y ? g_X : g_Y);
}

/***main.c****/
#include <stdio.h>
/*定义两个全局变量*/
int g_X=10;
int g_Y=20;
int max();
int main(void)
{
    int result;
    result = max();
    printf("the max value is %d\n",result);
    return 0;
}

运行结果为:
the max value is 20

对于多个文件的工程,都可以采用上面这种方法来操作。对于模块化的程序文件,可在其文件中预先留好外部变量的接口,也就是只采用 extern 声明变量,而不定义变量,max.c 文件中的 g_X 与 g_Y 就是如此操作的。

通常,这些外部变量的接口都是在模块程序的头文件中声明的,当需要使用该模块时,只需要在使用时具体定义一下这些外部变量即可。main.c 里的 g_X 与 g_Y 则是相关示例。

不过,需要特别注意的是,由于用 extern
引用外部变量,可以在引用的模块内修改其变量的值,因此,如果有多个文件同时要对应用的变量进行操作,而且可能会修改该变量,那就会影响其他模块的使用。因此,我们要慎重使用

extern “C”

1. 背景

C++ 就是在 C 语言的基础上增加了一些新特性,从大的方面讲,C++ 不仅支持面向过程编程,还支持面向对象编程和泛型编程;从小的方面讲,C++ 还支持命名空间、函数重载、内联函数等。
C++ 和 C 可以进行混合编程。但需要注意的是,由于 C++ 和 C 在程序的编译、链接等方面都存在一定的差异,而这些差异往往会导致程序运行失败。
举个例子,如下就是一个用 C++ 和 C 混合编程实现的实例项目:

//myfun.h
void display();

//myfun.c
#include <stdio.h>
#include "myfun.h"
void display(){
   printf("C++:http://c.biancheng/net/cplus/");
}

//main.cpp
#include <iostream>
#include "myfun.h"
using namespace std;
int main(){
   display();
   return 0;
}

在此项目中,主程序是用 C++ 编写的,而 display() 函数的定义是用 C 语言编写的。从表面上看,这个项目很完整,我们可以尝试运行它:

In function `main': undefined reference to `display()'

如上是调用 GCC 编译器运行此项目时给出的错误信息,指的是编译器无法找到 main.cpp 文件中 display() 函数的实现代码。导致此错误的原因,就是因为 C++ 和 C 编译程序的方式存在差异。

原因如下:

  • 之所以 C++ 支持函数的重载,是因为 C++ 会在程序的编译阶段对函数的函数名进行“再次重命名”,例如:
void Swap(int a, int b) 会被重命名为_Swap_int_int;
void Swap(float x, float y) 会被重命名为_Swap_float_float。

显然通过重命名,可以有效避免编译器在程序链接阶段无法找到对应的函数。

  • 但是,C 语言是不支持函数重载的,它不会在编译阶段对函数的名称做较大的改动。仍以 void Swap(int a, int b) 和 void Swap(float x, float y) 为例,若以 C 语言的标准对它们进行编译,两个函数的函数名将都是_Swap

不同的编译器有不同的重命名方式,但根据 C++ 标准编译后的函数名几乎都由原有函数名和各个参数的数据类型构成,而根据 C 语言标准编译后的函数名则仅有原函数名构成。这里仅仅举例说明,实际情况可能并非如此。

这也就意味着,

使用 C 和 C++ 进行混合编程时,考虑到对函数名的处理方式不同,势必会造成编译器在程序链接阶段无法找到函数具体的实现,导致链接失败。

幸运的是,C++ 给出了相应的解决方案,即借助 extern “C”,就可以轻松解决 C++ 和 C 在处理代码方式上的差异性。

2. 用法

extern 是 C 和 C++ 的一个关键字,但对于 extern “C”,读者大可以将其看做一个整体,和 extern 毫无关系。

extern “C” 既可以修饰一句 C++ 代码,也可以修饰一段 C++ 代码,它的功能是让编译器以处理 C 语言代码的方式来处理修饰的 C++ 代码。

仍以上面例子来说,main.cpp 和 myfun.c 文件中都包含 myfun.h 头文件,当程序进行预处理操作时,myfun.h 头文件中的内容会被分别复制到这 2 个源文件中

  • 对于 main.cpp 文件中包含的 display() 函数来说,编译器会以 C++ 代码的编译方式来处理它;
  • 而对于 myfun.c 文件中的 display() 函数来说,编译器会以 C 语言代码的编译方式来处理它。

为了避免 display() 函数以不同的编译方式处理,我们应该使其在 main.cpp 文件中仍以 C 语言代码的方式处理,这样就可以解决函数名不一致的问题。因此,可以像如下这样来修改 myfun.h:

#ifdef __cplusplus //如果是c++
extern "C" void display(); //按C编译
#else //不是C++,正常编译
void display();
#endif

可以看到,当 myfun.h 被引入到 C++ 程序中时,会选择带有 extern “C” 修饰的 display() 函数;反之如果 myfun.h 被引入到 C 语言程序中,则会选择不带 extern “C” 修饰的 display() 函数。由此,无论 display() 函数位于 C++ 程序还是 C 语言程序,都保证了 display() 函数可以按照 C 语言的标准来处理。

再次运行该项目,会发现之前的问题消失了,可以正常运行:

C++:http://c.biancheng/net/cplus/

在实际开发中,对于解决 C++ 和 C 混合编程的问题,通常在头文件中使用如下格式:


#ifdef __cplusplus //如果是c++代码 {}内按照C来编译,不是就正常编译
extern "C" {
#endif

void display();
...其他声明...

#ifdef __cplusplus
}
#endif

总结一句话:

让 c++写的 .cpp文件包含的 头文件内容 也按照C语言的方式编译。