C/C++編譯器常用參數
- Linux下常用的是gcc, icc(intel compiler)
- Windows下則是msvc與mingw
gcc
gcc and g++分別是gnu的c & c++編譯器 gcc/g++在執行編譯工作的時候,總共需要4步
- 預處理,生成.i的文件[預處理器cpp]
- 將預處理後的文件不轉換成組合語言,生成文件.s[編譯器egcs]
- 組語變為目標代碼(機器代碼)生成.o的文件[彙編器as]
- 連接目標代碼,生成可執行程序[鏈接器ld]
-c
- 只激活預處理,編譯,和彙編,也就是他只把程序做成obj文件。
- gcc -c hello.c 將生成hello.o的obj文件。
-S
- 只激活預處理和編譯,就是指把文件編譯成為彙編代碼。
- gcc -S hello.c 將生成hello.s的彙編代碼,你可以用文本編輯器察看。
-E
- 只激活預處理,這個不生成文件,你需要把它重定向到一個輸出文件裡面.
- gcc -E hello.c > plain.txt
-o
- 制定目標名稱,缺省的時候,gcc 編譯出來的文件是a.out
- gcc -o hello hello.c
-ansi
- 關閉gnu c中與ansi c不兼容的特性,激活ansi c的專有特性(包括禁止一些asm inline typeof關鍵字,以及UNIX,vax等預處理巨集。
-fno-asm
+ 此選項實現ansi選項的功能的一部分,它禁止將asm,inline和typeof用作關鍵字。-fno-strict-prototype
- 只對g++起作用,使用這個選項,g++將對不帶參數的函數,都認為是沒有顯式的對參數的個數和類型說明,而不是沒有參數.
- 而gcc無論是否使用這個參數,都將對沒有帶參數的函數,認為城沒有顯式說明的類型
-fthis-is-varialble
- 向傳統c++看齊,可以使用this當一般變量使用.
-include file
- 含某個代碼,簡單來說,就是便以某個文件,需要另一個文件的時候,就可以用它設定,功能就相當於在代碼中使用#include
; - gcc hello.c -include /tmp/hello.h
- 含某個代碼,簡單來說,就是便以某個文件,需要另一個文件的時候,就可以用它設定,功能就相當於在代碼中使用#include
-Dmacro
- 相當於C語言中的#define macro
- -Dmacro=defn
gcc -c hello.c -o hello.o -DDEBUG
int main()
{
#ifdef DEBUG
printf("this is before/n");
#endif
printf("helloworld/n");
#ifdef DEBUG
printf("this is end/n");
#endif
exit(0);
}
-Umacro
- 相當於C語言中的#undef macro
-Idir
- 在你是用#include"file"的時候,gcc/g++會先在當前目錄查找你所制定的頭文件,如果沒有找到,他回到預設的頭文件目錄找
- 如果使用-I制定了目錄,他回先在你所制定的目錄查找,然後再按常規的順序去找.
- 對於#include
;,gcc/g++會到-I制定的目錄查找,查找不到,然後將到系統的缺省的頭文件目錄查找
-I-
- 就是取消前一個參數的功能,所以一般在-Idir之後使用
-idirafter dir
- 在-I的目錄裡面查找失敗,講到這個目錄裡面查找.
-nostdinc
- 使編譯器不再系統缺省的頭文件目錄裡面找頭文件,一般和-I聯合使用,明確限定頭文件的位置
-C
- 在預處理的時候,不刪除註釋信息,一般和-E使用,有時候分析程序,用這個很方便的.
-M, -MM, -MD, -MMD
- 生成文件關聯的信息。包含目標文件所依賴的所有源代碼你可以用gcc -M hello.c來測試一下,很簡單。
- MM 和上面的那個一樣,但是它將忽略由#include
;造成的依賴關係。 - -MD與-MMD同上,但將輸出導致.d的檔案中。
-llibrary
- 制定編譯的時候使用的函式庫
- gcc -lcurses hello.c , 使用ncurses庫編譯程序.
-Ldir
- 制定編譯的時候,搜索庫的路徑。比如你自己的庫,可以用它制定目錄,不然編譯器將只在標準庫的目錄找。這個dir就是目錄的名稱。
-O0, -O1, -O2, -O3
- 編譯器的優化選項的4個級別,-O0表示沒有優化,-O1為缺省值,-O3優化級別最高
-gstabs, -gstabs+
- 以stabs格式聲稱調試信息,但是不包括gdb調試信息.
- 以stabs格式聲稱調試信息,並且包含僅供gdb使用的額外調試信息.
-ggdb
- 此選項將盡可能的生成gdb的可以使用的調試信息.
-static
- 此選項將禁止使用動態庫,所以,編譯出來的東西,一般都很大,也不需要什麼動態連接庫,就可以運行.
-share
- 此選項將盡量使用動態庫,所以生成文件比較小,但是需要系統由動態庫.
-traditional
- 試圖讓編譯器支持傳統的C語言特性
-x language filename
- 設定文件所使用的後綴名,對以後的多個有效。
- 預設C語言的後綴名稱是.c的,而C++的後綴名是.C或者.cpp
- 可以使用的參數嗎有下面的這些c, objective-c, c-header, c++, cpp-output, assembler, and assembler-with-cpp.
- gcc -x c hello.pig
-x none filename
- 關掉上一個選項,也就是讓gcc根據文件名後綴,自動識別文件類型.
- gcc -x c hello.pig -x none hello2.c
-fPIC
- fPIC 作用於編譯階段,告訴編譯器產生與位置無關程式碼(Position-Independent Code),
- 則產生的程式碼中,沒有絕對位址,全部使用相對位址,故而程式碼可以被加載器加載到記憶體的任意位址,這正是共用程式庫所要求的,共用程式庫被加載時,在記憶體的位置不是固定的。
- gcc -shared -fPIC -o 1.so 1.c
- 如果不加-fPIC,則加載.so檔案的程式碼段時,程式碼段參照的資料物件需要重定位, 重定位會修改程式碼段的內容,這就造成每個使用這個.so檔案程式碼段的程式在核心裡都會生成這個.so檔案程式碼段的copy.每個copy都不一樣,取決於 這個.so檔案程式碼段和資料段記憶體對映的位置.
- 不加fPIC編譯出來的so,是要再加載時根據加載到的位置再次重定位的.(因為它裡面的程式碼并不是位置無關程式碼)
- 如果被多個應用程式共同使用,那么它們必須每個程式維護一份so的程式碼副本了.(因為so被每個程式加載的位置都不同,顯然這些重定位后的程式碼也不同,當然無法共用)
- 我們總是用fPIC來生成so,也從來不用fPIC來生成a.
- fPIC與動態鏈結可以說基本沒有關係,libc.so一樣可以不用fPIC編譯,只是這樣的so必須要在加載到用戶程式的位址空間時重導所有表目.
因此,不用fPIC編譯so并不總是不好,如果你滿足以下4個需求/條件:
- 程式庫可能需要經常更新
- 式庫需要非常高的效率(尤其是有很多全域量的使用時)
- 式庫并不很大.
- 式庫基本不需要被多個應用程式共用
用沒有加這個參數的編譯后的共用程式庫,也可以使用的話,可能是兩個原因:
- gcc預設開啟-fPIC選項
- loader使你的程式碼位置無關
從GCC來看,shared應該是包含fPIC選項的,但似乎不是所以系統都支援,所以最好顯式加上fPIC選項。
函式庫(library)
- Library可分成三種,static、shared與dynamically loaded.
Static libraries
- Static 程式庫用於靜態連結,簡單講是把一堆object檔用ar(archiver)包裝集合起來,檔名以`.a’ 結尾。優點是執行效能通常會比後兩者快,
- 而且因為是靜態連結,所以不易發生執行時找不到library或版本錯置而無法執行的問題。缺點則是檔案較大,維護度較低;例如library如果發現bug需要更新,那麼就必須重新連結執行檔。
- 編譯方式很簡單,先用`-c’ 編出object 檔,再用ar 包起來即可。
- gcc -c hello.c world.c / 編出hello.o 與world.o /
- ar rcs libmylib.a hello.o world.o / 包成limylib.a /
- 這樣就可以建出一個檔名為libmylib.a 的檔。輸出的檔名其實沒有硬性規定,但如果想要配合gcc 的’-l’ 參數來連結,一定要以
lib’ 開頭,中間是你要的library名稱,然後緊接著
.a’ 結尾。
#include “mylib.h”
int main() {
hello();
world();
}
gcc main.c libmylib.a或gcc main.c -L. -lmylib
shared libraries
Shared library 會在程式執行起始時才被自動載入。因為程式庫與執行檔是分離的,所以維護彈性較好。有兩點要注意,shared library是在程式起始時就要被載入,而不是執行中用到才載入,而且在連結階段需要有該程式庫才能進行連結。
首先有一些名詞要弄懂,soname、real name與linker name。
- soname 用來表示是一個特定library 的名稱,像是libmylib.so.1 。前面以
lib’ 開頭,接著是該library 的名稱,然後是
.so’ ,接著是版號,用來表名他的介面;如果介面改變時,就會增加版號來維護相容度。 - real name 是實際放有library程式的檔案名稱,後面會再加上minor 版號與release 版號,像是libmylib.so.1.0.0 。
- 一般來說,版號的改變規則是尾碼的release版號用於程式內容的修正,介面完全沒有改變。中間的minor用於有新增加介面,但相舊介面沒改變,所以與舊版本相容。最前面的version版號用於原介面有移除或改變,與舊版不相容時。
- linker name是用於連結時的名稱,是不含版號的soname ,如: libmylib.so。通常linker name與real name是用ln 指到對應的real name ,用來提供彈性與維護性。
- soname 用來表示是一個特定library 的名稱,像是libmylib.so.1 。前面以
shared library的製作過程較複雜:
- gcc -c -fPIC hello.c world.c
- 編譯時要加上-fPIC 用來產生position-independent code。
- gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0 hello.o world.o
- shared 表示要編譯成shared library
- Wl 用於參遞參數給linker,因此-soname與libmylib.so.1會被傳給linker處理。
- soname用來指名soname 為limylib.so.1
- library會被輸出成libmylib.so.1.0.0 (也就是real name)
- 若不指定soname 的話,在編譯結連後的執行檔會以連時的library檔名為soname,並載入他。否則是載入soname指定的library檔案。
- 可以利用objdump 來看library 的soname。objdump -p libmylib.so
- 在編譯後再用ln 來建立soname 與linker name 兩個檔案。
- ln -s libmylib.so.1.0.0 libmylib.so
- ln -s libmylib.so.1.0.0 libmylib.so.1
- gcc -c -fPIC hello.c world.c
使用時與static library 相同
- gcc main.c libmylib.so 直接指定與libmylib.so 連結。
- 或者用 gcc main.c -L. -lmylib inker會搜尋libmylib.so 來進行連結
如果目錄下同時有static與shared library的話,會以shared為主。
- 使用-static 參數可以避免使用shared連結。
- gcc main.c -static -L. -lmylib
可以用ldd 看編譯出的執行檔與shared程式庫的相依性
- ldd a.out
找不到shared library的處理方式
- 假設找不到ibmylib.so.1
- 把libmylib.so.1 安裝到系統的library目錄,如/usr/lib下。
- 設定/etc/ld.so.conf ,加入一個新的library搜尋目錄,並執行ldconfig更新快取
- 設定LD_LIBRARY_PATH 環境變數來搜尋library, LD_LIBRARY_PATH=. ./a.out
Dynamically loaded libraries
- Dynamicaaly loaded libraries 才是像windows 所用的DLL ,在使用到時才載入,編譯連結時不需要相關的library。動態載入庫常被用於像plugins的應用。
- 動態載入是透過一套dl function來處理。
include
- void dlopen(const char filename, int flag); 開啟載入filename 指定的library。
- void dlsym(void handle, const char *symbol); 取得symbol 指定的symbol name在library被載入的記憶體位址。
- int dlclose(void *handle);關閉dlopen開啟的handle。
- char *dlerror(void); 傳回最近所發生的錯誤訊息。
// dltest.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main() {
void *handle;
void (*f)();
char *error;
/* 開啟之前所撰寫的libmylib.so 程式庫*/
handle = dlopen(“./libmylib.so”, RTLD_LAZY);
if( !handle ) {
fputs( dlerror(), stderr);
exit(1);
}
/* 取得hello function 的address */
f = dlsym(handle, “hello”);
if(( error=dlerror())!=NULL) {
fputs(error, stderr);
exit(1);
}
/* 呼叫該function */
f();
dlclose(handle);
}
- 編譯時要加上-ldl 參數來與dl library 連結: gcc dltest.c -ldl