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

205 篇文章 38 订阅 ¥9.90 ¥99.00
本文介绍了C++中的编译单元概念,解释了内部链接和外部链接的区别,以及为何会出现重定义错误、外部符号无法解析等问题。并讨论了内联函数、模板声明与定义的位置要求,以及宏的链接性质。
摘要由CSDN通过智能技术生成

你曾经碰到的问题: 

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 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<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.宏是内部链接还是外部链接

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




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值