用 C 語言理解 Linux 軟件庫

軟件庫是重復使用代碼的一種簡單而合理的方式扫尺。

軟件庫是一種是一直以來長期存在的品山、簡單合理的復用代碼的方式局雄。這篇文章解釋了如何從頭開始構建庫并使得其可用紧唱。盡管這兩個示例庫都以 Linux 為例,但創(chuàng)建灰粮、發(fā)布和使用這些庫的步驟也可以應用于其它類 Unix 系統(tǒng)仔涩。

這些示例庫使用 C 語言編寫,非常適合該任務粘舟。Linux 內(nèi)核大部分由 C 語言和少量匯編語言編寫(Windows 和 Linux 的表親如 macOS 也是如此)熔脂。用于輸入/輸出、網(wǎng)絡柑肴、字符串處理霞揉、數(shù)學、安全晰骑、數(shù)據(jù)編碼等的標準系統(tǒng)庫等主要由 C 語言編寫适秩。所以使用 C 語言編寫庫就是使用 Linux 的原生語言來編寫。除此之外些侍,C 語言的性能也在一眾高級語言中鶴立雞群。

還有兩個來訪問這些庫的示例<ruby style="box-sizing: border-box;">客戶程序<rt style="box-sizing: border-box;">client</rt></ruby>(一個使用 C政模,另一個使用 Python)岗宣。毫無疑問可以使用 C 語言客戶程序來訪問 C 語言編寫的庫,但是 Python 客戶程序示例說明了一個由 C 語言編寫的庫也可以服務于其他編程語言淋样。

靜態(tài)庫和動態(tài)庫對比

Linux 系統(tǒng)存在兩種類型庫:

  • 靜態(tài)庫(也被稱為歸檔庫):在編譯過程中的鏈接階段耗式,靜態(tài)庫會被編譯進程序(例如 C 或 Rust)中。每個客戶程序都有屬于自己的一份庫的拷貝趁猴。靜態(tài)庫有一個顯而易見的缺點 —— 當庫需要進行一定改動時(例如修復一個 bug)刊咳,靜態(tài)庫必須重新鏈接一次。接下來要介紹的動態(tài)庫避免了這一缺點儡司。
  • 動態(tài)庫(也被稱為共享庫):動態(tài)庫首先會在程序編譯中的鏈接階段被標記娱挨,但是客戶程序和庫代碼在運行之前仍然沒有聯(lián)系,且?guī)齑a不會進入到客戶程序中捕犬。系統(tǒng)的動態(tài)加載器會把一個共享庫和正在運行的客戶程序進行連接跷坝,無論該客戶程序是由靜態(tài)編譯語言(如 C)編寫,還是由動態(tài)解釋語言(如 Python)編寫碉碉。因此柴钻,動態(tài)庫不需要麻煩客戶程序便可以進行更新。最后垢粮,多個客戶程序可以共享同一個動態(tài)庫的單一副本贴届。

通常來說,動態(tài)庫優(yōu)于靜態(tài)庫,盡管其復雜性較高而性能較低毫蚓。下面是兩種類型的庫如何創(chuàng)建和發(fā)布:

  1. 庫的源代碼會被編譯成一個或多個目標模塊占键,目標模塊是二進制文件,可以被包含在庫中并且鏈接到可執(zhí)行的二進制中绍些。
  2. 目標模塊會會被打包成一個文件捞慌。對于靜態(tài)庫,標準的文件拓展名是 .a 意為歸檔柬批;對于動態(tài)庫啸澡,標準的文件拓展名是 .so 意為共享目標shared object。對于這兩個相同功能的示例庫氮帐,分別發(fā)布為 libprimes.a (靜態(tài)庫)和 libshprimes.so (動態(tài)庫)嗅虏。兩種庫的文件名都使用前綴 lib 進行標識。
  3. 庫文件被復制到標準目錄下上沐,使得客戶程序可以輕松地訪問到庫皮服。無論是靜態(tài)庫還是動態(tài)庫,典型的位置是 /usr/lib 或者 /usr/local/lib参咙,當然其他位置也是可以的龄广。

