明白了聲明與定義的區別,還需要明白 內部鏈接、外部鏈接。只有明白了它們你纔會知道開頭提出的問題。
在編譯時,編譯器只檢測程序語法和函數、變量是否被聲明。如果函數未被聲明,編譯器會給出一個警告,但可以生成目標文件。而在鏈接程序時,鏈接器會在所有的目標文件中找尋函數的實現。如果找不到,那到就會報鏈接錯誤碼(Linker Error)。在VC下,這種錯誤一般是:Link 2001錯誤,意思說是說,鏈接器未能找到函數的實現。
鏈接把不同編譯單元產生的符號聯繫起來。有兩種鏈接方式:內部鏈接和外部鏈接。如果一個符號名對於它的編譯單元來說是局部的,並且在鏈接時不可能與其他編譯單元中的同樣的名稱相沖突,那個這個符號就是內部鏈接。內部鏈接意味着對此符號的訪問僅限於當前的編譯單元中,對其他編譯單元都是不可見的。
static關鍵字作用在全局變量時,表示靜態全局變量。但是作用域僅僅在當前文件作用域內。其他文件中即使使用extern聲明也是無法使用的。const也類似。
帶有static、const關鍵字和枚舉類型的連接是內部的。
具有內部鏈接的符號無法作用於當前文件外部,要讓其影響程序的其他部分,可以將其放在.h文件中。此時在所有包含此.h文件的源文件都有自己的定義且互不影響。類的定義具有內部鏈接,由於它是定義,因此在同一編譯單元中不能重複出現。如果需要在其他編譯單元使用,類必須被定義在頭文件且被其他文件包含。僅僅在其他文件中使用class a;聲明是不行的,原因就是類的定義是內部鏈接,不會在目標文件導出符號。也就不會被其他單元解析它們的未定義符號。理解這一點很重要。
內聯函數也具有內部鏈接。
在一個多文件的程序中,如果一個符號在鏈接時可以和其他編譯單元交互,那麼這個名稱就有外部鏈接。外部鏈接意味着該定義不僅僅侷限在單個編譯單元中。它可以在.o文件中產生外部符號。可以被其他編譯單元訪問用來解析它們未定義的符號。因此它們在整個程序中必須是唯一的,否則將會導致重複定義。
非內聯成員函數、非內聯函數、非靜態自由函數都具有外部鏈接。
內聯函數之所有具有內部鏈接,因爲編譯器在可能的時候,會將所有 對函數的調用替換爲函數體,不將任何符號寫入.o文件。
判斷一個符號是內部鏈接還是外部鏈接的一個很好的方法就是看該符號是否被寫入.o文件。
前面說的是定義對鏈接方式的影響,接下來說下聲明對鏈接方式的影響。由於聲明只對當前編譯單元有用,因此聲明並不將任何東西寫入.o文件。 如extern int a;
int func();這些聲明本身不會影響到.o文件的內容。每一個都只是命名一個外部符號,使當前的編譯單元在需要的時候可以訪問相應的全局定義。 函數調用會導致一個未定義的符號被寫入到.o文件。如果a在該文件中沒有被使用,那麼沒有被寫入到.o文件。而func函數有對此函數的調用。也就會將此符號寫入目標文件。此後此.o文件與定義此符號的.o文件被連接在一起,前面未定義的符號被解析。類聲明和類定義都是內部鏈接。只是爲當前編譯單元所用。