一個C程序可能是由多個分別編譯的部分組成,這些不同部分通過一個通常叫做鏈接器(或連接器,載入器)的程序合并成一個整體。因為編譯器一般每次只處理一個文件,所以它不能檢測出那些需要一次了解多個源程序文件才能察覺的錯誤。而且,在許多系統中鏈接器是獨立于C語言實現的,因此如果前述錯誤的原因與C語言相關,鏈接器對此也同樣束手無策。某些C語言實現提供了一個稱為lint的程序,可以捕獲到大量的此類錯誤,但遺憾的是并非全部的C語言實現都提供了該程序。如果能夠找到諸如lint的程序,就一定善加利用,這一點無論怎么強調都不為過。
? ? ? ? C語言中的一個重要思想就是分別編譯(Separate Compilation),即若干個源程序可以在不同的時候單獨進行編譯,然后在恰當的時候整合到一起。但是,鏈接器一般是與C編譯器分離的,它不可能了解C語言的諸多細節。那么,鏈接器是如何做到把若干個C源程序合并成一個整體呢?盡管鏈接器并不理解C語言,然而它卻能夠理解機器語言和內存布局。編譯器的責任就是把C源程序“翻譯”成對鏈接器有意義的形式,這樣鏈接器就能夠“讀懂”C源程序了。
? ? ? ? 典型的鏈接器把有編譯器或匯編器生成的若干個目標模塊,整合成一個被稱為載入模塊或可執行文件的實體,該實體能夠被操作系統直接執行。其中,某些目標模塊是直接作為輸入提供給鏈接器的;而另外一些目標模塊則是根據鏈接過程的需要,從包括有類似printf函數的庫文件中取得的。鏈接器通常把目標模塊看成是一組外部對象(external object)組成的。每個外部對象代表著機器內存中的某個部分,并通過一個外部名稱來識別。因此,程序中的每個函數和每個外部變量,如果沒有聲明為static,就都是一個外部對象。某些C編譯器會對靜態函數和靜態變量的名稱做一定改變,將他們也作為外部對象。由于經過了“名稱修飾”,所以他們不會與其它原程序文件中的同名函數或同名變量發生命名沖突。
? ? ? ? 大多數鏈接器都禁止同一個載入模塊中的兩個不同外部對象擁有相同的名稱。然而,在多個目標模塊整合成一個載入模塊時,這些目標模塊可能就包含了同名的外部對象。鏈接器的一個重要工作就是處理這類命名沖突。處理命名沖突的最簡單的方法就是干脆完全禁止。對于外部對象是函數的情形,這種做法當然正確,一個程序如果包括兩個同名的不同函數,編譯器根本就不應該接受。而對于外部對象是變量的情形,問題就變得有些困難了。不同的鏈接器對這種情形有著不同的處理方式。
? ? ? ??鏈接器的輸入是一組目標模塊或者庫文件。鏈接器的輸出是一個載入模塊。鏈接器讀入目標模塊和庫文件,同時生成載入模塊。對每個目標模塊中的每個外部對象,鏈接器要檢查載入模塊,看是否已有同名的外部對象。如果沒有,鏈接器就將該外部對象添加到載入模塊中;如果有,鏈接器就要開始處理命名沖突。
? ? ? ? 除了外部對象之外,目標模塊中還可能包括了對其他模塊中的外部對象的引用。例如,一個調用了函數printf的C程序所生成的目標模塊,就包括了一個對函數printf的引用??梢酝茰y得出,該引用指向的是一個位于某個庫文件中的外部對象。在鏈接器生成載入模塊的過程中,它必須同時記錄這些外部對象的引用。當鏈接器讀入一個目標模塊時,它必須解析出這個目標模塊中定義的所有外部對象的引用,并作出標記說明這些外部對象不再是未定義的。
?
一個c程序可能是由多個分別編譯的部分組成,這些不同部分通過連接器合并成一個整體。因為編譯器一般每次只處理一個文件,所以吧能檢測出那些需要一次了解多個源程序文件才能察覺的錯誤。某些c程序實現提供了一個稱為lint的程序,可以捕獲大量的此類錯誤,但不是所有的都能捕獲到!
評論
查看更多