構建和發(fā)布每種庫的具體步驟會在下面詳細介紹。首先我將介紹兩種庫里涉及到的 C 函數(shù)蕴侧。

示例庫函數(shù)

這兩個示例庫都是由五個相同的 C 函數(shù)構建而成的择同,其中四個函數(shù)可供客戶程序使用。第五個函數(shù)是其他四個函數(shù)的一個工具函數(shù)净宵,它顯示了 C 語言怎么隱藏信息敲才。每個函數(shù)的源代碼都很短,可以將這些函數(shù)放在單個源文件中择葡,盡管也可以放在多個源文件中(如四個公布的函數(shù)都有一個文件)紧武。

這些庫函數(shù)是基本的處理函數(shù),以多種方式來處理質(zhì)數(shù)敏储。所有的函數(shù)接收無符號(即非負)整數(shù)值作為參數(shù):

  • is_prime 函數(shù)測試其單個參數(shù)是否為質(zhì)數(shù)阻星。
  • are_coprimes 函數(shù)檢查了其兩個參數(shù)的最大公約數(shù)greatest common divisor(gcd)是否為 1,即是否為互質(zhì)數(shù)已添。
  • prime_factors:函數(shù)列出其參數(shù)的質(zhì)因數(shù)迫横。
  • glodbach:函數(shù)接收一個大于等于 4 的偶數(shù),列出其可以分解為兩個質(zhì)數(shù)的和酝碳。它也許存在多個符合條件的數(shù)對矾踱。該函數(shù)是以 18 世紀數(shù)學家 克里斯蒂安·哥德巴赫Christian Goldbach 命名的,他的猜想是任意一個大于 2 的偶數(shù)可以分解為兩個質(zhì)數(shù)之和疏哗,這依舊是數(shù)論里最古老的未被解決的問題呛讲。

工具函數(shù) gcd 留在已部署的庫文件中,但是在沒有包含這個函數(shù)的文件無法訪問此函數(shù)。因此贝搁,一個使用庫的客戶程序無法調(diào)用 gcd 函數(shù)吗氏。仔細觀察 C 函數(shù)可以明白這一點。

更多關于 C 函數(shù)的內(nèi)容

每個在 C 語言中的函數(shù)都有一個存儲類雷逆,它決定了函數(shù)的范圍弦讽。對于函數(shù),有兩種選擇膀哲。

  • 函數(shù)默認的存儲類是 extern往产,它給了函數(shù)一個全局域。一個客戶程序可以調(diào)用在示例庫中用 extern 修飾的任意函數(shù)某宪。下面是一個帶有顯式 extern 聲明的 are_coprimes 函數(shù)定義:
    1. extern unsigned are_coprimes(unsigned n1, unsigned n2) {
    2. ...
    3. }
  • 存儲類 static 將一個函數(shù)的的范圍限制到函數(shù)被定義的文件中仿村。在示例庫中,工具函數(shù) gcd 是靜態(tài)的(static):
    1. static unsigned gcd(unsigned n1, unsigned n2) {
    2. ...
    3. }

只有在 primes.c 文件中的函數(shù)可以調(diào)用 gcd兴喂,而只有 are_coprimes 函數(shù)會調(diào)用它蔼囊。當靜態(tài)庫和動態(tài)庫被構建和發(fā)布后,其他的程序可以調(diào)用外部的(extern)函數(shù)衣迷,如 are_coprimes 畏鼓,但是不可以調(diào)用靜態(tài)(static)函數(shù) gcd。靜態(tài)(static)存儲類通過將函數(shù)范圍限制在其他庫函數(shù)內(nèi)壶谒,進而實現(xiàn)了對庫的客戶程序隱藏 gcd 函數(shù)云矫。

primes.c 文件中除了 gcd 函數(shù)外,其他函數(shù)并沒有指明存儲類佃迄,默認將會設置為外部的(extern)泼差。然而贵少,在庫中顯式注明 extern 更加常見呵俏。

