1. 编译单元
简单地,一个cpp文件就是一个编译单元。定义:当一个c或者cpp文件在编译时,预处理器首先递归包含头文件,形成一个含有所有必要信息的单个源文件,这个源文件就是一个编译单元。
编译每个编译单元时互相独立,即每个cpp文件之间不知道对方的存在(一般不这样写:#include“xxx.cpp”),编译器会分别将每个编译单元进行编译,生成相应的obj文件,然后生成最终的可执行文件。
2. 内部链接和外部链接
C++中声明和定义是可以分开的,例如我们在B.cpp中声明并定义一个函数func_b(),在A.cpp中只需要声明一下这个函数,就可以在A.cpp中使用这个函数了。
然而每个编译单元之间是相互独立,不知道彼此存在的,但A.cpp却知道func_b()的定义。这是因为,在编译一个编译单元生成相应的obj文件过程中,编译器会分析这个编译单元,将其所能提供给其他编译单元的函数、变量定义记录下来,而将自己缺少的函数、变量的定义也记录下来。
也就是说,A.obj记录了“我能提供main函数定义,我需要func_b()函数定义”,而B.obj记录了“我能提供func_b()函数的定义”,通过链接,在最终的可执行文件中我们能看到func_b()函数的运行。
内部链接-如果一个名称对编译单元来说是局部的,在链接的时候其他编译单元无法链接到它且不会与其他编译单元中的同样名称相冲突。(例如被关键字static,inline标识)
外部链接-如果一个名称对编译单元来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。
相应地有一个编程中常见的问题:
- aaa在BBB中重定义
在不同的cpp中重复定义了一个具有外部链接的函数或变量,链接器在链接时找到了多个一样的函数或变量定义。
- 无法解析的外部符号
只提供了函数或变量的声明没有提供其定义或者声明和定义的函数原型不一致,链接器没有找到其定义在哪里。
- 部分内联函数的定义需要写在头文件中
内联函数是内部链接的
- 对于模板,声明和定义都要写在一起
假设有如下代码:
A.h
#pragma once
template<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::a(const int& t)的定义,于是编译器寄希望于链接器,希望它能够在其他的.obj中找到定义,而在编译器编译A.cpp的时候,并没有用到A,而模板只有被用到时才会实例化,A.cpp并不知道main.cpp用了A,所以它不会提供定义。解决的方法是在A.cpp中加入一个函数用到A,或者直接将模板的定义和声明都放在头文件中。
宏是内部链接?外部链接?
宏在预处理环节被替换掉了,而内部链接和外部链接都是针对编译环节和链接环节的。
欢迎扫描二维码关注微信公众号 深度学习与数学 [每天获取免费的大数据、AI等相关的学习资源、经典和最新的深度学习相关的论文研读,算法和其他互联网技能的学习,概率论、线性代数等高等数学知识的回顾]