extern关键字的作用
extern关键字的作用
本文讲述了在c/c++代码中,extern关键字及extern “C"和extern “C++“的作用以及原理
extern
extern关键字在c/c++官方文档中的解释
- 具有外部 链接的静态存储期说明符
- 语言链接说明
- 显式模板实例化声明(或具有外部 链接的静态存储期说明符“extern 模板”)
- 用于类模板
- 用于函数模板
1)具有外部 链接的静态存储期说明符
- 告诉编译器该变量或函数的定义在其他文件中, 链接 时会在全局符号表中查找。
- 用于在多个文件(一般是源文件,因为头文件直接include即可)之间共享全局变量或函数。
例如,在file1.cpp中定义一个全局变量,在file2.cpp中使用extern 声明 该变量,就可以在file2.cpp中使用该变量。同理函数也是如此,因为在file2.cpp声明该变量/函数后,编译器就知道某个地方存在该变量/函数,在 链接 的时候,就会在全局符号表中寻找该符号。
例如,以下代码,在test.cpp中定义一个全局变量a,和一个函数print,在main.cpp中声明该变量和函数,可以正确访问。
//test.cpp
#include <iostream>
int a = 9527;
void print()
{
std::cout << "hello world" << std::endl;
}
//main.cpp
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
extern int a;
extern void print();
int main()
{
cout << a << endl;
print();
return 0;
}
2)语言链接说明
作用 :
- 用于与 C 语言或其他语言编写的代码交互。
- 默认情况下,C++ 会对函数名进行名称修饰(Name Mangling),而 C 语言不会。使用
extern "C"
可以禁用名称修饰。
2.1)什么是名称修饰
在c++中,函数是可以重载的,即可以支持函数支持针对不同的参数类型和参数数量进行不同的处理。以vs编译器为例,其实现的原理是,用不同的符号标记不同的参数,在编译阶段使用函数名+返回值类型+参数类型标识拼接的方式形成符号表,这样就可以区分出不同参数类型和参数数量的函数重载。
例如,以下重载了两个add函数,两个add函数的参数类型和个数都不相同,编译查看其汇编代码main.asm,可以看到,汇编中有两个add函数,为 ?add@@YAHHHH@Z 和 ?add@@YANNN@Z ,其中add为源文件中函数名,@@为固定格式,YA是调用方法,第一个字符为返回值,后面的字母为函数类型,有几个字母就是有几个参数,在vs下面,H标识int类型,N标识double类型,@Z是结束标识。通过这种方式,编译器就可以区分不同的函数重载。
int add(int a, int b, int c)
{
return a + b + c;
}
double add(double a, double b)
{
return a + b;
}
int main()
{
cout << add(10, 20, 30) << endl;
cout << add(3.14, 3.15) << endl;
return 0;
}
2.2)extern 指定语言链接
extern可以指定在链接某部分代码时使用C语言或者C++,因为c++是兼容c的,所以一般使用extern “C++“的场景较少,一般使用extern “C”,使用C语言链接,一个很大的特征就是函数无法直接重载,因为C语言是没有名称修饰的功能,破坏了C++重载的条件。
在原来代码基础上,add函数使用extern “C"修饰,链接时报错,原因是无法找到调用函数形成的修饰名称
extern "C"
{
int add(int a, int b, int c)
{
return a + b + c;
}
}
extern "C"
{
double add(double a, double b)
{
return a + b;
}
}
int main()
{
cout << add(10, 20, 30) << endl;
cout << add(3.14, 3.15) << endl;
}
3)显式模板实例化声明
extern
可以用于显式模板实例化声明,以避免在多个翻译单元中重复实例化相同的模板。
作用 :
- 减少编译时间和生成的目标文件大小。
- 声明模板实例化在其他翻译单元中定义。
extern 修饰显式实例化声明是C++11引入的新特性,使用extern修饰显式模板实例化声明的作用在编译过程中,不会重复实例化模板。
例如,在test.h中定义了一个模板类和一个模板函数,在test.cpp中显式实例化,在main.cpp中使用extern声明显式实例化,结果就是在编译过程中,main.cpp不会再去实例化一份int类型的模板。
//test.h
template<typename T>
class A
{
public:
void print(T a)
{
std::cout << a << std::endl;
}
};
template<typename T>
void showNumber(T num)
{
std::cout << num << std::endl;
}
//test.cpp
#include <iostream>
#include "test.h"
template A<int>;
template void showNumber<int>(int);
//main.cpp
#include <iostream>
#include "test.h"
extern template A<int>;
extern template void showNumber<int>(int);
int main()
{
A<int> a;
a.print(2);
showNumber(11);
}