C++编译与链接(2)-浅谈内部链接与外部链接
发现每次写技术博客时,都会在文章开头处花费一番功夫
...从前,有一个程序员....他的名字叫magicsoar
为什么有时会出现aaa已在bbb中重定义的错误?
为什么有时会出现无法解析的外部符号?
为什么有的内联函数的定义需要写在头文件中?
为什么对于模板,声明和定义都要写在一起?
读完这篇博客,相信你会有一个初步的认识
注,我们现在谈的编译其实可以认为由4个环节组成,其中有编译环节,链接环节, 我会尽量在上下文中指明说的总体的编译,还是具体的编译环节,望读者周知
关于编译过程详解说明,可以参照我之前的一篇博客 C++编译与链接(1)-编译与链接过程
编译单元
首先让我们来认识一下编译单元,什么是编译单元呢?简单来说一个cpp文件就是一个编译单元。
在集成式的IDE中,我们往往点击一下运行便可以了,编译的所有工作都交给了IDE去处理,往往忽略了其中的内部流程
事实上编译每个编译单元(.cpp)时是相互独立的,即每个cpp文件之间是不知道对方的存在的(不考虑#include “xxx.cpp" 这种奇葩的写法)
编译器会分别将每个编译单元(.cpp)进行编译,生成相应的obj文件
然后链接器会将所有的obj文件进行链接,生成最终可执行文件
内部链接与外部链接
那么什么内部链接和外部链接又是什么呢?
我们知道C++中声明和定义是可以分开的
例如在vs中,我们可以一个函数声明定义放在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记录了以下的信息
然后在链接器连接的时候就会知道a.obj需要show函数定义,而b.obj中恰好提供了show函数的定义,通过链接,在最终的可执行文件中我们能看到show函数的运行
哪这些又和内部链接,外部链接有什么关系呢?
那些编译单元(.cpp)中能向其他编译单元(.cpp)展示,提供其定义,让其他编译单元(.cpp)使用的的函数,变量就是外部链接,例如全局变量
而那些编译单元(.cpp)中不能向其他编译单元(.cpp)展示,提供其定义的函数,变量就是内部链接,例如static函数,inline函数等
好了让我们看下编译单元,内部链接和外部链接比较正式的定义吧
编译单元:当一个c或cpp文件在编译时,预处理器首先递归包含头文件,形成一个含有所有 必要信息的单个源文件,这个源文件就是一个编译单元。
内部连接:如果一个名称对编译单元(.cpp)来说是局部的,在链接的时候其他的编译单元无法链接到它且不会与其它编译单元(.cpp)中的同样的名称相冲突。
外部连接:如果一个名称对编译单元(.cpp)来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。
最后让我们回到文章开头处的那几个问题吧
为什么有时会出现aaa已在bbb中重定义的错误?
答:你可能在不同的cpp中重复定义了一个具有外部链接的函数或变量,链接器在链接时找到了多个一样的函数或变量定义
为什么有时会出现无法解析的外部符号?
答:你可能只提供了函数或变量的声明,没有提供其定义,或者声明和定义的函数原型不一致,链接器没有找到其定义在哪里,所以在链接环节出现了无法解析的外部符号的错误
为什么有的内联函数的定义需要写在头文件中呢?
答:因为内链函数是内部链接的,如果你在b.cpp中定义这个函数,那么在a.cpp中即使有这个函数声明,但由于内链函数是内部链接的,所以b.cpp不会提供其定义
所以在链接时a.obj无法找到这个函数的定义,便会出现无法解析的外部符号的错误
为什么对于模板,声明和定义都要写在一起呢?
答:我们假设我们有如下结构的代码
b.h
#pragma once
template<typename T>
class A
{
public:
A(const T &t);
};
b.cpp
#include "b.h"
#include <iostream>
template<typename T>
A<T>::A(const T &t)
{
std::cout << t << std::endl;
}
a.cpp
#include "b.h"
int main()
{
//A<int> a(5);
return 0;
}
那么a.cpp中注释的那行代码能否正常运行呢?答案是不能我们首先来分析一下编译器在编译a.cpp时,发现其缺少A<int>::a(const int& t)的定义而在编译器编译b.cpp时,由于每个编译单元是独立的,而模板只有被用到的时候才会被实例化,产生定义,b.cpp不知道a.cpp用了A<int>::a(const int& t),所以它不会提供A<int>::a(const int& t)的定义,编译器不会有任何反应,这样在链接时a.obj无法找到A<int>::a(const int& t)的定义,就会出现无法解析的外部符号的错误
宏是内部链接还是外部链接
答:都不是,宏在预处理环节时就被替换掉了,而内部链接与外部链接是针对编译环节与链接环节而言的
- Hbase常用Shell命令
- 对缓存的思考【续】——编写高速缓存友好代码
- 前端AES的加密和解密
- Reverse Linked List II
- python爬虫(六)_urllib2:handle处理器和自定义opener
- Hbase多版本的读写(Shell&Java API版)
- 总结---3
- Mac系统实现git命令自动补全
- [大数据之Yarn]——资源调度浅学
- Mac系统的终端显示git当前分支
- 不掌握这几个人工智能编程语言怎么能说懂AI
- Gulp实现css、js、图片的压缩以及css、js文件的MD5命名
- 大数据之Yarn——Capacity调度器概念以及配置
- 移动端web开发,click touch tap区别
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 卷积神经网络之 - Alexnet
- Linux系列之学会使用Top命令进行系统监控
- 一简单线程同步笔试题分享,欢迎纠错分享更多思路
- 卷积神经网络之-NiN 网络(Network In Network)
- 【注意力机制】空间注意力机制之Spatial Transformer Network
- 【小技巧】用Python给你的视频添加字幕
- 震惊!我三步就搞定了 Tomcat 源码环境搭建!
- 【小技巧】深度学习中的那些效率提升利器(附资源)
- 多个线程为了同个资源打起架来了,操作系统是如何让他们安分的?
- 学习链表,这些题你值得一刷!
- Gophish钓鱼测试
- 【位运算】只出现一次的数字 II,数电的知识终于用上了!
- Swaks伪造邮件发件人绕过SPF
- 某次网站的渗透测试
- phpMyAdmin 渗透利用总结