C++内部链接与外部链接

来源:互联网 发布:java 网络请求框架 编辑:程序博客网 时间:2024/05/21 17:59

你曾经碰到的问题: 

1.为什么有时会出现aaa已在bbb中重定义的错误?

2.为什么有时会出现无法解析的外部符号?

3.为什么有的内联函数的定义需要写在头文件中?

4.为什么对于模板,声明和定义都要写在一起?


编译单元

 什么是编译单元呢?简单来说一个cpp文件就是一个编译单元。

编译单元:当一个c或cpp文件在编译时,预处理器首先递归包含头文件,形成一个含有所有 必要信息的单个源文件,这个源文件就是一个编译单元。

事实上编译每个编译单元(.cpp)时是相互独立的,即每个cpp文件之间是不知道对方的存在的(不考虑#include “xxx.cpp" 这种奇葩的写法)

编译器会分别将每个编译单元(.cpp)进行编译,生成相应的obj文件

然后链接器会将所有的obj文件进行链接,生成最终可执行文件。

 

内部链接与外部链接

那么什么内部链接和外部链接又是什么呢?

我们知道C++中声明和定义是可以分开的

例如 我们可以一个函数声明定义放在b.cpp中,在a.cpp只需再声明一下这个函数,就可以在a.cpp中使用这个函数了

a.cpp

复制代码
void show();int main(){      show();     return 0;}
复制代码

b.cpp

#include <iostream>void show(){    std::cout << "Hello" << std::endl;}

而通过之前的了解,我们知道每个编译单元间是相互独立不知道彼此的存在的

那么a.cpp又是如何知道show函数的定义的呢

其实在编译一个编译单元(.cpp)生成相应的obj文件过程中

编译器会将分析这个编译单元(.cpp)

将其所能提供给其他编译单元(.cpp)使用的函数,变量定义记录下来。

而将自己缺少的函数,变量的定义也记录下来。

所以可以认为a.obj和b.obj记录了以下的信息

image

然后在链接器连接的时候就会知道a.obj需要show函数定义,而b.obj中恰好提供了show函数的定义,通过链接,在最终的可执行文件中我们能看到show函数的运行。


 

好了让我们看下 内部链接外部链接比较正式的定义吧 

内部连接:如果一个名称对编译单元(.cpp)来说是局部的,在链接的时候其他的编译单元无法链接到它且不会与其它编译单元(.cpp)中的同样的名称相冲突。例如static函数,inline函数等(注 : 用static修饰的函数,本限定在本源码文件中,不能被本源码文件以外的代码文件调用。而普通的函数,默认是extern的,也就是说,可以被其它代码文件调用该函数。


外部连接:如果一个名称对编译单元(.cpp)来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。 例如变量就是外部链接, 全局变量。

 

那么回到最初的问题:

1. 为什么有时会出现aaa已在bbb中重定义的错误?

答:  你可能在不同的cpp中重复定义了一个具有外部链接的函数或变量,链接器在链接时找到了多个一样的函数或变量定义。

 

2. 为什么有时会出现无法解析的外部符号?

答:你可能只提供了函数或变量的声明,没有提供其定义,或者声明和定义的函数原型不一致,链接器没有找到其定义在哪里,所以在链接环节出现了无法解析的外部符号的错误。

 

3. 为什么有的内联函数的定义需要写在头文件中呢?

答:因为内链函数是内部链接的,如果你在b.cpp中定义这个函数,那么在a.cpp中即使有这个函数声明,但由于内联函数是内部链接的,所以b.cpp不会提供其定义。所以在链接时a.obj无法找到这个函数的定义,便会出现无法解析的外部符号的错误

 

4.为什么对于模板,声明和定义都要写在一起呢?

答:我们假设我们有如下结构的代码


a.h

复制代码
#pragma oncetemplate<typename T>class A{public:    A(const T &t);};
复制代码

 

a.cpp

复制代码
#include "a.h"#include <iostream>template<typename T>A<T>::A(const T &t){    std::cout << t << std::endl;}
复制代码

 

main.cpp

复制代码
#include "a.h"int main(){     A<int> a(5);    return 0;}
复制代码

 那么程序能否正常运行呢?答案是不能 我们首先来分析一下编译器在编译main.cpp时,只有声明,发现其缺少A<int>::a(const int& t)的定义 ,因为它不在a.h里面, 于是编译器只好寄希望于连接器, 希望它能够在其他.obj里找到定义, 而在编译器编译a.cpp时,没有用到A<int> , 模板只有被用到的时候才会被实例化, 每个编译单元是独立的,它也不知道main.cpp用了A<int> ,所以它不会提供定义,编译出来的a.obj文件中关于A 的一行二进制代码也没有这样在链接时main.obj无法找到A<int>::a(const int& t)的定义,就会出现无法解析的外部符号的错误LNK1120 (注意, 在a.cpp 中加入一个函数用到A, void f2(){ A<int >  a(222); } ,则此问题解决  )

 

5.宏是内部链接还是外部链接

答:都不是,宏在预处理环节时就被替换掉了,而内部链接与外部链接是针对编译环节与链接环节而言的


Created by 黄强