軟件庫是重復使用代碼的一種簡單而合理的方式扫尺。
軟件庫是一種是一直以來長期存在的品山、簡單合理的復用代碼的方式局雄。這篇文章解釋了如何從頭開始構建庫并使得其可用紧唱。盡管這兩個示例庫都以 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ā)布:
- 庫的源代碼會被編譯成一個或多個目標模塊占键,目標模塊是二進制文件,可以被包含在庫中并且鏈接到可執(zhí)行的二進制中绍些。
- 目標模塊會會被打包成一個文件捞慌。對于靜態(tài)庫,標準的文件拓展名是
.a
意為歸檔柬批;對于動態(tài)庫啸澡,標準的文件拓展名是.so
意為共享目標shared object。對于這兩個相同功能的示例庫氮帐,分別發(fā)布為libprimes.a
(靜態(tài)庫)和libshprimes.so
(動態(tài)庫)嗅虏。兩種庫的文件名都使用前綴lib
進行標識。 - 庫文件被復制到標準目錄下上沐,使得客戶程序可以輕松地訪問到庫皮服。無論是靜態(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ù)定義:extern unsigned are_coprimes(unsigned n1, unsigned n2) {
...
}
- 存儲類
static
將一個函數(shù)的的范圍限制到函數(shù)被定義的文件中仿村。在示例庫中,工具函數(shù)gcd
是靜態(tài)的(static
):static unsigned gcd(unsigned n1, unsigned n2) {
...
}
只有在 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
的完整定義:
extern unsigned are_coprimes(unsigned n1, unsigned n2) { /* 定義 */
return 1 == gcd(n1, n2); /* 最大公約數(shù)是否為 1? */
}
函數(shù)返回一個布爾值(0
代表假彼水,1
代表真)崔拥,取決于兩個整數(shù)參數(shù)值的最大公約數(shù)是否為 1。工具函數(shù) gcd
計算兩個整數(shù)參數(shù) n1
和 n2
的最大公約數(shù)凤覆。
函數(shù)聲明不同于定義链瓦,其不需要主體部分:
extern unsigned are_coprimes(unsigned n1, unsigned n2); /* 聲明 */
聲明在參數(shù)列表后用一個分號代表結束,它沒有被花括號包圍起來的主體部分盯桦。程序中的函數(shù)可以被多次聲明慈俯。
為什么需要聲明?在 C 語言中俺附,一個被調(diào)用的函數(shù)必須對其調(diào)用者可見肥卡。有多種方式可以提供這樣的可見性,具體依賴于編譯器如何實現(xiàn)事镣。一個必然可行的方式就是當它們二者位于同一個文件中時步鉴,將被調(diào)用的函數(shù)定義在在它的調(diào)用者之前。
void f() {...} /* f 定義在其被調(diào)用前 */
void g() { f(); } /* ok */
當函數(shù) f
被在調(diào)用前聲明璃哟,此時函數(shù) f
的定義可以移動到函數(shù) g
的下方氛琢。
void f(); /* 聲明使得函數(shù) f 對調(diào)用者可見 */
void g() { f(); } /* ok */
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 程序可見梯浪。
/** 頭文件 primes.h:函數(shù)聲明 **/
extern unsigned is_prime(unsigned);
extern void prime_factors(unsigned);
extern unsigned are_coprimes(unsigned, unsigned);
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)頁 上找到。
#include <stdio.h>
#include <math.h>
extern unsigned is_prime(unsigned n) {
if (n <= 3) return n > 1; /* 2 和 3 是質(zhì)數(shù) */
if (0 == (n % 2) || 0 == (n % 3)) return 0; /* 2 和 3 的倍數(shù)不會是質(zhì)數(shù) */
/* 檢查 n 是否是其他 < n 的值的倍數(shù) */
unsigned i;
for (i = 5; (i * i) <= n; i += 6)
if (0 == (n % i) || 0 == (n % (i + 2))) return 0; /* 不是質(zhì)數(shù) */
return 1; /* 一個不是 2 和 3 的質(zhì)數(shù) */
}
extern void prime_factors(unsigned n) {
/* 在數(shù)字 n 的質(zhì)因數(shù)分解中列出所有 2 */
while (0 == (n % 2)) {
printf("%i ", 2);
n /= 2;
}
/* 數(shù)字 2 已經(jīng)處理完成肛炮,下面處理奇數(shù) */
unsigned i;
for (i = 3; i <= sqrt(n); i += 2) {
while (0 == (n % i)) {
printf("%i ", i);
n /= i;
}
}
/* 還有其他質(zhì)因數(shù)止吐?*/
if (n > 2) printf("%i", n);
}
/* 工具函數(shù):計算最大公約數(shù) */
static unsigned gcd(unsigned n1, unsigned n2) {
while (n1 != 0) {
unsigned n3 = n1;
n1 = n2 % n1;
n2 = n3;
}
return n2;
}
extern unsigned are_coprimes(unsigned n1, unsigned n2) {
return 1 == gcd(n1, n2);
}
extern void goldbach(unsigned n) {
/* 輸入錯誤 */
if ((n <= 2) || ((n & 0x01) > 0)) {
printf("Number must be > 2 and even: %i is not.\n", n);
return;
}
/* 兩個簡單的例子:4 和 6 */
if ((4 == n) || (6 == n)) {
printf("%i = %i + %i\n", n, n / 2, n / 2);
return;
}
/* 當 n > 8 時,存在多種可能性 */
unsigned i;
for (i = 3; i < (n / 2); i++) {
if (is_prime(i) && is_prime(n - i)) {
printf("%i = %i + %i\n", n, i, n - i);
/* 如果只需要一對侨糟,那么用 break 語句替換這句 */
}
}
}
庫函數(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)提示符,兩個井字符 #
是我的注釋留荔。
% gcc -c primes.c ## 步驟1(靜態(tài))
這一步生成目標模塊是二進制文件 primes.o
吟孙。-c
標志意味著只編譯澜倦。
下一步是使用 Linux 的 ar
命令將目標對象歸檔聚蝶。
% ar -cvq libprimes.a primes.o ## 步驟2(靜態(tài))
-cvq
三個標識分別是“創(chuàng)建”、“詳細的”藻治、“快速添加”(以防新文件沒有添加到歸檔中)的簡稱碘勉。回憶一下桩卵,前文提到過前綴 lib
是必須的,而庫名是任意的始花。當然赁严,庫的文件名必須是唯一的,以避免沖突高职。
歸檔已經(jīng)準備好要被發(fā)布:
% sudo cp libprimes.a /usr/local/lib ## 步驟3(靜態(tài))
現(xiàn)在靜態(tài)庫對接下來的客戶程序是可見的,示例在后面辞州。(包含 sudo
可以確保有訪問權限將文件復制進 /usr/local/lib
目錄中)
動態(tài)庫還需要一個或多個對象模塊進行打包:
% gcc primes.c -c -fpic ## 步驟1(動態(tài))
增加的選項 -fpic
指示編譯器生成與位置無關的代碼怔锌,這意味著不需要將該二進制模塊加載到一個固定的內(nèi)存位置。在一個擁有多個動態(tài)庫的系統(tǒng)中這種靈活性是至關重要的变过。生成的對象模塊會略大于靜態(tài)庫生成的對象模塊埃元。
下面是從對象模塊創(chuàng)建單個庫文件的命令:
% 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
目錄:
% sudo cp libshprimes.so.1 /usr/local/lib ## 步驟3(動態(tài))
現(xiàn)在在共享庫的邏輯名(libshprimes.so
)和它的物理文件名(/usr/local/lib/libshprimes.so.1
)之間設置一個符號鏈接还最。最簡單的方式是將 /usr/local/lib
作為工作目錄,在該目錄下輸入命令:
% 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ā)布的庫。
% sudo ldconfig ## 步驟5(動態(tài))
到現(xiàn)在帕膜,動態(tài)庫已為包括下面的兩個在內(nèi)的示例客戶程序準備就緒了枣氧。
一個使用庫的 C 程序
這個示例 C 程序是一個測試程序,它的源代碼以兩條 #include
指令開始:
#include <stdio.h> /* 標準輸入/輸出函數(shù) */
#include <primes.h> /* 我的庫函數(shù) */
文件名兩邊的尖括號表示可以在編譯器的搜索路徑中找到這些頭文件(對于 primes.h
文件來說在 /usr/local/inlcude
目錄下)垮刹。如果不包含 #include
达吞,編譯器會抱怨缺少 is_prime
和 prime_factors
等函數(shù)的聲明,它們在兩個庫中都有發(fā)布荒典。順便提一句酪劫,測試程序的源代碼不需要更改即可測試兩個庫中的每一個庫吞鸭。
相比之下,庫的源文件(primes.c
)使用 #include
指令打開以下頭文件:
#include <stdio.h>
#include <math.h>
math.h
頭文件是必須的覆糟,因為庫函數(shù) prime_factors
會調(diào)用數(shù)學函數(shù) sqrt
刻剥,其在標準庫 libm.so
中。
作為參考滩字,這是測試庫程序的源代碼:
#include <stdio.h>
#include <primes.h>
int main() {
/* 是質(zhì)數(shù) */
printf("\nis_prime\n");
unsigned i, count = 0, n = 1000;
for (i = 1; i <= n; i++) {
if (is_prime(i)) {
count++;
if (1 == (i % 100)) printf("Sample prime ending in 1: %i\n", i);
}
}
printf("%i primes in range of 1 to a thousand.\n", count);
/* prime_factors */
printf("\nprime_factors\n");
printf("prime factors of 12: ");
prime_factors(12);
printf("\n");
printf("prime factors of 13: ");
prime_factors(13);
printf("\n");
printf("prime factors of 876,512,779: ");
prime_factors(876512779);
printf("\n");
/* 是合數(shù) */
printf("\nare_coprime\n");
printf("Are %i and %i coprime? %s\n",
21, 22, are_coprimes(21, 22) ? "yes" : "no");
printf("Are %i and %i coprime? %s\n",
21, 24, are_coprimes(21, 24) ? "yes" : "no");
/* 哥德巴赫 */
printf("\ngoldbach\n");
goldbach(11); /* error */
goldbach(4); /* small one */
goldbach(6); /* another */
for (i = 100; i <= 150; i += 2) goldbach(i);
return 0;
}
測試程序
在編譯 tester.c
文件到可執(zhí)行文件時透敌,難處理的部分時鏈接選項的順序√咝担回想前文中提到兩個示例庫都是用 lib
作為前綴開始酗电,并且每一個都有一個常規(guī)的拓展后綴:.a
代表靜態(tài)庫 libprimes.a
,.so
代表動態(tài)庫 libshprimes.so
内列。在鏈接規(guī)范中撵术,前綴 lib
和拓展名被忽略了。鏈接標志以 -l
(小寫 L)開始话瞧,并且一條編譯命令可能包含多個鏈接標志嫩与。下面是一個完整的測試程序的編譯指令,使用動態(tài)庫作為示例:
% gcc -o tester tester.c -lshprimes -lm
第一個鏈接標志指定了庫 libshprimes.so
交排,第二個鏈接標志指定了標準數(shù)學庫 libm.so
划滋。
鏈接器是懶惰的,這意味著鏈接標志的順序是需要考慮的埃篓。例如处坪,調(diào)整上述實例中的鏈接順序?qū)a(chǎn)生一個編譯時錯誤:
% 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ù)想邦。編譯測試程序返回的錯誤是:
primes.c: undefined reference to 'sqrt'
因此,鏈接標志的順序應該是通知鏈接器需要 sqrt
函數(shù):
% gcc -o tester tester.c -lshprimes -lm ## 首先鏈接 -lshprimes
鏈接器在 libshprimes.so
庫中發(fā)現(xiàn)了對庫函數(shù) sqrt
的調(diào)用委刘,所以接下來對數(shù)學庫 libm.so
做了合適的鏈接丧没。鏈接還有一個更復雜的選項,它支持鏈接的標志順序锡移。然而在本例中呕童,最簡單的方式就是恰當?shù)嘏帕墟溄訕酥尽?/p>
下面是運行測試程序的部分輸出結果:
is_prime
Sample prime ending in 1: 101
Sample prime ending in 1: 401
...
168 primes in range of 1 to a thousand.
prime_factors
prime factors of 12: 2 2 3
prime factors of 13: 13
prime factors of 876,512,779: 211 4154089
are_coprime
Are 21 and 22 coprime? yes
Are 21 and 24 coprime? no
goldbach
Number must be > 2 and even: 11 is not.
4 = 2 + 2
6 = 3 + 3
...
32 = 3 + 29
32 = 13 + 19
...
100 = 3 + 97
100 = 11 + 89
...
對于 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_factors
和 goldbach
返回 void
而不是返回一個具體類型祟霍,但是 ctypes
默認會將 C 語言中的 void
替換為 Python 語言中的 int
杏头。當從 Python 代碼中調(diào)用時,這兩個 C 函數(shù)會從棧中返回一個隨機整數(shù)值(因此沸呐,該值無任何意義)醇王。然而,可以對 ctypes
進行配置崭添,讓這些函數(shù)返回 None
(Python 中為 null
類型)寓娩。下面是對 prime_factors
函數(shù)的配置:
primes.prime_factors.restype = None
可以用類似的語句處理 goldbach
函數(shù)。
下面的交互示例(在 Python3 中)展示了在 Python 客戶程序和 primes
庫之間的接口是簡單明了的呼渣。
>>> from ctypes import cdll
>>> primes = cdll.LoadLibrary("libshprimes.so") ## 邏輯名
>>> primes.is_prime(13)
1
>>> primes.is_prime(12)
0
>>> primes.are_coprimes(8, 24)
0
>>> primes.are_coprimes(8, 25)
1
>>> primes.prime_factors.restype = None
>>> primes.goldbach.restype = None
>>> primes.prime_factors(72)
2 2 2 3 3
>>> primes.goldbach(32)
32 = 3 + 29
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