介紹
GCC(英文全拼:GNU Compiler Collection)是 GNU 工具鏈的主要組成部分,是一套以 GPL 和 LGPL 許可證發布的程式語言編譯器自由軟體,由 Richard Stallman 于 1985 年開始開發,
GCC 原名為 GNU C語言編譯器,因為它原本只能處理 C 語言,但如今的 GCC 不僅可以編譯 C、C++ 和 Objective-C,還可以通過不同的前端模塊支持各種語言,包括 Java、Fortran、Ada、Pascal、Go 和 D 語言等等,
編譯程序
GCC 的編譯程序可以劃分為四個階段:預處理(Pre-Processing)、編譯(Compiling)、匯編(Assembling)以及鏈接(Linking),

Linux 程式員可以根據自己的需要控制 GCC 的編譯階段,以便檢查或使用編譯器在該階段的輸出資訊,幫助除錯和優化程式,以 C 語言為例,從源檔案的編譯到可執行檔案的運行,整個程序大致如下,

各檔案后綴說明如下:
| 后綴 | 描述 | 后綴 | 描述 |
|---|---|---|---|
| .c | C 源檔案 | .s/.S | 匯編語言源檔案 |
| .C/.cc/.cxx/.cpp | C++ 源檔案 | .o/.obj | 目標檔案 |
| .h | C/C++ 頭檔案 | .a/.lib | 靜態庫 |
| .i/.ii | 經過預處理的 C/C++ 檔案 | .so/.dll | 動態庫 |
語法
gcc [options] file...
選項
-pass-exit-codes:從一個階段以最高錯誤代碼退出,--target-help:顯示特定于目標的命令列選項,--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...]:顯示特定型別的命令列選項(使用-v --help顯示子行程的命令列選項),-dumpspecs:顯示所有內置規范字串,-dumpversion:顯示編譯器的版本,-dumpmachine:顯示編譯器的目標處理器,-print-search-dirs:顯示編譯器搜索路徑中的目錄,-print-libgcc-file-name:顯示編譯器配套庫的名稱,-print-file-name=<lib>:顯示庫<lib>的完整路徑,-print-prog-name=<prog>:顯示編譯器組件<prog>的完整路徑,-print-multiarch:顯示目標的規范化 GNU 三元組,用作庫路徑中的一個組件,-print-multi-directory:顯示 libgcc 版本的根目錄,-print-multi-lib:顯示命令列選項和多個庫搜索目錄之間的映射,-print-multi-os-directory:顯示作業系統庫的相對路徑,-print-sysroot:顯示目標庫目錄,-print-sysroot-headers-suffix:顯示用于查找標題的 sysroot 后綴,-Wa,<options>:將逗號分隔的<options>傳遞給匯編器(assembler),-Wp,<options>:將逗號分隔的<options>傳遞給前處理器(preprocessor),-Wl,<options>:將逗號分隔的<options>傳遞給聯結器(linker),-Xassembler <arg>:將<arg>傳遞給匯編器(assembler),-Xpreprocessor <arg>:將<arg>傳遞給前處理器(preprocessor),-Xlinker <arg>:將<arg>傳遞給聯結器(linker),-save-temps:不用洗掉中間檔案,-save-temps=<arg>:不用洗掉指定的中間檔案,-no-canonical-prefixes:在構建其他 gcc 組件的相對前綴時,不要規范化路徑,-pipe:使用管道而不是中間檔案,-time:為每個子流程的執行計時,-specs=<file>:使用<file>的內容覆寫內置規范,-std=<standard>:假設輸入源為<standard>,--sysroot=<directory>:使用<directory>作為頭檔案和庫的根目錄,-B <directory>:將<directory>添加到編譯器的搜索路徑,-v:顯示編譯器呼叫的程式,-###:與-v類似,但參考的選項和命令不執行,-E:僅執行預處理(不要編譯、匯編或鏈接),-S:只編譯(不匯編或鏈接),-c:編譯和匯編,但不鏈接,-o <file>:指定輸出檔案,-pie:創建一個動態鏈接、位置無關的可執行檔案,-I:指定頭檔案的包含路徑,-L:指定鏈接庫的包含路徑,-shared:創建共享庫/動態庫,-static:使用靜態鏈接,--help:顯示幫助資訊,--version:顯示編譯器版本資訊,
示例
階段編譯
假設有檔案 hello.c,內容如下:
#include <stdio.h>
int main(void)
{
printf("Hello, GetIoT\n");
return 0;
}
編譯 hello.c,默認輸出 a.out
gcc hello.c
編譯 hello.c 并指定輸出檔案為 hello
gcc hello.c -o hello
只執行預處理,輸出 hello.i 源檔案
gcc -E hello.c -o hello.i
只執行預處理和編譯,輸出 hello.s 匯編檔案
gcc -S hello.c
也可以由 hello.i 檔案生成 hello.s 匯編檔案
gcc -S hello.i -o hello.s
只執行預處理、編譯和匯編,輸出 hello.o 目標檔案
gcc -c hello.c
也可以由 hello.i 或 hello.s 生成目標檔案 hello.o
gcc -c hello.i -o hello.o
gcc -c hello.s -o hello.o
由 hello.o 目標檔案鏈接成可執行檔案 hello
gcc hello.o -o hello
使用靜態庫
創建一個 foo.c 檔案,內容如下:
#include <stdio.h>
void foo(void)
{
printf("Here is a static library\n");
}
將 foo.c 編譯成靜態庫 libfoo.a
gcc -c foo.c # 生成 foo.o 目標檔案
ar rcs libfoo.a foo.o # 生成 libfoo.a 靜態庫
查看檔案描述
$ file *
foo.c: C source, ASCII text
foo.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
libfoo.a: current ar archive
修改 hello.c 檔案,呼叫 foo 函式
#include <stdio.h>
void foo(void);
int main(void)
{
printf("Hello, GetIoT\n");
foo();
return 0;
}
編譯 hello.c 并鏈接靜態庫 libfoo.a(加上 -static 選項)
gcc hello.c -static libfoo.a -o hello
也可以使用 -L 指定庫的搜索路徑,并使用 -l 指定庫名
gcc hello.c -static -L. -lfoo -o hello
運行結果
$ ./hello
Hello, GetIoT
Here is a static library
查看 hello 檔案描述
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=b72236c2211dd8f0c3003bc02ad5e70bb2354e8c, for GNU/Linux 3.2.0, not stripped
使用共享庫
修改 foo.c 檔案,內容如下:
#include <stdio.h>
void foo(void)
{
printf("Here is a shared library\n");
}
將其編譯為動態庫/共享庫(由于動態庫可以被多個行程共享加載,所以需要使用 -fPIC 選項生成位置無關的代碼
gcc foo.c -shared -fPIC -o libfoo.so
hello.c 代碼無需修改,內容仍然如下:
#include <stdio.h>
void foo(void);
int main(void)
{
printf("Hello, GetIoT\n");
foo();
return 0;
}
編譯 hello.c 并鏈接共享庫 libfoo.so
gcc hello.c libfoo.so -o hello
也可以使用 -L 和 -l 選項指定庫的路徑和名稱
gcc hello.c -L. -lfoo -o hello
但是此時運行 hello 程式失敗
$ ./hello
./hello: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
原因是找不到 libfoo.so 共享庫
$ ldd hello
linux-vdso.so.1 (0x00007fff5276d000)
libfoo.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcc90fa7000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcc911bd000)
這是因為 libfoo.so 并不在 Linux 系統的默認搜索目錄中,解決辦法是我們主動告訴系統,libfoo.so 共享庫在哪里,
方式一:設定環境變數 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$(pwd)
將 libfoo.so 所在的當前目錄添加到 LD_LIBRARY_PATH 變數,再次執行 hello
$ ./hello
Hello, GetIoT
Here is a shared library
方式二:使用 rpath 將共享庫位置嵌入到程式
gcc hello.c -L. -lfoo -Wl,-rpath=`pwd` -o hello
rpath 即 run path,是種可以將共享庫位置嵌入程式中的方法,從而不用依賴于默認位置和環境變數,這里在鏈接時使用 -Wl,-rpath=/path/to/yours 選項,-Wl 會發送以逗號分隔的選項到聯結器,注意逗號分隔符后面沒有空格哦,
這種方式要求共享庫必須有一個固定的安裝路徑,欠缺靈活性,不過如果設定了 LD_LIBRARY_PATH,程式加載時也是會到相應路徑尋找共享庫的,
方式三:將 libfoo.so 共享庫添加到系統路徑
sudo cp libfoo.so /usr/lib/
執行程式
$ ./hello
Hello, GetIoT
Here is a shared library
如果 hello 程式仍然運行失敗,請嘗試執行 ldconfig 命令更新共享庫的快取串列,
此時,再次查看 hello 程式的共享庫依賴
$ ldd hello
linux-vdso.so.1 (0x00007ffecfbb1000)
libfoo.so => /lib/libfoo.so (0x00007f3f3f1ad000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3f3efbb000)
/lib64/ld-linux-x86-64.so.2 (0x00007f3f3f1d6000)
可以看到 libfoo.so 已經被發現了,其中 /lib 是 /usr/lib 目錄的軟鏈接,
示例代碼可以在 GitHub 找到,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287334.html
標籤:其他
下一篇:畢業這三年,我們都干了些啥?
