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記錄了以下的信息

image

 

然后在鏈接器連接的時候就會知道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)的定義,就會出現無法解析的外部符號的錯誤

 

宏是內部鏈接還是外部鏈接

答:都不是,宏在預處理環節時就被替換掉了,而內部鏈接與外部鏈接是針對編譯環節與鏈接環節而言的


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



C++編譯與鏈接(1) c++編譯與鏈接 C++編譯與鏈接(0) c++編譯鏈接不對 C++編譯與鏈接 C、C++編譯,鏈接,extern鏈接 C++編譯鏈接錯誤 C++編譯和鏈接原理 c++編譯鏈接模板 C++ 模板的編譯與鏈接
 
粤ICP备14056181号  © 2014-2021 ITdaan.com