C 語言區(qū)分了函數(shù)的<ruby style="box-sizing: border-box;">定義<rt style="box-sizing: border-box;">definition</rt></ruby>和<ruby style="box-sizing: border-box;">聲明<rt style="box-sizing: border-box;">declaration</rt></ruby>,這對庫來說很重要滔灶。接下來讓我們開始了解定義普碎。C 語言僅允許命名函數(shù)不允許匿名函數(shù),并且每個函數(shù)需要定義以下內(nèi)容:

  • 一個唯一的名字录平。一個程序不允許存在兩個同名的函數(shù)麻车。
  • 一個可以為空的參數(shù)列表。參數(shù)需要指明類型斗这。
  • 一個返回值類型(例如:int 代表 32 位有符號整數(shù))动猬,當沒有返回值時設置為空類型(void)。
  • 用一對花括號包圍起來的函數(shù)主體部分表箭。在一個特制的示例中赁咙,函數(shù)主體部分可以為空。

程序中的每個函數(shù)必須要被定義一次。

下面是庫函數(shù) are_coprimes 的完整定義:

  1. extern unsigned are_coprimes(unsigned n1, unsigned n2) { /* 定義 */
  2. return 1 == gcd(n1, n2); /* 最大公約數(shù)是否為 1? */
  3. }

函數(shù)返回一個布爾值(0 代表假彼水,1 代表真)崔拥,取決于兩個整數(shù)參數(shù)值的最大公約數(shù)是否為 1。工具函數(shù) gcd 計算兩個整數(shù)參數(shù) n1n2 的最大公約數(shù)凤覆。

函數(shù)聲明不同于定義链瓦,其不需要主體部分:

  1. extern unsigned are_coprimes(unsigned n1, unsigned n2); /* 聲明 */

聲明在參數(shù)列表后用一個分號代表結束,它沒有被花括號包圍起來的主體部分盯桦。程序中的函數(shù)可以被多次聲明慈俯。

為什么需要聲明?在 C 語言中俺附,一個被調(diào)用的函數(shù)必須對其調(diào)用者可見肥卡。有多種方式可以提供這樣的可見性,具體依賴于編譯器如何實現(xiàn)事镣。一個必然可行的方式就是當它們二者位于同一個文件中時步鉴,將被調(diào)用的函數(shù)定義在在它的調(diào)用者之前。

  1. void f() {...} /* f 定義在其被調(diào)用前 */
  2. void g() { f(); } /* ok */

當函數(shù) f 被在調(diào)用前聲明璃哟,此時函數(shù) f 的定義可以移動到函數(shù) g 的下方氛琢。

  1. void f(); /* 聲明使得函數(shù) f 對調(diào)用者可見 */
  2. void g() { f(); } /* ok */
  3. void f() {...} /* 相較于前一種方式,此方式顯得更簡潔 */

但是當如果一個被調(diào)用的函數(shù)和調(diào)用它的函數(shù)不在同一個文件中時呢随闪?因為前文提到一個函數(shù)在一個程序中需要被定義一次阳似,那么如何使得讓一個文件中被定義的函數(shù)在另一個文件中可見?

這個問題會影響庫铐伴,無論是靜態(tài)庫還是動態(tài)庫撮奏。例如在這兩個質(zhì)數(shù)庫中函數(shù)被定義在源文件 primes.c 中,每個庫中都有該函數(shù)的二進制副本当宴,但是這些定義的函數(shù)必須要對使用庫的 C 程序可見畜吊,該 C 程序有其自身的源文件。

函數(shù)聲明可以幫助提供跨文件的可見性户矢。對于上述的“質(zhì)數(shù)”例子玲献,它有一個名為 primes.h 的頭文件,其聲明了四個函數(shù)使得它們對使用庫的 C 程序可見梯浪。

  1. /** 頭文件 primes.h:函數(shù)聲明 **/
  2. extern unsigned is_prime(unsigned);
  3. extern void prime_factors(unsigned);
  4. extern unsigned are_coprimes(unsigned, unsigned);
  5. extern void goldbach(unsigned);

這些聲明通過為每個函數(shù)指定其調(diào)用語法來作為接口捌年。

