extern "C"

一、C/C++程序的编译过程

首先看一下,C/C++程序的编译过程:

extern "C"

1、源文件经过“预处理”生成扩展的源文件(仍然是.cpp文件);

2、扩展的源文件经过“编译”生成汇编代码文件;

3、汇编代码文件经过“组装(Assembler)”生成中间代码文件;

4、中间代码文件经过“链接”生成可执行文件。

    总体来说就是,C/C++首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(Compile)。然后再把大量的 Object File合成执行文件,这个动作叫作链接(Link)。

    编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(.O文件或是.OBJ文件)。

    链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(.O文件或是.OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。上图中的“Object Code For Library Functions”,指的就是这些库文件中的中间代码。

二、C/C++程序是如何编译的

    C++支持函数重载,C语言不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y );该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“Mangled Name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。

    同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

三、extern关键字

    extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字。extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

extern int a;

    仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现链接错误。

    引用一个定义在其它模块的全局变量或函数(如,全局函数或变量定义在A模块,B欲引用)有两种方法,一、B模块中include模块A的头文件。二、模块B中对欲引用的模块A的变量或函数重新声明一遍,并在声明前面加extern关键字。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错。它会在链接阶段中从模块A编译生成的目标代码中找到此函数。但是一定要避免使用第二种方式。

四、extern "C"

    首先要明确一点,extern "C"是C++中的关键字,C语言是不支持extern "C"的。

    我们来看一个例子,在VC++ 6.0中新建一个工程,在工程中编写两个文件,分别是a.c和b.cpp。代码如下:

//file a.c
int foo(int x, int y)
{
	return x+y;
}
//file b.cpp
#include<iostream>
#include<windows.h>
using namespace std;

extern int foo(int x, int y);

int main()
{

	int z = foo(1, 3);
	
	cout<<z<<endl;

	system("pause");

}

    然后我们编译整个工程,编译器报错,LNK2001和LNK1120,无法解析的外部符号,工程出现了链接错误。原因就是我们上文提到的,a.c文件是按照C语言的方式进行编译的,foo函数编译后生成的符号为_foo;而b.cpp是按照C++方式编译的,在链接时,b.cpp想要找到的符号是_foo_int_int,所以编译器会报错,“无法解析的外部符号”。

    如果我们把b.cpp中的extern int foo(int x, int y)用extern "C"修饰则会出现正确的结果,如下:

//file b.cpp
#include<iostream>
#include<windows.h>
using namespace std;

extern "C"
{
	int foo(int x, int y);
}

int main()
{

	int z = foo(1, 3);
	
	cout<<z<<endl;

	system("pause");

}

    extern "C" 告诉编译器,foo函数是按照C语言的方式编译的,不是按照C++方式编译的,所以在链接时直接去找_foo这样的符号就行了。

    所以,C中编写的函数如果有在C++中调用的可能,通常会有下面形式的声明:

#ifdef  __cplusplus

extern "C" {

#endif

/**** some declaration or so *****/

#ifdef  __cplusplus

}

#endif

    其实extern "C" 的存在就是为了使C++更好的兼容C。

你可能感兴趣的:("C",C/C++编译;extern)