GNU C 編譯器的程序員入門指南
帶你一窺生成二進位文件步驟的幕後,以便在出現一些錯誤時,你知道如何逐步解決問題。
C 語言廣為人知,深受新老程序員的好評。使用 C 語言編寫的源文件代碼,使用了標準的英語術語,因而人們可以方便閱讀。然而,計算機只能理解二進位代碼。為將代碼轉換為機器語言,你需要使用一種被稱為 編譯器 的工具。
最常見的編譯器是 GCC( GNU 編譯器集 )。編譯過程涉及到一系列的中間步驟及相關工具。
安裝 GCC
為驗證在你的系統上是否已經安裝了 GCC,使用 gcc
命令:
$ gcc --version
如有必要,使用你的軟體包管理器來安裝 GCC。在基於 Fedora 的系統上,使用 dnf
:
$ sudo dnf install gcc libgcc
在基於 Debian 的系統上,使用 apt
:
$ sudo apt install build-essential
在安裝後,如果你想查看 GCC 的安裝位置,那麼使用:
$ whereis gcc
演示使用 GCC 來編譯一個簡單的 C 程序
這裡有一個簡單的 C 程序,用於演示如何使用 GCC 來編譯。打開你最喜歡的文本編輯器,並在其中粘貼這段代碼:
// hellogcc.c
#include <stdio.h>
int main() {
printf("Hello, GCC!n");
return 0;
}
保存文件為 hellogcc.c
,接下來編譯它:
$ ls
hellogcc.c
$ gcc hellogcc.c
$ ls -1
a.out
hellogcc.c
如你所見,a.out
是編譯後默認生成的二進位文件。為查看你所新編譯的應用程序的輸出,只需要運行它,就像你運行任意本地二進位文件一樣:
$ ./a.out
Hello, GCC!
命名輸出的文件
文件名稱 a.out
是非常莫名其妙的,所以,如果你想具體指定可執行文件的名稱,你可以使用 -o
選項:
(LCTT 譯註:注意這和最近 Linux 內核廢棄的 a.out 格式無關,只是名字相同,這裡生成的 a.out 是 ELF 格式的 —— 也不知道誰給起了個 a.out
這破名字,在我看來,默認輸出文件名就應該是去掉了 .c
擴展名後的名字。by wxy)
$ gcc -o hellogcc hellogcc.c
$ ls
a.out hellogcc hellogcc.c
$ ./hellogcc
Hello, GCC!
當開發一個需要編譯多個 C 源文件文件的大型應用程序時,這種選項是很有用的。
在 GCC 編譯中的中間步驟
編譯實際上有四個步驟,即使在簡單的用例中 GCC 自動執行了這些步驟。
- 預處理 :GNU 的 C 預處理器(cpp)解析頭文件(
#include
語句),展開 宏 定義(#define
語句),並使用展開的源文件代碼來生成一個中間文件,如hellogcc.i
。 - 編譯 :在這個期間中,編譯器將預處理的源文件代碼轉換為指定 CPU 架構的彙編代碼。由此生成是彙編文件使用一個
.s
擴展名來命名,如在這個示例中的hellogcc.s
。 - 彙編 :彙編程序(
as
)將彙編代碼轉換為目標機器代碼,放在目標文件中,例如hellogcc.o
。 - 鏈接 :鏈接器(
ld
)將目標代碼和庫代碼鏈接起來生成一個可執行文件,例如hellogcc
。
在運行 GCC 時,可以使用 -v
選項來查看每一步的細節:
$ gcc -v -o hellogcc hellogcc.c
手動編譯代碼
體驗編譯的每個步驟可能是很有用的,因此在一些情況下,你不需要 GCC 完成所有的步驟。
首先,除源文件文件以外,刪除在當前文件夾下生成的文件。
$ rm a.out hellogcc.o
$ ls
hellogcc.c
預處理器
首先,啟動預處理器,將其輸出重定向為 hellogcc.i
:
$ cpp hellogcc.c > hellogcc.i
$ ls
hellogcc.c hellogcc.i
查看輸出文件,並注意一下預處理器是如何包含頭文件和擴展宏中的源文件代碼的。
編譯器
現在,你可以編譯代碼為彙編代碼。使用 -S
選項來設置 GCC 只生成彙編代碼:
$ gcc -S hellogcc.i
$ ls
hellogcc.c hellogcc.i hellogcc.s
$ cat hellogcc.s
查看彙編代碼,來看看生成了什麼。
彙編
使用你剛剛所生成的彙編代碼來創建一個目標文件:
$ as -o hellogcc.o hellogcc.s
$ ls
hellogcc.c hellogcc.i hellogcc.o hellogcc.s
鏈接
要生成一個可執行文件,你必須將對象文件鏈接到它所依賴的庫。這並不像前面的步驟那麼簡單,但它卻是有教育意義的:
$ ld -o hellogcc hellogcc.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: hellogcc.o: in function `main`:
hellogcc.c:(.text+0xa): undefined reference to `puts'
在鏈接器查找完 libc.so
庫後,出現一個引用 undefined puts
錯誤。你必須找出適合的鏈接器選項來鏈接必要的庫以解決這個問題。這不是一個小技巧,它取決於你的系統的布局。
在鏈接時,你必須鏈接代碼到 核心運行時 (CRT)目標,這是一組幫助二進位可執行文件啟動的子常式。鏈接器也需要知道在哪裡可以找到重要的系統庫,包括 libc
和 libgcc
,尤其是其中的特殊的開始和結束指令。這些指令可以通過 --start-group
和 --end-group
選項來分隔,或者使用指向 crtbegin.o
和 crtend.o
的路徑。
這個示例使用了 RHEL 8 上的路徑,因此你可能需要依據你的系統調整路徑。
$ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2
-o hello
/usr/lib64/crt1.o /usr/lib64/crti.o
--start-group
-L/usr/lib/gcc/x86_64-redhat-linux/8
-L/usr/lib64 -L/lib64 hello.o
-lgcc
--as-needed -lgcc_s
--no-as-needed -lc -lgcc
--end-group
/usr/lib64/crtn.o
在 Slackware 上,同樣的鏈接過程會使用一組不同的路徑,但是,你可以看到這其中的相似之處:
$ ld -static -o hello
-L/usr/lib64/gcc/x86_64-slackware-linux/11.2.0/
/usr/lib64/crt1.o /usr/lib64/crti.o hello.o /usr/lib64/crtn.o
--start-group
-lc -lgcc -lgcc_eh
--end-group
現在,運行由此生成的可執行文件:
$ ./hello
Hello, GCC!
一些有用的實用程序
下面是一些幫助檢查文件類型、 符號表 和鏈接到可執行文件的庫的實用程序。
使用 file
實用程序可以確定文件的類型:
$ file hellogcc.c
hellogcc.c: C source, ASCII text
$ file hellogcc.o
hellogcc.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$ file hellogcc
hellogcc: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb76b241d7d00871806e9fa5e814fee276d5bd1a, for GNU/Linux 3.2.0, not stripped
對目標文件使用 nm
實用程序可以列出 符號表 :
$ nm hellogcc.o
0000000000000000 T main
U puts
使用 ldd
實用程序來列出動態鏈接庫:
$ ldd hellogcc
linux-vdso.so.1 (0x00007ffe3bdd7000)
libc.so.6 => /lib64/libc.so.6 (0x00007f223395e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f2233b7e000)
總結
在這篇文章中,你了解到了 GCC 編譯中的各種中間步驟,和檢查文件類型、 符號表 和鏈接到可執行文件的庫的實用程序。在你下次使用 GCC 時,你將會明白它為你生成一個二進位文件所要做的步驟,並且當出現一些錯誤時,你會知道如何逐步處理解決問題。
via: https://opensource.com/article/22/5/gnu-c-compiler
作者:Jayashree Huttanagoudar 選題:lkxed 譯者:robsean 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive