1. 程式人生 > >與C/C++關鍵字extern有關的原理

與C/C++關鍵字extern有關的原理

為什麽 拒絕 方式 明顯 想要 概念 編譯 純c 嚴重

關鍵字有一定的語義,但是用法不唯一。

對於C/C++語言的預編譯、編譯、匯編、鏈接。我相信大家在接觸C++一年不到就背的滾瓜爛熟,但是其中的細節,是後來才慢慢想明白的。為什麽我不講extern關鍵字呢?extern關鍵字的淵源深著呢,耐心學完前面的內容,extern的神秘面紗自然就解開了。

眾所周知,C語言的出現先於C++,而匯編語言的出現又先於C。但是不管你用它們中任何一門語言編寫程序。編譯後都生成一個可執行的程序(前提是代碼沒有語法錯誤)。

對於使用匯編寫好的程序,我們只需要把匯編源代碼交給匯編器,匯編器就輸出可執行文件給我們。

對於C語言寫好的程序,我們把C源代碼交給預處理器,預處理器把C源代碼中的宏“消化”掉,生成純C源代碼,然後把純C代碼交給編譯器,編譯器輸出相應的匯編代碼,之後的處理方式就跟匯編寫的一樣了,交給匯編器,匯編代碼與最後的可執行代碼可以說是一一對應的。使用C比使用匯編方便,因為編譯器為我們做了很多固定的事情,但是前提是你必須明白編譯器的原理,不然就亂套了。

隨著軟件規模的發展,後來的人們發現 有的軟件功能太多了,全部放在一個main.c文件中真的太大了,邏輯混亂,不好讀。另外還有更重要的一點,在A程序中寫過的一個功能,在B程序中又要使用同樣的功能,需要去A的源代碼裏面找到,復制過來,復制錯了還麻煩。所以出現了一個重要的概念,“多文件編譯”:每一個.c文件經過一個編譯流程都能生成一個目標文件。即使.c文件沒有main函數,也能生成一個目標文件。這裏我們約定,沒有main函數的.c文件編譯出來的叫做目標文件,含有main函數的.c文件編譯出來的叫做待鏈接可執行文件。一個目標文件可以調用其他任何一個目標文件中定義的函數代碼、變量,前提是另外一個目標文件“允許”,如何允許或者拒絕呢?這裏就靠我們使用extern和static、auto這類關鍵詞控制了,如何控制我們之後再細談。

鏈接器就是這個時候產生的,一個待鏈接可執行文件+n個相關的目標文件=可執行文件,這裏的n可以等於0。這樣就形成了多文件編譯,雖然過程復雜了點,但是把一個main.c分割成多個源文件,解決了上面說到的兩個嚴重問題。當然你不喜歡鏈接也可以不鏈接,把所有內容寫在main.c裏就不需要鏈接過程了。我們想要編寫應用程序時,第一件事就是創建一個.c文件,在裏面寫一個main函數,然後開始編寫main函數。我們在main函數中可以調用一些其他的函數,這些函數可以不跟main函數在一個源文件裏,註意,不在同一個源文件也就意味著不在同一個目標文件裏了,不在同一個目標文件,就需要跨目標文件調用,這是鏈接器的工作,我們只需要把main函數可能用到的所有目標文件丟給鏈接器,鏈接器會根據函數名和調用關系把他們鏈接生成一個完整的可執行文件。所有的目標文件都可以互相調用,但是我們在編寫程序時,大多數這個調用關系是單向的,最後匯總到main函數。如果一個函數(不管在哪個文件裏)沒有被main函數直接或者間接的使用,那他就沒有被鏈接的必要,鏈接器在鏈接的時候就不把他鏈接進可執行文件裏。

多文件編譯 全靠 鏈接器 支持,鏈接器怎麽這麽神奇?它為什麽知道要怎麽鏈接?哈哈,其實鏈接器並沒有你想的那麽神奇,它並不是憑空就把目標文件鏈接起來了。在它鏈接之前,我們需要給它留下一些記號、給它一些提示。要是提示的不明顯、有二義性,它就立即報錯讓你鏈接失敗,這一點想必經歷過多文件編譯的人都深有體會。有語法錯誤編譯器會提示我語法錯誤在第幾行。但是有時明明沒有語法錯誤,鏈接器還給我報滿滿一屏幕的編譯錯誤,而且還不告訴我在第幾行,這個就是鏈接錯誤。鏈接錯誤很簡單,所有的鏈接錯誤都是鏈接器在說:根據你給我留下的提示,我找不到你想要調用的函數在哪,或者我找到了好多個可行選擇,不知道選哪個。。。

接下來重點來了,我告訴你如何提示鏈接器正確的鏈接。接下來我會用到3個關鍵詞extern,static,auto。

純C語言源代碼由全局函數和全局變量組成(局部變量總是包含在全局函數裏,算是全局函數的子集,就不作數了),對於函數一共有這3種寫法:int fun(); static int fun(); extern int fun()。對於變量一共有4種寫法:int val; auto int val; static int val; extern int val;

額,為什麽不對稱呢,函數為什麽不能auto int fun();呢?

接下來我說幾個C鏈接器的遊戲規則:

寫int fun();和寫extern int fun();是一樣的意思。意思是:這個函數可以讓其他目標文件調用。而static int fun();的意思相反,不讓其他目標文件看到這個函數,讓他們在鏈接時找不到,只有在本文件內才能調用static修飾的函數。對於函數,就這麽兩個規則。

對於全局變量,寫int val;和auto int val;意思一樣,都是讓其他源文件可以訪問。而寫static int val;意思就是只能在本文件內部使用。寫extern int val;的意思就是:val變量不是在這裏產生的,而是其他文件定義的,我想調用它,事先聲明一下,這就叫做擴展聲明。而函數就不需要擴展聲明,因為沒有函數體的函數都是函數聲明,所以不需要留有關鍵詞做提示。

現在大概明白這三個關鍵詞的語義了吧。static最直當明了,沾到它別的文件就別想訪問了。而auto幾乎沒什麽用,幾乎沒人會在這裏用auto(c++11中auto關鍵詞可以用來讓編譯器自動推導出類型,使代碼簡潔)。

再後來,有了C++這門語言,編譯器為我們做了更多的事情,比C編譯器做的還多。最簡單的,重載函數,為了支持重載函數,編譯器會在編譯時為它們重命名。這時如果你不知道C++的遊戲規則,可能又會使鏈接器找不到鏈接對象了。

與C/C++關鍵字extern有關的原理