C++编译链接那些事

来源:互联网 发布:只会做蛋炒饭挂机软件 编辑:程序博客网 时间:2024/05/20 23:40

最近一直在研究编译链接,所以写篇文章总结一下。

先来了解几个概念吧:

要明白的几个概念:

    1、编译:编译器对源文件进行编译,就是把源文件中的文本形式存在的源代码翻译成机器语言形式的目标文件的过程,在这个过程中,编译器会进行一系列的语法检查。如果编译通过,就会把对应的CPP转换成OBJ文件(其实编译好像只是生成一个中间文件,再由汇编器生成obj文件)。

    2、编译单元:根据C++标准,每一个CPP文件就是一个编译单元。每个编译单元之间是相互独立并且互相不可知。

    3、目标文件:由编译所生成的文件,以机器码的形式包含了编译单元里所有的代码和数据,还有一些期他信息,如未解决符号表,导出符号表和地址重定向表等。目标文件是以二进制的形式存在的。

我打算以例子来分析编译链接:

例子一:

A.cpp:

int a

B.cpp:

int a
当这两个放在一起编译的时候,均没问题(因为编译是单独编译,彼此没有联系),但是链接的时候会出现问题:

B.obj : error LNK2005: "int a" (?a@@3HA) already defined in A.obj

分析:两个a均为全局变量,并且编译器会帮我们自动给它进行初始化为0,相当于进行了定义,而不仅仅是声明,在编译的时候,他们都会放入到.obj文件的符号表中,执行链接时,两个.obj文件的导出符号表均有a这个符号,所以会报错。大家注意到上面红体字会发现:定义和声明是不一样的。如果把其中之一改成声明,那就OK了。

如:A.cpp:extern int a;

这个extern关键字就是告诉编译器n已经在别的编译单元里定义了,在这个单元里就不要定义了。a其实是放入到A.obj的“未解决符号表”,也就是unresolved symbol table。当链接成.exe文件时,只有全部的unresolved symbol table中的符号都能在别的编译单元中的导出符号表中找到,链接才能通过。

和这个例子相似的是在一个编译单元中声明函数并使用函数,但是在另一个编译单元中定义该函数。 

例子二:

A.cpp:

static int a;

B.cpp:

static int a;
你会发现这么链接也没问题。

分析:如果该关键字位于全局函数或者变量的声明前面,表明该编译单元不导出这个函数或变量,因些这个符号不能在别的编译单元中使用(内部链接)。如果是static局部变量,则该变量的存储方式和全局变量一样,但是仍然不导出符号。

内部链接和外部链接的区别:看这个符号是不是写入到.obj文件中。

对于const变量,默认内部链接。所以你可以像写static变量那样定义const变量,但必须进行初始化。
例子三:

int a;void func(){     int a;}

大家都会写出这样的代码。而且非常正确。

分析:全局变量a在前面解释过,放入到导出符号表中,但是局部变量a呢?它由栈管理,并不放入导出符号表中。

下面来分析一个有难度的:头文件和实现文件

例子四:

c.h:

class A{public:const int a;static int b;public:A(int num):a(num){}void test();};

在这里探讨const成员没有任何意义,只是为了熟悉类中const成员变量和引用成员变量(intialization list中定义)。

先来看static int b吧。这只是一个声明(内部链接)。需要在别的文件中进行定义,但这个定义是外部链接。什么意思呢?

A.cpp:

int A::b=1;

B.cpp:

int A::b=2;

执行链接的时候会报错。重复定义A::b。

再来看看void test(),一般我们都是在别的文件中实现这个方法,如:

d.cpp:

void A::test(){     ......}
如果我们需要用到A的话,加入#include "c.h"就可以了。但一切来的这个简单的原因是什么呢?

分析:头文件只是一个声明,声明一个类A,类A中声明一系列东西。d.cpp文件对test()方法进行了定义,test符号写入到d.obj导出符号表中。别的文件使用A a;a.test()就可以链接到了,不会报错。

大家都知道A.cpp :#include "c.h"代表什么意思:编译的时候c.h中内容出现在A.cpp中,因为此时头文件中的东西是内部链接(没有定义),所以在B.cpp:#include "c.h"是不会出现编译链接问题的。

但如果我们不把实现放入到别的文件中呢?而是放入到头文件中呢?如:

class A{public:const int a;static int b;public:A(int num):a(num){}void test();}; void A::test(){cout<<"A类链接过来了..."<<endl;}

A.cpp :#include "c.h"    B.cpp:#include "c.h"
那么这样就会出现问题了。因为定义是外部链接, A.cpp中包含了test,B.cpp中也包含了test :

B.obj : error LNK2005: "public: void __thiscall A::test(void)" (?test@A@@QAEXXZ) already defined in A.obj

解决办法:将其声明为内联函数inline void test(); 并且内联函数要定义在头文件中,因为编译时编译单元之间互不知道,如果内联被定义于.cpp文件中,编译其他使用该函数的编译单元的时候没有办法找到函数的定义,因些无法对函数进行展开。所以如果内联函数定义于.cpp里,那么就只有这个.cpp文件能使用它。

通过对编译链接的学习,才对那些.lib,.dll文件有了进一步的认识,为什么要这些东西。这些文件中实际上就是我们头文件中声明的函数定义等,我们仅仅引入头文件是不行的,找不到定义也完全没用。那么api基本上都是通过静态库和动态库的方式提供给我们。

下面我们来看看模版编译问题

让我们从一个简单的例子开始吧:

2.h:

template<class T>class A{public: int test(T a);};
3.cpp:

#include<iostream>#include "2.h"template<class T>int  A<T>::test(T a){std::cout<<"A test "<<a<<std::endl;return a;}

1.cpp:

#include<iostream>#include "2.h"void main(){A<int> a;a.test(4);}
很遗憾的告诉你,链接出现问题:

1.obj : error LNK2001: unresolved external symbol "public: int __thiscall A<int>::test(int)" (?test@?$A@H@@QAEHH@Z)
找不到test的定义?怎么会呢?我们一般的类不都是这样写的么?注意这里是模版类,和一般类编译是不一样的。
分析:1.cpp中肯定是有test()的声明,寻找test()的定义是在链接的时候发生的。那可以肯定的是3.obj中肯定没有test()的定义<----这一切都是我们推出来的。那为什么没有test()的定义呢?

根据C++标准,当一个模板不被用到进它就不应该被具体化-----什么意思呢?

3.cpp里面如果没有用到A<int>::test()的话,A<int>::test()函数的二进制代码就不会被编译到3.obj文件中去。

如果还是不明白的话,可以来实践一下:

3. cpp:

#include<iostream>#include "2.h"template<class T>int  A<T>::test(T a){std::cout<<"A test "<<a<<std::endl;return a;}A<int> a;int g_a=a.test(4);
你再运行一下,会发现什么问题都没有了。。这下肯定理解了那句话是什么意思了吧。

你现在会抱怨了,这多不专业啊!其实我们可以采用包含编译模式:

将模版定义也放在类中:

2.h:

#include<iostream>template<class T>class A{public: int test(T a);};template<class T>int  A<T>::test(T a){std::cout<<"A test "<<a<<std::endl;return a;}

这有点像我们的inline函数。

编译链接就OVER了。。

原创粉丝点击