為什麼有時會出現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)的定義,就會出現無法解析的外部符号的錯誤