為了客戶程序的便利性,頭文件 primes.h 應該存儲在 C 編譯器查找路徑下的目錄中挂洛。典型的位置有 /usr/include/usr/local/include礼预。一個 C 語言客戶程序應使用 #include 包含這個頭文件,并盡可能將這條語句其程序源代碼的首部(頭文件將會被導入另一個源文件的“頭”部)虏劲。C 語言頭文件可以被導入其他語言(如 Rust 語言)中的 bindgen托酸,使其它語言的客戶程序可以訪問 C 語言的庫荠藤。

總之,一個庫函數(shù)只可以被定義一次获高,但可以在任何需要它的地方進行聲明哈肖,任一使用 C 語言庫的程序都需要該聲明。頭文件可以包含函數(shù)聲明念秧,但不能包含函數(shù)定義淤井。如果頭文件包含了函數(shù)定義,那么該文件可能會在一個 C 語言程序中被多次包含摊趾,從而破壞了一個函數(shù)在 C 語言程序中必須被精確定義一次的規(guī)則币狠。

庫的源代碼

下面是兩個庫的源代碼。這部分代碼砾层、頭文件漩绵、以及兩個示例客戶程序都可以在 我的網(wǎng)頁 上找到。

  1. #include <stdio.h>

  2. #include <math.h>

  3. extern unsigned is_prime(unsigned n) {

  4. if (n <= 3) return n > 1; /* 2 和 3 是質(zhì)數(shù) */

  5. if (0 == (n % 2) || 0 == (n % 3)) return 0; /* 2 和 3 的倍數(shù)不會是質(zhì)數(shù) */

  6. /* 檢查 n 是否是其他 < n 的值的倍數(shù) */

  7. unsigned i;

  8. for (i = 5; (i * i) <= n; i += 6)

  9. if (0 == (n % i) || 0 == (n % (i + 2))) return 0; /* 不是質(zhì)數(shù) */

  10. return 1; /* 一個不是 2 和 3 的質(zhì)數(shù) */

  11. }

  12. extern void prime_factors(unsigned n) {

  13. /* 在數(shù)字 n 的質(zhì)因數(shù)分解中列出所有 2 */

  14. while (0 == (n % 2)) {

  15. printf("%i ", 2);

  16. n /= 2;

  17. }

  18. /* 數(shù)字 2 已經(jīng)處理完成肛炮,下面處理奇數(shù) */

  19. unsigned i;

  20. for (i = 3; i <= sqrt(n); i += 2) {

  21. while (0 == (n % i)) {

  22. printf("%i ", i);

  23. n /= i;

  24. }

  25. }

  26. /* 還有其他質(zhì)因數(shù)止吐?*/

  27. if (n > 2) printf("%i", n);

  28. }

  29. /* 工具函數(shù):計算最大公約數(shù) */

  30. static unsigned gcd(unsigned n1, unsigned n2) {

  31. while (n1 != 0) {

  32. unsigned n3 = n1;

  33. n1 = n2 % n1;

  34. n2 = n3;

  35. }

  36. return n2;

  37. }

  38. extern unsigned are_coprimes(unsigned n1, unsigned n2) {

  39. return 1 == gcd(n1, n2);

  40. }

  41. extern void goldbach(unsigned n) {

  42. /* 輸入錯誤 */

  43. if ((n <= 2) || ((n & 0x01) > 0)) {

  44. printf("Number must be > 2 and even: %i is not.\n", n);

  45. return;

  46. }

  47. /* 兩個簡單的例子:4 和 6 */

  48. if ((4 == n) || (6 == n)) {

  49. printf("%i = %i + %i\n", n, n / 2, n / 2);

  50. return;

  51. }

  52. /* 當 n > 8 時,存在多種可能性 */

  53. unsigned i;

  54. for (i = 3; i < (n / 2); i++) {

  55. if (is_prime(i) && is_prime(n - i)) {

  56. printf("%i = %i + %i\n", n, i, n - i);

  57. /* 如果只需要一對侨糟,那么用 break 語句替換這句 */

  58. }

  59. }

  60. }

庫函數(shù)

這些函數(shù)可以被庫利用碍扔。兩個庫可以從相同的源代碼中獲得,同時頭文件 primes.h 是兩個庫的 C 語言接口秕重。

構建庫

靜態(tài)庫和動態(tài)庫在構建和發(fā)布的步驟上有一些細節(jié)的不同不同。靜態(tài)庫需要三個步驟,而動態(tài)庫需要增加兩個步驟即一共五個步驟溶耘。額外的步驟表明了動態(tài)庫的動態(tài)方法具有更多的靈活性二拐。讓我們先從靜態(tài)庫開始。

庫的源文件 primes.c 被編譯成一個目標模塊凳兵。下面是命令百新,百分號 % 代表系統(tǒng)提示符,兩個井字符 # 是我的注釋留荔。

  1. % gcc -c primes.c ## 步驟1(靜態(tài))

這一步生成目標模塊是二進制文件 primes.o吟孙。-c 標志意味著只編譯澜倦。

下一步是使用 Linux 的 ar 命令將目標對象歸檔聚蝶。

  1. % ar -cvq libprimes.a primes.o ## 步驟2(靜態(tài))

-cvq 三個標識分別是“創(chuàng)建”、“詳細的”藻治、“快速添加”(以防新文件沒有添加到歸檔中)的簡稱碘勉。回憶一下桩卵,前文提到過前綴 lib 是必須的,而庫名是任意的始花。當然赁严,庫的文件名必須是唯一的,以避免沖突高职。

歸檔已經(jīng)準備好要被發(fā)布:

  1. % sudo cp libprimes.a /usr/local/lib ## 步驟3(靜態(tài))

現(xiàn)在靜態(tài)庫對接下來的客戶程序是可見的,示例在后面辞州。(包含 sudo 可以確保有訪問權限將文件復制進 /usr/local/lib 目錄中)

動態(tài)庫還需要一個或多個對象模塊進行打包:

  1. % gcc primes.c -c -fpic ## 步驟1(動態(tài))

增加的選項 -fpic 指示編譯器生成與位置無關的代碼怔锌,這意味著不需要將該二進制模塊加載到一個固定的內(nèi)存位置。在一個擁有多個動態(tài)庫的系統(tǒng)中這種靈活性是至關重要的变过。生成的對象模塊會略大于靜態(tài)庫生成的對象模塊埃元。

下面是從對象模塊創(chuàng)建單個庫文件的命令:

  1. % gcc -shared -Wl,-soname,libshprimes.so -o libshprimes.so.1 primes.o ## 步驟2(動態(tài))

選項 -shared 表明了該庫是一個共享的(動態(tài)的)而不是靜態(tài)的。-Wl 選項引入了一系列編譯器選項媚狰,第一個便是設置動態(tài)庫的 -soname岛杀,這是必須設置的。soname 首先指定了庫的邏輯名字(libshprimes.so)崭孤,接下來的 -o 選項指明了庫的物理文件名字(libshprimes.so.1)类嗤。這樣做的目的是為了保持邏輯名不變的同時允許物理名隨著新版本而發(fā)生變化。在本例中辨宠,在物理文件名 libshprimes.so.1 中最后的 1 代表是第一個庫的版本土浸。盡管邏輯文件名和物理文件名可以是相同的,但是最佳做法是將它們命名為不同的名字彭羹。一個客戶程序?qū)ㄟ^邏輯名(本例中為 libshprimes.so)來訪問庫黄伊,稍后我會進一步解釋。

接下來的一步是通過復制共享庫到合適的目錄下使得客戶程序容易訪問派殷,例如 /usr/local/lib 目錄:

  1. % sudo cp libshprimes.so.1 /usr/local/lib ## 步驟3(動態(tài))

現(xiàn)在在共享庫的邏輯名(libshprimes.so)和它的物理文件名(/usr/local/lib/libshprimes.so.1)之間設置一個符號鏈接还最。最簡單的方式是將 /usr/local/lib 作為工作目錄,在該目錄下輸入命令:

  1. % sudo ln --symbolic libshprimes.so.1 libshprimes.so ## 步驟4(動態(tài))

邏輯名 libshprimes.so 不應該改變毡惜,但是符號鏈接的目標(libshrimes.so.1)可以根據(jù)需要進行更新拓轻,新的庫實現(xiàn)可以是修復了 bug,提高性能等经伙。

