GCC 的命令的權威解釋還是要查詢 官方網站 https://gcc.gnu.org/,同時一些鏈接選項不方便在網站上查詢可以利用操作系統的 man 指令來查詢(比如 man ld),
這里記錄一些常用選項,不定時更新。
1.最常用的選項:
-o file 輸出目標文件;
-E 將源文件進行預處理;
gcc -E test.c -o test.i
-S 將源文件進行匯編處理;
gcc -S test.c -o test.s
-c 編譯源文件;
gcc -c test.c -o test.o
最終鏈接步驟:
gcc test1.o test2.o test3.o -o test
-Wall 打開所有的警告
gcc -c test.c -Wall -o test
-O打開優化選項:
-O0 (默認)減少編譯時間,生成 debug 級別的結果;
-O1/O2/O3 優化級別逐級上升,一般 release 版本的優化等級都會采用 O2 級別;
gcc -c test.c -O2 -o test
-g 生成當前系統本地格式化的調試信息, GDB 可識別並調試;
-ggdb 專門為 gdb 生成調試信息;
gcc test.c -o test -g gdb test
-shared 生成一個可執行文件可以動態鏈接的共享庫,這是個鏈接選項,編譯生成共享庫的目標文件的源文件時通常需要添加編譯選項 -fpic;
-fpic 生成位置無關代碼,在編譯共享庫的目標文件時使用,這是一個編譯選項;
gcc -c test.c -o test.o -fpic gcc -shared -o libtest.so test.o
-I(大寫 i) (-Idir 或者 -I dir)添加頭文件搜索目錄, 這是一個編譯選項;
test.c 包含 test.h, test.h 位於./inc 中 gcc -c test.c -o test.o -I inc
-l(小寫L) (-llib 或者 -l lib)執行鏈接時的共享庫名稱,如當前有一個共享庫 libcshare.so, 那么鏈接命令如下:
gcc test.c -o test -lcshare 或 gcc test.c -o test -l cshare
如果當前鏈接目錄下同時存在相同名稱的共享庫和靜態庫,比如libcshare.so、libcshare.a,在不加任何選項的情況下,
編譯器優先選擇鏈接共享庫,除非添加-static;
-L (-Ldir)添加鏈接時共享庫搜索目錄;
gcc test.c -o test -lcshare -L/xx/xx
-std= 選擇適配的 C/C++ 標准版本,可選的有 c89/c90/c99/c11 等;C++有c++98/c++11/c++14等等;
2.其他常用選項;
-M 為 GNU make 輸出顯式依賴規則,包含標准庫頭文件及系統頭文件;
-MM 類似於 -M, 但是只會包含當前工程的頭文件依賴;
-MF file 把依賴結果寫入到 file;
gcc -M test.c -Ixxx/xxx gcc -MM test.c -Ixxx/xxx gcc -MM test.c -Ixxx/xxx -MF test.d
然后查看依賴文件:
cat test.d test.o: test.c xxx/xxx/test.h
此外還有其他常用的鏈接選項:
-Wl,-Bsymbolic 優先使用本地符號, 防止鏈接當前共享庫的應用程序中的符號覆蓋當前共享庫中同名的符號;
-Wl,-soname,libtest.so.1 設置共享庫的 SONAME 為 libtest.so.1,readelf -d libtest.so 可以查看共享庫的 SONAME;
-Wl,-rpath=/xxx/xxx 設置運行時共享庫搜索目錄;
-Wl,-rpath=. 設置運行時的共享庫搜索目錄優先選擇當前目錄;
-Wl,--version-script=test.map 控制共享庫的導出符號,符號表的形式為:
VER_1 { global: test1; test2; test3; local: *; }; VER_2 { global: test4; } VER_1; #依賴於版本1
如果只需要控制符號表,可以寫成如下形式:
{ global: test1; test2; test3; local: *; };
-Wl,--retain-symbols-file=test.sym 控制靜態庫的導出符號,test.sym 的格式如下
test1 test2 test3
創建共享庫時,添加以上鏈接選項 可以同時控制靜態庫導出符號和共享庫導出符號,如下所示:
gcc test1.o test2.o test3.o test4.o -o libtest.so -shared -Wl,--version-script=test.map,--retain-symbols-file=test.sym
3.另一種控制符號導出的方式 -fvisibility=[default|internal|hidden|protected]
如果要公開你的接口或者 API,那么就需要將 __attribute__ ((visibility ("xxxxxx"))) 放在你需要公開的結構、類或者函數聲明中,然后在編譯
選項中增加 -fvisibility=xxxx(可選的項有 default、internal、hidden 和 protected)。
舉個例子,如果編譯選項中添加 -fvisibility=hidden ,那么所有被聲明為 __attribute__ ((visibility ("hidden"))) 符號都將被隱藏,其他的應用程序
或者共享庫在鏈接本庫的時候會報出類似於“對 xxx 未定義的引用……”這樣的錯誤;如果想要導出符號,則需要給符號以 __attribute__ ((visibility ("default")))
這樣的聲明,這樣應用程序或者其他的共享庫在鏈接本庫時就會找到導出的符號,可以正確鏈接。
以下是偽碼示例:
#if __GNUC__ >= 4 #define EXP_DEF __attribute__ ((visibility ("default"))) #define IMP_DEF __attribute__ ((visibility ("hidden"))) #else #define EXP_DEF #define IMP_DEF #endif #ifdef __cplusplus extern "C" { #endif // 這是 C 風格的導出函數 EXP_DEF void func(int a); #ifdef __cplusplus } #endif // C++ 導出類 class EXP_DEF person { public: person(int a); …… }
對於其他選項,由於不是特別常用而且筆者精力有限,不做過多總結,讀者可以查閱 gcc 的官方文檔或者以下的參考資料來學習:
參考資料:https://gcc.gnu.org/wiki/Visibility
4.編譯期字符集編碼的控制
假設我們當前的源文件是 GBK 編碼,但是我們想要讓應用程序在運行時以 UTF-8 編碼來顯示中文,可以在編譯原文件時輸入以下命令:
CFLG += -finput-charset=gbk CFLG += -fexec-charset=utf-8
gcc -c test.c -O2 -o test $(CFLG)
這樣,程序在編譯時會將源文件進行轉碼操作,然后運行時就會將中文字符以 UTF-8 的方式來呈現。
5.對於linux下應用程序和共享庫間接依賴問題的心得(-rpath、-rpath-link、-unresolved-symbols 等選項的選擇)
我想經常在linux下編譯應用程序的朋友必定會遇到過這樣的問題:在編譯應用程序時,程序所依賴的共享庫可能會依賴另一個共享庫,
在沒有經過適當的處理的情況下,最終鏈接應用程序時鏈接器極有可能會報出找不到依賴共享庫(/usr/bin/ld: warning: xxx.so, needby xxx.so
, not found,try using -rpath or -rpath-link) 以及未定義的引用(/usr/bin/ld: xxx.so: undefined reference to 'xxx')這樣的錯誤,現在我首先
在這里復現一下該問題,我的開發平台是 Ubuntu-Server 20.04.3 LTS-x86_64,gcc version 9.3.0。
設當前項目有三個目標libalib.so、libblib.so 以及可執行程序 main,它們的源文件和頭文件分別為 alib.c/alib.h、blib.c/blib.h、main.c,
其中,main 依賴於 blib,blib 依賴於 alib,如下所示:
/*--------------------libalib.so--------------------*/ /* alib.h */ #ifndef ALIB_H #define ALIB_H int afunc(); #endif /* alib.c */ #include "alib.h" int afunc() { return 1024; } /*--------------------libblib.so--------------------*/ /* blib.h */ #ifndef BLIB_H #define BLIB_H int bfunc(); #endif /* blib.c */ #include "alib.h" int bfunc() { return afunc(); } /*----------------------main---------------------*/ /* main.c */ #include <stdio.h> #include "blib.h" int main(int argc, char *argv[]) { printf("b = %d\n", bfunc()); } /*----------------------Makefile---------------------*/ CC := gcc -Wall main:main.c libblib.so libalib.so $(CC) $< -o $@ -lblib -L. libblib.so:blib.o libalib.so $(CC) -shared -o $@ $< -lalib -L. blib.o:blib.c $(CC) -fPIC -c $< libalib.so:alib.o $(CC) -shared -o $@ $< -L. alib.o:alib.c $(CC) -fPIC -c $< .PHONY: clean: rm -f *.o main *.so /*---------------------------------------------------*/
編寫完程序后,輸入make進行編譯,結果如下
gcc -Wall -fPIC -c blib.c gcc -Wall -fPIC -c alib.c gcc -Wall -shared -o libalib.so alib.o -L. gcc -Wall -shared -o libblib.so blib.o -lalib -L. gcc -Wall main.c -o main -lblib -L. /usr/bin/ld: warning: libalib.so, needed by ./libblib.so, not found (try using -rpath or -rpath-link) /usr/bin/ld: ./libblib.so: undefined reference to `afunc' collect2: error: ld returned 1 exit status make: *** [Makefile:4: main] Error 1
為什么會出現這個錯誤呢?我的理解是,鏈接器在鏈接可執行程序的時候會進行一次運行時(RUNTIME)查找,並主動按照順
序檢查所依賴共享庫中的符號,如果它依賴的共享庫同時依賴了其他的模塊,那么它會沿着這樣的依賴路徑一直查找下去。如果發現
有符號未定義,鏈接器就會報錯終止,所以對於我們的例子,main 調用了 bfunc,bfunc 中調用了 afunc,那么在鏈接生成 main 的時
候,鏈接器必須知道 afunc 是否定義,而我們在成功生成 libalib.so 和 libblib.so 后,由於並未設定程序運行時查找路徑,所以 libalib.so
找不到 libblib.so,所以 main 無法確定 afunc 是否定義,所以有了以上報錯。
那么如何解決該問題?方法有這么幾種:
1.最簡單的方法就是先設置環境變量,指定運行時庫目錄位置,直接執行 export LD_LIBRARY_PATH=xxx,然后再進行鏈接;
2.將運行時的共享庫搜索目錄 /etc/ld.so.conf.d/ 寫入一個配置文件 xxx.conf,在這個文件里指定要搜索的目錄,然后執行 ldconfig,
這樣會將你指定的目錄記錄到系統全局的目錄表里,任何一個共享庫都會搜索你指定的目錄;
3.鏈接選項添加 -Wl,-unresolved-symbols=ignore-in-shared-libs 忽略該錯誤,這樣可以正常生成可執行程序,但是程序運行階段依
舊要解決找不到共享庫的問題;
4.采用提示的 -Wl,-rpath=xxx 指定運行時查找路徑,鏈接器會將該路徑寫到應用程序,在運行時會優先查找設定的路徑;
5.采用提示的 -Wl,-rpath-link=xxx 指定運行時查找路徑,鏈接器僅在鏈接階段進行查找,並不會將該路徑記錄到應用程序;
6.如果依賴的共享庫數量較少,可以讓應用程序依賴所有的直接、間接的共享庫。