你曾經碰到的問題:
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記錄了以下的資訊
然後在連結器連接配接的時候就會知道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.宏是内部連結還是外部連結
答:都不是,宏在預處理環節時就被替換掉了,而内部連結與外部連結是針對編譯環節與連結環節而言的
Created by 黃強