最后一步(一個預防措施)是調(diào)用 ldconfig 工具來配置系統(tǒng)的動態(tài)加載器扶叉。這個配置保證了加載器能夠找到新發(fā)布的庫。

  1. % sudo ldconfig ## 步驟5(動態(tài))

到現(xiàn)在帕膜,動態(tài)庫已為包括下面的兩個在內(nèi)的示例客戶程序準備就緒了枣氧。

一個使用庫的 C 程序

這個示例 C 程序是一個測試程序,它的源代碼以兩條 #include 指令開始:

  1. #include <stdio.h> /* 標準輸入/輸出函數(shù) */
  2. #include <primes.h> /* 我的庫函數(shù) */

文件名兩邊的尖括號表示可以在編譯器的搜索路徑中找到這些頭文件(對于 primes.h 文件來說在 /usr/local/inlcude 目錄下)垮刹。如果不包含 #include达吞,編譯器會抱怨缺少 is_primeprime_factors 等函數(shù)的聲明,它們在兩個庫中都有發(fā)布荒典。順便提一句酪劫,測試程序的源代碼不需要更改即可測試兩個庫中的每一個庫吞鸭。

相比之下,庫的源文件(primes.c)使用 #include 指令打開以下頭文件:

  1. #include <stdio.h>
  2. #include <math.h>

math.h 頭文件是必須的覆糟,因為庫函數(shù) prime_factors 會調(diào)用數(shù)學函數(shù) sqrt刻剥,其在標準庫 libm.so 中。

作為參考滩字,這是測試庫程序的源代碼:

  1. #include <stdio.h>

  2. #include <primes.h>

  3. int main() {

  4. /* 是質(zhì)數(shù) */

  5. printf("\nis_prime\n");

  6. unsigned i, count = 0, n = 1000;

  7. for (i = 1; i <= n; i++) {

  8. if (is_prime(i)) {

  9. count++;

  10. if (1 == (i % 100)) printf("Sample prime ending in 1: %i\n", i);

  11. }

  12. }

  13. printf("%i primes in range of 1 to a thousand.\n", count);

  14. /* prime_factors */

  15. printf("\nprime_factors\n");

  16. printf("prime factors of 12: ");

  17. prime_factors(12);

  18. printf("\n");

  19. printf("prime factors of 13: ");

  20. prime_factors(13);

  21. printf("\n");

  22. printf("prime factors of 876,512,779: ");

  23. prime_factors(876512779);

  24. printf("\n");

  25. /* 是合數(shù) */

  26. printf("\nare_coprime\n");

  27. printf("Are %i and %i coprime? %s\n",

  28. 21, 22, are_coprimes(21, 22) ? "yes" : "no");

  29. printf("Are %i and %i coprime? %s\n",

  30. 21, 24, are_coprimes(21, 24) ? "yes" : "no");

  31. /* 哥德巴赫 */

  32. printf("\ngoldbach\n");

  33. goldbach(11); /* error */

  34. goldbach(4); /* small one */

  35. goldbach(6); /* another */

  36. for (i = 100; i <= 150; i += 2) goldbach(i);

  37. return 0;

  38. }

測試程序

在編譯 tester.c 文件到可執(zhí)行文件時透敌,難處理的部分時鏈接選項的順序√咝担回想前文中提到兩個示例庫都是用 lib 作為前綴開始酗电,并且每一個都有一個常規(guī)的拓展后綴:.a 代表靜態(tài)庫 libprimes.a.so 代表動態(tài)庫 libshprimes.so内列。在鏈接規(guī)范中撵术,前綴 lib 和拓展名被忽略了。鏈接標志以 -l (小寫 L)開始话瞧,并且一條編譯命令可能包含多個鏈接標志嫩与。下面是一個完整的測試程序的編譯指令,使用動態(tài)庫作為示例:

  1. % gcc -o tester tester.c -lshprimes -lm

第一個鏈接標志指定了庫 libshprimes.so交排,第二個鏈接標志指定了標準數(shù)學庫 libm.so划滋。

鏈接器是懶惰的,這意味著鏈接標志的順序是需要考慮的埃篓。例如处坪,調(diào)整上述實例中的鏈接順序?qū)a(chǎn)生一個編譯時錯誤:

  1. % gcc -o tester tester.c -lm -lshprimes ## 危險!

