1、C/C++程序的编译过程 ios
首先看一下,C/C++程序的编译过程: windows
一、源文件通过“预处理”生成扩展的源文件(仍然是.cpp文件); 函数
二、扩展的源文件通过“编译”生成汇编代码文件; spa
三、汇编代码文件通过“组装(Assembler)”生成中间代码文件; code
四、中间代码文件通过“连接”生成可执行文件。 内存
整体来讲就是,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”,指的就是这些库文件中的中间代码。 io
2、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++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,咱们以"."来区分。而本质上,编译器在进行编译时,与函数的处理类似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不一样。
3、extern关键字
extern是C/C++语言中代表函数和全局变量做用范围(可见性)的关键字。extern能够置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其余模块中寻找其定义。
extern int a;
仅仅是一个变量的声明,其并非在定义变量a,并未为a分配内存空间。变量a在全部模块中做为一种全局变量只能被定义一次,不然会出现连接错误。
引用一个定义在其它模块的全局变量或函数(如,全局函数或变量定义在A模块,B欲引用)有两种方法,1、B模块中include模块A的头文件。2、模块B中对欲引用的模块A的变量或函数从新声明一遍,并在声明前面加extern关键字。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,可是并不会报错。它会在连接阶段中从模块A编译生成的目标代码中找到此函数。可是必定要避免使用第二种方式。
4、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。