寫庫函數供他人用的原理是什麼?

c最終編譯鏈接後的代碼是不保留方法名的,那麼編譯完成的代碼不可以直接拿來作為我的庫文件給其他人使用的。這樣理解對嗎?

我搜索過這個問題,但是沒找到答案。按照搜索的結果,打成lib包只需要gcc加-c開關,然後用ar打包。那是否說明編譯未鏈接前編譯的代碼是保留方法名的。那像微軟的系統api都是只編譯、打包就提供給我們用的?但是,不經鏈接程序,微軟自己的代碼也不能運行吧?所以,我的猜測應該是錯的,那該怎樣理解?


理解的重點就在於「符號表」(symbol table)。之前回答過一個略微相關的問題,C 語言程序變數作用域的實現機制是什麼? - RednaxelaFX 的回答

那個問題主要關注的是符號表對局部變數的體現,而題主這個問題關注的是符號表在函數層面的體現。

舉個例子:C語言在極限情況下可以「裸奔」——完全不依賴於任何外部的運行時庫,編譯出來的就是單一、獨立的可執行文件。正因為如此,它可以方便的用於寫直接跑在裸硬體上的程序,例如操作系統自身。

但一般用C寫應用層面的程序,多少還是得依賴一些外部庫的。最常見的情況之一就是與C Runtime Library(CRT)動態鏈接。為了能做到這點,CRT作為動態鏈接庫自然要提供鏈接用的符號信息,而應用程序也需要提供鏈接用的符號信息說明自己依賴於哪些符號。

於是就帶回到題主的問題了。C寫的程序,編譯出來的目標文件,在(靜態-)鏈接前自然帶有所有鏈接用的符號信息,而在靜態鏈接後仍然可能帶有動態鏈接用的符號信息。

目標文件里,鏈接/載入相關的符號信息表大致有三種:

  • 導入表(import table):描述這個目標文件依賴於哪些符號是由外部提供實現的;
  • 導出表(export table):描述這個目標文件向外部提供哪些符號的實現;
  • 重定位表(relocation table):在生成可重定位代碼(relocatable code / position independent code (PIC))時,描述重定位後需要修正的東西的偏移量。

題主問到微軟的情況,接下來稍微提一下Windows上是怎麼做的。

MSVC編譯程序時可以生成可執行文件或動態鏈接庫。這是最終產物,中間步驟會涉及靜態庫文件(static library,.lib文件)。

例如說,一個程序假如有a.c、b.c和c.c三個源文件,分別編譯然後打包成一個「東西」,那麼中間步驟就會有a.obj、b.obj和c.obj三個目標文件,可以配置為打包成:

  • 一個靜態鏈接用的靜態庫文件(static library,.lib文件),這基本上就是把三個.obj文件打包在了一起而已
  • 一個動態鏈接庫(.dll)以及對應的一個導入庫文件(import library,同樣是.lib文件)
  • 一個可執行文件(.exe)

題主要導出靜態鏈接用的符號不用做啥特別的事,但要導出動態鏈接用的符號的話,在源碼里函數聲明處要加上__declspec(dllexport),或者是用.def文件來指定導出符號。

這裡,靜態庫文件與動態鏈接庫文件可能比較好理解,其包含的符號信息肯定要能滿足靜態/動態鏈接器對導入/導出信息的需要。但是配合動態鏈接庫的那個「導入庫文件」又是啥?

其實一個「導入庫文件」也是一個靜態庫文件,它包含的樁代碼(stub)會通過IAT(Import Address Table)調用位於DLL文件里的實際實現,而它包含的符號信息正好對應於配套的DLL文件。

例如說,一個C寫的程序通過MSVC工具鏈編譯與鏈接,要與MSVC的CRT(例如msvcrt90.dll)動態鏈接的話,可以在靜態鏈接時與配套的import library(例如msvcrt90.lib)鏈接起來,這樣得到的鏈接後代碼就有足夠符號信息和stub代碼去在運行時正確調用動態鏈接庫的函數。

以上這種在靜態鏈接時使用import library的做法,在MSDN的文檔上叫做implicit linking,又名load-time dynamic linking。

相對應的,還有explicit linking,又名run-time dynamic linking。這種用法不需要使用import library,而是調用方自己顯式調用LoadLibrary()載入DLL文件,然後用GetProcAddress()找到對應名字的函數的地址並對其調用,最後用完調用FreeLibrary()去釋放DLL文件。

請參考文檔:Linking an Executable to a DLL

要是想弄load-time dynamic linking但是又不想依賴import library的話,其實在程序里 __declspec(dllimport) 也是可以的。事實上這樣生成的代碼還略微快一些(少了一次stub跳轉)。


推薦閱讀《程序員的自我修養》


花點時間用一下readelf命令,了解下幾個主要的section是幹嘛的,基本就明白了。


命令strings,

取出二進位文件中的字元串,

對庫文件用了發現有函數名,

裡面是什麼結構就先別管了,

知道函數名還在就行了,


這年頭髮布C的靜態庫都不帶頭文件了嗎???


都是一些黑黑的手段啊


我覺得 RednaxelaFX寫的答案非常好了。

但是如果你想對這個有更多興趣,去單片機,mcu領域。

這是入門必知的知識。我們經常看編譯出來的符號表。

arm 的重定位,ram運行,玩過,這些都自然回答了。


推薦閱讀:

C語言編譯中,如何查看定義了哪些宏?
如果要寫一個類似於現在Word的軟體,僅用C語言或者C++,可以完成嗎?
清華大學譚浩強的C語言書籍怎麼樣?
譚浩強的 C 語言技術真很牛嗎?他寫過哪些厲害的程序?
為什麼printf("%ld
", -2147483648/1000000000);得到的結果是2而不是-2?

TAG:C編程語言 | 編譯原理 | 動態鏈接庫 |