2020-04-10
簡而言之,編譯就是把原始碼(一個由程式設計師編寫的人類可讀的程式的說明)翻譯成計算機處理器的語言的過程。
不是。正如我們所看到的,有些程式比如 shell 指令碼就不需要編譯。它們直接執行。這些程式是用所謂的指令碼或直譯語言編寫的。近年來,這些語言變得越來越流行,包括 Perl、Python、PHP、Ruby 和許多其它語言。
指令碼語言由一個叫做直譯器的特殊程式執行。一個直譯器輸入程式檔案,讀取並執行程式中包含的每一條指令。通常來說,解釋型程式執行起來要比編譯程式慢很多。這是因爲每次解釋型程式執行時,程式中每一條原始碼指令都需要翻譯,而一個已經編譯好的程式,一條原始碼指令只翻譯了一次,翻譯後的指令會永久地記錄到最終的執行檔案中。
那麼爲什麼解釋型程式這樣流行呢?對於許多程式設計任務來說,原因是「足夠快」,但是真正的優勢是一般來說開發解釋型程式要比編譯程式快速且容易。通常程式開發需要經歷一個不斷重複的寫碼、編譯和測試周期。隨着程式變得越來越大,編譯階段會變得相當耗時。直譯語言刪除了編譯步驟,這樣就加快了程式開發。
在我們編譯之前,然而我們需要一些工具,像編譯器、鏈接器以及
make
。在 Linux 環境中,普遍使用的 C 編譯器叫做 gcc
(GNU C 編譯器),最初由 Richard Stallman 寫出來的。大多數 Linux 系統發行版預設不安裝 gcc
。我們可以這樣檢視該編譯器是否存在:
[root@VM_0_7_centos ~]# which gcc
/usr/bin/gcc
在這個例子中的輸出結果表明安裝了 gcc
編譯器。
爲了我們的編譯練習,我們將編譯一個叫做 diction 的程式,來自 GNU 專案。這是一個小巧方便的程式,檢查文字檔案的書寫品質和樣式。就程式而言,它相當小,且容易建立。
遵照慣例,首先我們要建立一個名爲 src
的目錄來存放我們的原始碼,然後使用 ftp
協定把原始碼下載下來。
[root@VM_0_7_centos ~]# mkdir src
[root@VM_0_7_centos ~]# cd src/
[root@VM_0_7_centos src]# ftp ftp.gnu.org
Trying 209.51.188.20...
Connected to ftp.gnu.org (209.51.188.20).
220 GNU FTP server ready.
Name (ftp.gnu.org:root): anonymous
230-NOTICE (Updated October 13 2017):
230-
230-Because of security concerns with plaintext protocols, we still
230-intend to disable the FTP protocol for downloads on this server
230-(downloads would still be available over HTTP and HTTPS), but we
230-will not be doing it on November 1, 2017, as previously announced
230-here. We will be sharing our reasons and offering a chance to
230-comment on this issue soon; watch this space for details.
230-
230-If you maintain scripts used to access ftp.gnu.org over FTP,
230-we strongly encourage you to change them to use HTTPS instead.
230-
230----
230-
230-Due to U.S. Export Regulations, all cryptographic software on this
230-site is subject to the following legal notice:
230-
230- This site includes publicly available encryption source code
230- which, together with object code resulting from the compiling of
230- publicly available source code, may be exported from the United
230- States under License Exception "TSU" pursuant to 15 C.F.R. Section
230- 740.13(e).
230-
230-This legal notice applies to cryptographic software only. Please see
230-the Bureau of Industry and Security (www.bxa.doc.gov) for more
230-information about current U.S. regulations.
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd gnu/diction
250 Directory successfully changed.
ftp> sl
?Invalid command
ftp> ls
227 Entering Passive Mode (209,51,188,20,113,59).
150 Here comes the directory listing.
-rw-r--r-- 1 3003 65534 68940 Aug 28 1998 diction-0.7.tar.gz
-rw-r--r-- 1 3003 65534 90957 Mar 04 2002 diction-1.02.tar.gz
-rw-r--r-- 1 3003 65534 141062 Sep 17 2007 diction-1.11.tar.gz
-rw-r--r-- 1 3003 65534 189 Sep 17 2007 diction-1.11.tar.gz.sig
226 Directory send OK.
ftp> get diction-1.11.tar.gz
local: diction-1.11.tar.gz remote: diction-1.11.tar.gz
227 Entering Passive Mode (209,51,188,20,84,144).
150 Opening BINARY mode data connection for diction-1.11.tar.gz (141062 bytes).
226 Transfer complete.
141062 bytes received in 15.1 secs (9.32 Kbytes/sec)
ftp> bye
221 Goodbye.
[root@VM_0_7_centos src]# ls
diction-1.11.tar.gz
一旦 tar
檔案下載下來之後,必須解包。通過 tar
程式可以完成:
[root@VM_0_7_centos src]# tar xzf diction-1.11.tar.gz
[root@VM_0_7_centos src]# ls
diction-1.11 diction-1.11.tar.gz
該 diction 程式,像所有的 GNU 專案軟體,遵循着一定的原始碼打包標準。其它大多數在 Linux 生態系統中可用的原始碼也遵循這個標準。該標準的一個條目是,當原始碼 tar
檔案開啓的時候,會建立一個目錄,該目錄包含了原始碼樹,並且這個目錄將會命名爲 project-x.xx
,其包含了專案名稱和它的版本號兩項內容。這種方案能在系統中方便安裝同一程式的多個版本。然而,通常在開啓 tarball 之前檢驗原始碼樹的佈局是個不錯的主意。一些專案不會建立該目錄,反而,會把檔案直接傳遞給當前目錄。這會把你的(除非組織良好的)src
目錄弄得一片狼藉。爲了避免這個,使用下面 下麪的命令,檢查 tar
檔案的內容:
tar tzvf tarfile | head ---
開啓該 tar
檔案,會建立一個新的目錄,名爲 diction-1.11。這個目錄包含了原始碼樹。讓我們看一下裏面的內容:
[root@VM_0_7_centos src]# cd diction-1.11/
[root@VM_0_7_centos diction-1.11]# ls
config.guess configure de diction.c diction.spec.in en_GB getopt.c INSTALL misc.c nl sentence.c style.c
config.h.in configure.in de.po diction.pot diction.texi.in en_GB.po getopt.h install-sh misc.h nl.po sentence.h test
config.sub COPYING diction.1.in diction.spec en getopt1.c getopt_int.h Makefile.in NEWS README style.1.in
這些檔案包含了程式描述,如何建立和安裝它的資訊,還有其它許可條款。在試圖建立程式之前,仔細閱讀 README 和 INSTALL 檔案,總是一個不錯的主意。
在這個目錄中,其它有趣的檔案是那些以.c
和.h
爲後綴的檔案,這些.c
檔案包含了由該軟體包提供的兩個 C 程式(style 和 diction),被分割成模組。這是一種常見做法,把大型程式分解成更小,更容易管理的程式碼塊。原始碼檔案都是普通文字,可以用 less
命令檢視。這些.h
檔案被稱爲標頭檔案。它們也是普通檔案。標頭檔案包含了程式的描述,這些程式被包括在原始碼檔案或庫中。爲了讓編譯器鏈接到模組,編譯器必須接受所需的所有模組的描述,來完成整個程式。
大多數程式通過一個簡單的,兩個命令的序列構建:
./configure
make
這個 configure 程式是一個 shell 指令碼,由原始碼樹提供。它的工作是分析程式構建環境。大多數原始碼會設計爲可移植的。也就是說,它被設計成能夠在不止一種類 Unix 系統中進行構建。但是爲了做到這一點,在建立程式期間,爲了適應系統之間的差異,原始碼可能需要經過輕微的調整。configure 也會檢查是否安裝了必要的外部工具和元件。讓我們執行 configure 命令。因爲 configure 命令所在的位置不是位於 shell 通常期望程式所呆的地方,我們必須明確地告訴shell 它的位置,通過在命令之前加上./
字元,來表明程式位於當前工作目錄:
[root@VM_0_7_centos diction-1.11]# ./configure
configure 將會輸出許多資訊,隨着它測試和設定整個構建過程。當結束後,輸出結果看起來像這樣:
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking for gcc... gcc
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for a BSD-compatible install... /usr/bin/install -c
checking for strerror... yes
checking for library containing regcomp... none required
checking for broken realloc... no
checking for msgfmt... yes
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking libintl.h usability... yes
checking libintl.h presence... yes
checking for libintl.h... yes
checking for library containing gettext... none required
configure: creating ./config.status
config.status: creating Makefile
config.status: creating diction.1
config.status: creating diction.texi
config.status: creating diction.spec
config.status: creating style.1
config.status: creating test/rundiction
config.status: creating config.h
這裏最重要的事情是沒有錯誤資訊。如果有錯誤資訊,整個設定過程失敗,然後程式不能構建直到修正了錯誤。
我們看到在我們的原始碼目錄中 configure 命令建立了幾個新檔案。最重要一個是 Makefile。Makefile 是一個組態檔,指示 make 程式究竟如何構建程式。沒有它,make 程式就不能執行。Makefile 是一個普通文字檔案,所以我們能檢視它。
這個 make 程式把一個 makefile 檔案作爲輸入(通常命名爲 Makefile),makefile 檔案描述了包括最終完成的程式的各元件之間的關係和依賴性。
讓我們執行 make 命令並構建我們的程式:
[root@VM_0_7_centos diction-1.11]# make
這個 make 程式將會執行,使用 Makefile 檔案的內容來指導它的行爲。它會產生很多資訊。
當 make 程式執行結束後,現在我們將看到所有的目標檔案出現在我們的目錄中。
[root@VM_0_7_centos diction-1.11]# ls
config.guess config.sub de.mo diction.c diction.texi en_GB.po getopt_int.h Makefile.in nl sentence.h style.c
config.h configure de.po diction.o diction.texi.in getopt1.c getopt.o misc.c nl.mo sentence.o style.o
config.h.in configure.in diction diction.pot en getopt1.o INSTALL misc.h nl.po style test
config.log COPYING diction.1 diction.spec en_GB getopt.c install-sh misc.o README style.1
config.status de diction.1.in diction.spec.in en_GB.mo getopt.h Makefile NEWS sentence.c style.1.in
在這些檔案之中,我們看到 diction 和 style,我們開始要構建的程式。恭喜一切正常!我們剛纔原始碼編譯了我們的第一個程式。但是出於好奇,讓我們再執行一次 make 程式:
[root@VM_0_7_centos diction-1.11]# make
make: Nothing to be done for `all'.
它只是產生這樣一條奇怪的資訊。怎麼了?爲什麼它沒有重新構建程式呢?啊,這就是make 奇妙之處了。make 只是構建需要構建的部分,而不是簡單地重新構建所有的內容。由於所有的目標檔案都存在,make 確定沒有任何事情需要做。我們可以證明這一點,通過刪除一個目標檔案,然後再次執行 make 程式,看看它做些什麼。讓我們去掉一箇中間目標檔案:
[root@VM_0_7_centos diction-1.11]# rm getopt.o
rm: remove regular file ‘getopt.o’? y
[root@VM_0_7_centos diction-1.11]# ls
config.guess config.sub de.mo diction.c diction.texi en_GB.po getopt_int.h misc.c nl.mo sentence.o style.o
config.h configure de.po diction.o diction.texi.in getopt1.c INSTALL misc.h nl.po style test
config.h.in configure.in diction diction.pot en getopt1.o install-sh misc.o README style.1
config.log COPYING diction.1 diction.spec en_GB getopt.c Makefile NEWS sentence.c style.1.in
config.status de diction.1.in diction.spec.in en_GB.mo getopt.h Makefile.in nl sentence.h style.c
[root@VM_0_7_centos diction-1.11]# make
gcc -c -I. -DSHAREDIR=\"/usr/local/share\" -DLOCALEDIR=\"/usr/local/share/locale\" -g -O2 -pipe -Wno-unused -Wshadow -Wbad-function-cast -Wmissing-prototypes -Wstrict-prototypes -Wcast-align -Wcast-qual -Wpointer-arith -Wcast-align -Wwrite-strings -Wmissing-declarations -Wnested-externs -Wundef -pedantic -fno-common getopt.c
gcc -o diction -g diction.o sentence.o misc.o \
getopt.o getopt1.o
gcc -o style -g style.o sentence.o misc.o \
getopt.o getopt1.o -lm
我們看到 make 重新構建了 getopt.o 檔案,並重新鏈接了 diction 和 style 程式,因爲它們依賴於丟失的模組。這種行爲也指出了 make 程式的另一個重要特徵:它保持目標檔案是最新的。make 堅持目標檔案要新於它們的依賴檔案。
打包良好的原始碼經常包括一個特別的 make 目標檔案,叫做 install。這個目標檔案將在系統目錄中安裝最終的產品,以供使用。通常,這個目錄是 /usr/local/bin
,爲在本地所構建軟體的傳統安裝位置。然而,通常普通使用者不能寫入該目錄,所以我們必須變成超級使用者,來執行安裝操作:
[root@VM_0_7_centos diction-1.11]# sudo make install
執行了安裝後,我們可以檢查下程式是否已經可用:
[root@VM_0_7_centos diction-1.11]# which diction
/usr/local/bin/diction
[root@VM_0_7_centos diction-1.11]# man diction
在這一部分中,我們已經知道了三個簡單命令:
./configure
make
make install
可以用來構建許多原始碼包。我們也知道了在程式維護過程中,make 程式起到了舉足輕重的作用。make 程式可以用到任何需要維護一個目標/依賴關係的任務中,不僅僅爲了編譯原始碼。