C/C++之关键字extern

Typist : Akame Qixisi / Excel Bloonow

extern是我在学习C/C++的过程中存在疑惑较多的一个关键词,本文章简要记录了extern的基本用法;并较为详细的记录了在C++代码中extern "C"的作用。


关键字 extern

(一)概述

extern是计算机语言C/C++中一个关键字,可用于变量或函数前;对于使用者,它声明一个外部符号,以表示该变量或函数是定义在别的文件中的外部符号;对于创建者,它用在头文件中的一个声明之前或一个定义之前,显式说明该符号可以被其他文件使用(有些形式具有默认的外部链接,可以不用extern显式说明)。另外,extern也可以用来进行链接指定。

在编译时,提示编译器遇到此变量或函数时,知道这个标识符在其他文件中定义过,可以编译通过。在链接时,提示链接器在其他文件中寻找其定义。

需要注意的是,根据前述指针与数组的区别,在一个文件中使用int a[8]定义一个数组,而在另一个文件中使用extern int* a去声明这个外部符号,编译可以通过,但它们的链接是不成功的。因此说明,在一个文件中定义的外部符号和在另一文件中声明的外部符号,必须是严格类型相同的。

变量、函数(free函数默认有外部链接):

// a.c		// 定义外部符号,以供其他文件使用,注意参考《大规模C++程序设计》查阅默认具有外部链接的形式
int x = 10;
int add (int a, int b) { return a + b; }
// b.c		// 声明外部符号,以便使用其他文件定义的符号,可直接使用
extern int x;
extern int add(int, int);

现代编译器一般采用按文件编译(其实是编译单元)编译的方式,因此在编译时,各个编译单元中定义的全局变量是互相不透明的。也就是,在编译时,全局变量的可见域限制在文件内部。到了链接阶段,要将各个编译单元中的符号链接到一个程序中,因而,定义于某编译单元的全局变量,在链接完成后,它的可见范围被扩大到了整个程序,很容易推导出,一个文件中定义的全局变量,可以在整个程序的任何地方都使用。在链接时,如果在不同的编译单元中存在重复的全局定义,则会链接失败。

(二)extern “C”

首先明确,extern "C"是 C++ 中的写法( .h 头文件,.cpp 实现文件),C语言中并没有该写法。

总括:extern “C” 是 C++ 语言为了兼容 C 程序、使用 C 语言库,实现C++与C以及其他语言混合编程所引入的。为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而 extern “C” 就是其中的一个策略。任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。

在C++环境下使用C函数的时候,常常会出现编译器无法找到目标模块中的C函数定义,从而导数链接失败。原因如下:

对于一个函数 int add(int a, int b)。由于C++支持重载,编译器为了解决函数的重载问题,会将函数名和参数类型一起加到编译后的代码中,合起来生成一个中间的函数名称,如 _add_int_int 这样的符号;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名,如 _add 这样的符号。不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为”mangled name“。由于在C++代码中编译的符号和C代码中编译的符号不同,自然链接不成功。

同样地,本质上,编译器在C++中的类成员变量等时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与代码中的名字不同。

extern “C” 的主要作用就是为了能够正确实现C++代码调用其他C语言代码,加上extern "C"后,会指示编译器这部分(C++)代码按C语言(而不是C++)的方式进行编译,即函数编译成的符号不带参数类型信息,从而C++代码编译后在目标文件中的符号,就和C代码编译成的符号一样,自然可以链接成功。一般来说有三种使用方式:

// myheader.h	C++头文件
extern "C" int add(int, int);		// 对于单一函数使用

extern "C" {		// 可以是复合语句,相当于复合语句中的声明都使用了 extern "C"
    int sub(int, int);
    double mul(int, int);
}

extern "C" {		// 可以包含头文件,相当于头文件中的声明都使用了 extern "C"
    #include <cmath>
}
  • 如果C++调用一个C语言编写的 .dll 动态库文件时,当包括 .dll 的头文件或声明接口函数时,应用 extern “C” { } 的方式。

由于对头文件来说,无法靠后缀名等形式区分是C语言的头文件还是C++的头文件,所以常使用一个C++才有的宏来判断是否是C++文件,如下:

// myheader.h
#ifdef __cplusplus		// C++文件(编译器)才有的宏定义,如果定义了,就说明这是C++代码
extern "C" {
#endif

	int sub(int a, int b);

#ifdef __cplusplus
}
#endif
  • 不可以将 extern “C” 添加在函数内部。
  • 如果函数有多个声明,可以都添加 extern “C”,也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。
  • 除 extern “C” 之外,还有 extern “FORTRAN” 等。

常见的使用场合为:C++代码调用C语言代码;在C++的头文件中使用;在多人协同开发时,可能有人比较擅长C语言,有人擅长C++等。如在C++中使用C语言函数或C语言的库函数:

// c_sample.h		// C头文件
#ifndef C_SAMPLE_H
#define C_SAMPLE_H
extern int add(int x, int y);		// 自由free函数默认居然外部链接,可以不用extern
#endif

// c_sample.c		// C实现文件
#include "c_sample.h"
int add(int x, int y) { return x + y; }
// tool_model.cpp	// C++实现文件,也可以为C++头文件或其他C++文件
extern "C" {
    #include "c_sample.h"	// 可以采用其他方式,推荐使用头文件包含
}

// use the c function somewhere.
int result = add(lvalue, rvalue);

同理,如果要从C++语言代码中导出函数,一共C语言程序使用,也需要在C++源文件要导出的函数前使用 extern “C” 来修饰,以按C语言编译风格编译,让C语言程序可以调用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值