Linux中國

GNU C 編譯器的程序員入門指南

帶你一窺生成二進位文件步驟的幕後,以便在出現一些錯誤時,你知道如何逐步解決問題。

C 語言廣為人知,深受新老程序員的好評。使用 C 語言編寫的源文件代碼,使用了標準的英語術語,因而人們可以方便閱讀。然而,計算機只能理解二進位代碼。為將代碼轉換為機器語言,你需要使用一種被稱為 編譯 compiler 的工具。

最常見的編譯器是 GCC GNU 編譯器集 GNU Compiler Collection )。編譯過程涉及到一系列的中間步驟及相關工具。

安裝 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 自動執行了這些步驟。

  1. 預處理 Pre-Processing :GNU 的 C 預處理器(cpp)解析頭文件(#include 語句),展開 macros 定義(#define 語句),並使用展開的源文件代碼來生成一個中間文件,如 hellogcc.i
  2. 編譯 Compilation :在這個期間中,編譯器將預處理的源文件代碼轉換為指定 CPU 架構的彙編代碼。由此生成是彙編文件使用一個 .s 擴展名來命名,如在這個示例中的 hellogcc.s
  3. 彙編 Assembly :彙編程序(as)將彙編代碼轉換為目標機器代碼,放在目標文件中,例如 hellogcc.o
  4. 鏈接 Linking :鏈接器(ld)將目標代碼和庫代碼鏈接起來生成一個可執行文件,例如 hellogcc

在運行 GCC 時,可以使用 -v 選項來查看每一步的細節:

$ gcc -v -o hellogcc hellogcc.c

Compiler flowchart

手動編譯代碼

體驗編譯的每個步驟可能是很有用的,因此在一些情況下,你不需要 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&apos;

在鏈接器查找完 libc.so 庫後,出現一個引用 undefined puts 錯誤。你必須找出適合的鏈接器選項來鏈接必要的庫以解決這個問題。這不是一個小技巧,它取決於你的系統的布局。

在鏈接時,你必須鏈接代碼到 核心運行時 core runtime (CRT)目標,這是一組幫助二進位可執行文件啟動的子常式。鏈接器也需要知道在哪裡可以找到重要的系統庫,包括 libclibgcc,尤其是其中的特殊的開始和結束指令。這些指令可以通過 --start-group--end-group 選項來分隔,或者使用指向 crtbegin.ocrtend.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!

一些有用的實用程序

下面是一些幫助檢查文件類型、 符號表 symbol tables 和鏈接到可執行文件的庫的實用程序。

使用 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 實用程序可以列出 符號表 symbol tables

$ 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 編譯中的各種中間步驟,和檢查文件類型、 符號表 symbol tables 和鏈接到可執行文件的庫的實用程序。在你下次使用 GCC 時,你將會明白它為你生成一個二進位文件所要做的步驟,並且當出現一些錯誤時,你會知道如何逐步處理解決問題。

via: https://opensource.com/article/22/5/gnu-c-compiler

作者:Jayashree Huttanagoudar 選題:lkxed 譯者:robsean 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
1
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國

    Linux中國

    DevOps 將去向何方?

    微軟、谷歌、亞馬遜、IBM 和甲骨文如今都在關注云上的 DevOps。這些大公司正在給企業提供 IT 自動化的服務。然而,DevOps 仍然在持續的演進中。DevSecOps、AIOps 和 NoOps 正在成為下一個流行詞。