鏈接 libm.so 庫的標志先出現(xiàn)架专,但是這個庫中沒有函數(shù)被測試程序顯式調(diào)用同窘;因此,鏈接器不會鏈接到 math.so 庫部脚。調(diào)用 sqrt 庫函數(shù)僅發(fā)生在 libshprimes.so 庫中包含的 prime_factors 函數(shù)想邦。編譯測試程序返回的錯誤是:

  1. primes.c: undefined reference to 'sqrt'

因此,鏈接標志的順序應該是通知鏈接器需要 sqrt 函數(shù):

  1. % gcc -o tester tester.c -lshprimes -lm ## 首先鏈接 -lshprimes

鏈接器在 libshprimes.so 庫中發(fā)現(xiàn)了對庫函數(shù) sqrt 的調(diào)用委刘,所以接下來對數(shù)學庫 libm.so做了合適的鏈接丧没。鏈接還有一個更復雜的選項,它支持鏈接的標志順序锡移。然而在本例中呕童,最簡單的方式就是恰當?shù)嘏帕墟溄訕酥尽?/p>

下面是運行測試程序的部分輸出結果:

  1. is_prime

  2. Sample prime ending in 1: 101

  3. Sample prime ending in 1: 401

  4. ...

  5. 168 primes in range of 1 to a thousand.

  6. prime_factors

  7. prime factors of 12: 2 2 3

  8. prime factors of 13: 13

  9. prime factors of 876,512,779: 211 4154089

  10. are_coprime

  11. Are 21 and 22 coprime? yes

  12. Are 21 and 24 coprime? no

  13. goldbach

  14. Number must be &gt; 2 and even: 11 is not.

  15. 4 = 2 + 2

  16. 6 = 3 + 3

  17. ...

  18. 32 = 3 + 29

  19. 32 = 13 + 19

  20. ...

  21. 100 = 3 + 97

  22. 100 = 11 + 89

  23. ...

對于 goldbach 函數(shù),即使一個相當小的偶數(shù)值(例如 18)也許存在多個一對質(zhì)數(shù)之和的組合(在這種情況下罩抗,5+13 和 7+11)拉庵。因此這種多個質(zhì)數(shù)對是使得嘗試證明哥德巴赫猜想變得復雜的因素之一灿椅。

封裝使用庫的 Python 程序

與 C 不同套蒂,Python 不是一個靜態(tài)編譯語言钞支,這意味著 Python 客戶示例程序必須訪問動態(tài)版本而非靜態(tài)版本的 primes 庫。為了能這樣做操刀,Python 中有眾多的支持<ruby style="box-sizing: border-box;">外部語言接口<rt style="box-sizing: border-box;">foreign function interface</rt></ruby>(FFI)的模塊(標準的或第三方的)烁挟,它們允許用一種語言編寫的程序來調(diào)用另一種語言編寫的函數(shù)。Python 中的 ctypes 是一個標準的骨坑、相對簡單的允許 Python 代碼調(diào)用 C 函數(shù)的 FFI撼嗓。

任何 FFI 都面臨挑戰(zhàn),因為對接的語言不大可能會具有完全相同的數(shù)據(jù)類型欢唾。例如:primes 庫使用 C 語言類型 unsigned int且警,而 Python 并不具有這種類型;因此 ctypes FFI 將 C 語言中的 unsigned int 類型映射為 Python 中的 int 類型礁遣。在 primes 庫中發(fā)布的四個 extern C 函數(shù)中斑芜,有兩個在具有顯式 ctypes 配置的 Python 中會表現(xiàn)得更好。

C 函數(shù) prime_factorsgoldbach 返回 void 而不是返回一個具體類型祟霍,但是 ctypes 默認會將 C 語言中的 void 替換為 Python 語言中的 int杏头。當從 Python 代碼中調(diào)用時,這兩個 C 函數(shù)會從棧中返回一個隨機整數(shù)值(因此沸呐,該值無任何意義)醇王。然而,可以對 ctypes 進行配置崭添,讓這些函數(shù)返回 None (Python 中為 null 類型)寓娩。下面是對 prime_factors 函數(shù)的配置:

  1. primes.prime_factors.restype = None

