1. 程式人生 > >內部連結和外部連結

內部連結和外部連結

首先,我們來了解下定義:

內部連線:如果一個名稱對編譯單元(.cpp)來說是區域性的,在連結的時候其他的編譯單元無法連結到它。

外部連線:如果一個名稱對編譯單元來說不是區域性的,而在連結的時候其他的編譯單元可以訪問它,也就是說它可以和別的編譯單元互動。

通過對LIB與DLL的講解,我們可以更方便的理解內部連線與外部連線。

我們瞭解了一個編譯單元(.cpp)編譯成obj檔案後,至少還會有未解決符號表、匯出符號表、地址重定向表。而如果這個名稱是內部連線的話,那在匯出符號表中不儲存它的入口。也就是別的obj檔案無法連結到這個名稱。而外部連線剛好相反,在匯出的符號表中有它入口。

以下情況有內部連線: 內部連結要是實現在自己內部,那麼每次呼叫都每次實現,不會分不清!
a)所有的宣告
b)名字空間(包括全域性名字空間)中的靜態自由函式、靜態友元函式、靜態變數的定義
c)enum定義
d)inline函式定義(包括自由函式和非自由函式)
e)類的定義
f)名字空間中const常量定義
g)union的定義

以下情況是外部連線: 外部連結就必須要寫到一個cpp檔案內,這樣會出在一個匯出表中,其他函式比如main函式可以找到入口進行應用。如果是多個cpp檔案那麼就不知道入口在哪裡! 不確定是那個cpp檔案的

a)非static全域性變數與全域性函式

b)類非inline函式總有外部連線。包括類成員函式和類靜態成員函式
c)類靜態成員變數總有外部連線
d)名字空間(包括全域性名字空間)中非靜態自由函式、非靜態友元函式及非靜態變數

好了,我們通過程式來深刻的理解吧:

假設有3檔案:

TestBase.h:                       TestRun.cpp                          TestError.cpp

int a;                                    #include "TestBase.h"            #include "TestBase.h"

當然還有一個包含main()方法的Test.cpp輸出的檔案。

#include "TestBase.h"

#include <iostream>  

extern int a;

void main()

{

    std::cout << a<<std::endl;

}

分別編譯TestRun與TestError我相信大家都能通過編譯,但是你連結的時候肯定會出錯的,提示的資訊有一句為:Debug/Test.exe : fatal error LNK1169: one or more multiply defined symbols found(一個或多個定義符號被發現)。因為非static的全域性變數是外部連線的,其實也就是說TestRun.obj與TestError .obj的匯出符號表中都對a匯出了資訊入口(別問我為什麼匯出了它,因為編譯器預設對非static全域性變數都匯出了,想知道怎麼實現的,可以去找Microsoft)。而當我的Test.cpp中要用到a時我到底是用TestRun.obj還是TestError .obj中匯出

符號表中的a呢?所以連結肯定會出錯的。

我們在嘗試著把TestBase.h中的全域性變數a改了static。馬上能編譯與連結成功,並輸出0,其實是因為static全部變數是內部連線的,obj檔案的匯出符號表中沒有提供a符號的入口。而Test.cpp用到的是自己在編譯的時候得到的a的資訊,也就是TestBase.h檔案中a的預設值0.

函式的性質也是一樣的,所以大家只需要知道你宣告或定義的名稱是內部連線還是外部連線,如TestBase.h中的非全域性變數a是外部連線。你就會明白語法其實也就那麼回事了。如何讓標頭檔案的變數被一個和多個cpp檔案所引用!通過內部連結!

讓我們在來了解class裡的static變數和非static函式是什麼樣子的。

class是內部連線的,這就是為什麼可以有多個cpp檔案能包含它的原因了,但是如果我在class裡寫了個static變量了,那這個變數就是外部連線了。而內中的非static函數了是外部連線。所以針對class我總結了下3點必須:

(1)。類中的static變數請不要在宣告類中定義。一般我們類的宣告是寫到.h檔案中,而實現寫到對應的cpp檔案中的,理由看程式。

還是剛才3個檔案:

TestBase.h:                                        TestRun.cpp                                       TestError.cpp

class A                                               #include "TestBase.h"                        #include "TestBase.h" 
{                                                             int A::getA()這兒如何寫函式實現的話,那麼就不行!
public:                                                  {
     void setA( int a );                            return m_a;
                 int getA();                            }
                 inline int getB();                  void A::setA( int a )
private:                                                  {
                int m_a;                                              m_a = a;
                static int m_b;                        }
};

int A::m_b = 5;

編譯都成功後,連結會出現如下資訊:

TestError.obj : error LNK2005: "private: static int A::m_b" (?m_b@A@@0HA) already defined in Test.obj
TestRun.obj : error LNK2005: "private: static int A::m_b" (?m_b@A@@0HA) already defined in Test.obj
Debug/Test.exe : fatal error LNK1169: one or more multiply defined symbols found

也就是說類中的static變數m_b為外部連線,理由同上面寫的全域性變數a一樣。解決辦法是把int A::m_b = 5;寫到類的實現檔案TestRun.cpp 中,這樣就只有TestRun.obj檔案的匯出符號表中提供了m_b,而其他的obj檔案如果需要的話,那這隻能在它的未解決符號表中存在了,它自然只能在TestRun.obj檔案中找到m_b的入口,這樣就無任何衝突了。所以類中的static變數請一定不要在宣告類中定義。

(2)。內中的非static函式請一定也不要在宣告類實現,除非你的宣告和實現是寫到一起的。

TestBase.h:                                           TestRun.cpp                                       TestError.cpp

class A                                                  #include "TestBase.h"                          #include "TestBase.h" 
{                                                             int A::getA()
public:                                                  {
     void setA( int a );                        return m_a;   
                 int getA();                            }
                 inline int getB();                  int  A::m_b = 6;
private:                                                  
                int m_a;         

                 static int m_b;                                                                
}; 

void    A::setA( int a )

{

    m_a = a;

}          

編譯通過,連結後肯定不能通過,原因都一樣,就是因為類中的非static函式是外部連結。而如果你把定義和宣告寫到一起,那就沒問題,但你應該知道這樣寫就是相當於出賣了自己,你把你的實現程式碼都給了別人。

(3)inline函式請一定要在類的宣告檔案中實現。大家應該看到類中的inline函式我沒有寫它的實現程式碼,其實也就是為了這條定義一樣。你可以檢視VC提供的標頭檔案中inline的定義與實現都是寫在標頭檔案中的。

所以那個inline函式你必須寫在TestBase.h內。        

inline int A::getB()
{
         return m_b; 
}            

為什麼這樣呢?因為inline函式是內部連線,它不在匯出符號表。假如你把inline int getB()的實現程式碼寫在了 TestRun.cpp中,那連結後的錯誤資訊是:

Debug/Test.exe : fatal error LNK1120: 1 unresolved externals

也就是別的cpp檔案就無法用到找到它了。

好了,其實很多東西如果是在連結時候出錯了,或者為什麼語法這麼實現的,你可以從它是內部連線還是外部連結入手,你會發現很多規律以及規則的存在,這樣你的編寫的C++的程式碼安全性一定會大大的提高。相信我!