1. 程式人生 > >C++內部連結與外部連結

C++內部連結與外部連結

你曾經碰到的問題: 

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.巨集是內部連結還是外部連結

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

Created by 黃強