可以用類似的語句處理 goldbach 函數(shù)。

下面的交互示例(在 Python3 中)展示了在 Python 客戶程序和 primes 庫之間的接口是簡單明了的呼渣。

  1. >>> from ctypes import cdll

  2. >>> primes = cdll.LoadLibrary("libshprimes.so") ## 邏輯名

  3. >>> primes.is_prime(13)

  4. 1

  5. >>> primes.is_prime(12)

  6. 0

  7. >>> primes.are_coprimes(8, 24)

  8. 0

  9. >>> primes.are_coprimes(8, 25)

  10. 1

  11. >>> primes.prime_factors.restype = None

  12. >>> primes.goldbach.restype = None

  13. >>> primes.prime_factors(72)

  14. 2 2 2 3 3

  15. >>> primes.goldbach(32)

  16. 32 = 3 + 29

  17. 32 = 13 + 19

primes 庫中的函數(shù)只使用一個簡單數(shù)據(jù)類型:unsigned int根暑。如果這個 C 語言庫使用復雜的類型如結構體,如果庫函數(shù)傳遞和返回指向結構體的指針徙邻,那么比 ctypes 更強大的 FFI 更適合作為一個在 Python 語言和 C 語言之間的平滑接口排嫌。盡管如此,ctypes 示例展示了一個 Python 客戶程序可以使用 C 語言編寫的庫缰犁。值得注意的是淳地,用作科學計算的流行的 Numpy 庫是用 C 語言編寫的,然后在高級 Python API 中公開帅容。

簡單的 primes 庫和高級的 Numpy 庫強調(diào)了 C 語言仍然是編程語言中的通用語言颇象。幾乎每一個語言都可以與 C 語言交互,同時通過 C 語言也可以和任何其他語言交互并徘。Python 很容易和 C 語言交互遣钳,作為另外一個例子,當 Panama 項目 成為 Java Native Interface(JNI)一個替代品后麦乞,Java 語言和 C 語言交互也會變的很容易蕴茴。


via: https://opensource.com/article/21/2/linux-software-libraries

作者:Marty Kalin 選題:lujun9972 譯者:萌新阿巖 校對:wxy

本文由 LCTT 原創(chuàng)編譯劝评,Linux中國 榮譽推出

轉(zhuǎn)自 https://linux.cn/article-13413-1.html

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市倦淀,隨后出現(xiàn)的幾起案子蒋畜,更是在濱河造成了極大的恐慌,老刑警劉巖撞叽,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姻成,死亡現(xiàn)場離奇詭異,居然都是意外死亡愿棋,警方通過查閱死者的電腦和手機科展,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糠雨,“玉大人辛润,你說我怎么就攤上這事〖樱” “怎么了砂竖?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鹃答。 經(jīng)常有香客問我乎澄,道長,這世上最難降的妖魔是什么测摔? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任置济,我火速辦了婚禮,結果婚禮上锋八,老公的妹妹穿的比我還像新娘浙于。我一直安慰自己,他們只是感情好挟纱,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布羞酗。 她就那樣靜靜地躺著,像睡著了一般紊服。 火紅的嫁衣襯著肌膚如雪檀轨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天欺嗤,我揣著相機與錄音参萄,去河邊找鬼。 笑死煎饼,一個胖子當著我的面吹牛讹挎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼筒溃,長吁一口氣:“原來是場噩夢啊……” “哼马篮!你這毒婦竟也來了?” 一聲冷哼從身側響起铡羡,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤积蔚,失蹤者是張志新(化名)和其女友劉穎意鲸,沒想到半個月后烦周,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡怎顾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年读慎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片槐雾。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡夭委,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出募强,到底是詐尸還是另有隱情株灸,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布擎值,位于F島的核電站慌烧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鸠儿。R本人自食惡果不足惜屹蚊,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望进每。 院中可真熱鬧汹粤,春花似錦、人聲如沸田晚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贤徒。三九已至遭京,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泞莉,已是汗流浹背哪雕。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鲫趁,地道東北人斯嚎。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親堡僻。 傳聞我的和親對象是個殘疾皇子糠惫,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容