相關學習資料html
http://gcc.gnu.org/ https://gcc.gnu.org/onlinedocs/ http://zh.wikipedia.org/zh/GCC http://blog.csdn.net/casularm/article/details/316149 http://www.bccn.net/Article/kfyy/cyy/jc/200409/9.html http://linux.chinaunix.net/techdoc/develop/2008/12/16/1053006.shtml http://www.91linux.com/html/article/program/cpp/20071203/8745.html http://www.cnblogs.com/hnrainll/archive/2012/07/05/2578277.html http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2287738.html http://www.chinaunix.net/old_jh/23/408225.html http://blogimg.chinaunix.net/blog/upfile/070907021605.pdf
目錄linux
1. GCC簡介 2. GCC的編譯 3. GCC的命令行參數 4. GCC編譯中遇到的常見錯誤 5. Makefile 6. Makefile Example
1. GCC簡介程序員
GCC(GNU Compiler Collection GNU 編譯器家族)。GCC目前能夠支持:shell
1. C 2. Ada 3. C++ 4. Java 5. Objective C 6. Pascal 語言 7. COBOL 8. 函數式編程 9. 邏輯編程的Mercury等等
GCC目前幾乎支持全部的硬件(處理器)架構編程
2. GCC的編譯ubuntu
一個程序編譯過程是分爲四個階段進行的,即:架構
1. 預處理(也稱預編譯,Preprocessing) 2. 編譯(Linking) 3. 彙編 (Assembly) 4. 鏈接(Linking)
使用gcc指令能夠一步完成,也能夠單步獨立進行,方便程序員獲取中間代碼代碼,進行調試函數式編程
0x0: 編寫源代碼函數
0x1: 預處理(也Preprocessing)工具
預處理是C語言程序從源代碼變成可執行程序的第一步,主要是C語言編譯器對各類預處理命令進行處理,包括:
1. 頭文件的包含: #include 2. 宏定義的擴展: 1) #define: 定義一個預處理宏 #define MACRO_NAME(args) tokens(opt) 以後出現的MACRO_NAME將被替代爲所定義的標記(tokens). 宏可帶參數, 然後面的標記也是可選的. 2) #undef: 取消宏的定義 #define除了能夠獨立使用以便靈活設置一些參數外,還經常和條件編譯語法結合使用,以便靈活地控制代碼塊的編譯與否,也能夠用來避免同一個頭文件的屢次包含 3. 條件編譯的選擇: 1) #ifdef: 判斷某個宏是否被定義, 若已定義, 執行隨後的語句 2) #ifndef: 與#ifdef相反, 判斷某個宏是否未被定義 3) #if: 編譯預處理中的條件命令, 至關於C語法中的if語句 4) #elif: 若#if, #ifdef, #ifndef或前面的#elif條件不知足, 則執行#elif以後的語句, 至關於C語法中的else-if 5) #else: 與#if, #ifdef, #ifndef對應, 若這些條件不知足, 則執行#else以後的語句, 至關於C語法中的else 6) #endif: #if, #ifdef, #ifndef這些條件命令的結束標誌. 7) #line: 標誌該語句所在的行號 4. 編譯器指示指令(給編譯器看的指令) 1) #pragma: 說明編譯器信息 #pragma用編譯器用來添加新的預處理功能或者顯示一些編譯信息. #pragma的格式是各編譯器特定的, gcc的以下: #pragma GCC name token(s) 2) #warning: 顯示編譯警告信息 #warning, #error分別用於在編譯時顯示警告和錯誤信息 #error tokens 3) #error: 顯示編譯錯誤信息 #warning tokens
gcc預處理指令:
gcc -E test.c -o test.i(輸出預處理內容到文件)
gcc -E test.c(輸出預處理內容到命令行)
0x2: 編譯(Linking)
C語言編譯器會進行以下步驟進行編譯過程:
1. 詞法分析 2. 語法分析: 經過-std指定遵循哪一個標準 3. 代碼優化和存儲分配: 根據優化(-O)要求進行 4. 接着會把源代碼翻譯成中間語言,即彙編語言(由於C/C++是編譯性語言)
gcc編譯指令:
gcc -S test.i -o test.s(-S選項,表示在程序編譯期間,在生成彙編代碼後,中止,-o輸出彙編代碼文件)
0x3: 彙編(Assembly)
把做爲中間結果的彙編代碼翻譯成了機器代碼,即目標代碼(能夠在目標機器運行的機器碼),這一步其實就是咱們在進行彙編編程中的彙編編譯過程。這一步產生的所謂機器代碼還不能夠直接運行,還須要下一步的連接程序進行連接後才能夠
對於這一步的工做來講,咱們可使用:
1. as彙編工具來彙編AT&T格式的彙編語言,由於gcc產生的中間代碼就是AT&T格式的 2. 或者直接用gcc的彙編編譯器來進行彙編編譯
這一步執行後,獲得的就已是機器碼了
1. 用as把彙編語言彙編成目標代碼 //注意,這裏的.s文件是上一步編譯後的文件 as -o test.o test.s 2. gcc彙編指令 gcc -c test.s -o test.o
0x4: 連接(Linking)
連接的目的是處理可重定位文件(.so、.dll),重定位是將"符號引用"與"符號定義"進行連接的過程。這句話能夠這麼理解,咱們在寫代碼中會使用到不少的函數和變量,這些"函數名"、和"變量名"最終須要被轉換爲對應的虛擬內存地址,CPU才能夠執行,這個過程就是連接過程
連接又分爲:
1. 靜態連接 程序開發階段程序員用ld(gcc實際上在後臺調用了ld)靜態連接器手動連接的過程 例如: 若是連接到可執行文件中的是靜態鏈接庫libmyprintf.a,那麼. rodata節區在連接後須要被重定位到一個絕對的虛擬內存地址,以便程序運行時可以正確訪問該節區中的字符串信息 2. 動態連接 動態連接則是程序運行期間系統調用動態連接器(ld-linux.so)自動連接的過程 例如: 對於puts,由於它是動態鏈接庫libc.so中定義的函數,因此會在程序運行時經過動態符號連接找出puts函數在內存中的地址,以便程序調用該函數
關於交叉編譯
交叉編譯通俗地講就是在一種平臺上編譯出能運行在體系結構不一樣的另外一種平臺上,好比在咱們地PC平臺(X86 CPU)上編譯出能運行在sparc CPU平臺上的程序,編譯獲得的程序在X86 CPU平臺上是不能運行的,必須放到sparc CPU平臺上才能運行。固然兩個平臺用的都是linux。這種方法在異平臺移植和嵌入式開發時用得很是廣泛。
相對與交叉編譯,咱們日常作的編譯就叫本地編譯,也就是在當前平臺編譯,編譯獲得的程序也是在本地執行。
用來編譯這種程序的編譯器就叫交叉編譯器,相對來講,用來作本地編譯的就叫本地編譯器,通常用的都是gcc,但這種gcc跟本地的gcc編譯器是不同的,須要在編譯gcc時用特定的configure參數才能獲得支持交叉編譯的gcc。
爲了避免跟本地編譯器混淆,交叉編譯器的名字通常都有前綴,好比sparc-xxxx-linux-gnu-gcc、sparc-xxxx-linux-gnu-g++等等
交叉編譯器使用方法跟本地的gcc差很少,但有一點必需要注意的是:必須用-L和-I參數指定編譯器用sparc系統的庫和頭文件,不能用本地(X86)的庫,例如
sparc-xxxx-linux-gnu-gcc test.c -L/path/to/sparcLib -I/path/to/sparcInclude
3. GCC的命令行參數
Usage: gcc [options] file... Options: 1) -combine: 一次傳給gcc多個源文件 2) -save-temps: 保留中間文件 3) -B <directory>: 添加gcc搜索的源文件路徑 4) -E 源代碼file: 只單獨進行預處理(預編譯)這一步 5) -S 預處理後的file: 只單獨進行編譯這一步 6) -c 編譯後待彙編的文件: 只進行編譯和彙編這兩步 7) -o <file>: 輸出文件 8) -l參數 -l參數就是用來指定程序要連接的庫,-l參數緊接着就是庫名,庫名和真正的庫文件名之間有一個對應關係呢,例如數學庫,他的庫名是m,他的庫文件名是libm.so,把庫文件名的頭lib和尾.so去掉就是庫名了 值得注意的是,-l參數在搜索指定庫名的時候會從gcc默認的"庫路徑"下去查找 1. /lib 2. /usr/lib 3. /usr/local/lib 因此,如咱們自已要用到一個第三方提供的庫名字叫libtest.so,那麼咱們只要把libtest.so拷貝到這些路徑中(例如/usr/lib) example: 編譯時加上-ltest參數,咱們就能用上libtest.so庫了(咱們還須要與libtest.so配套的頭文件,對咱們須要使用的函數進行聲明,咱們才能在代碼中使用) 9) -L參數 咱們都知道,放在裏的庫直接用-l參數就能連接了,但若是庫文件沒放在這三個目錄裏,而是放在其餘目錄裏,這時咱們只用-l參數的話,連接仍是會出錯,出錯信息大概是:"/usr/bin/ld: cannot find -lxxx",也就是連接
程序ld在那3個目錄裏找不到 -L參數跟着的是庫文件所在的目錄名。再好比咱們把libtest.so放在/aaa/bbb/ccc目錄下,那連接參數就是-L/aaa/bbb/ccc -ltest 10) -include -include用來包含頭文件,但通常狀況下包含頭文件都在源碼裏用#include xxxxxx實現,因此-include參數不多用 11) -I參數 -I參數是用來指定頭文件目錄。/usr/include目錄通常是不用指定的,gcc知道去那裏找(默認路徑),可是若是頭文件不在/usr/include裏咱們就要用-I參數指定了,好比頭文件放在/myinclude目錄裏,那編譯命令行就要加上
"-I/myinclude"參數了,若是不加你會獲得一個"xxxx.h: No such file or directory"的錯誤 -I參數能夠用相對路徑,好比頭文件在當前目錄,能夠用-I.來指定 12) -O參數 這是一個程序優化參數,通常用-O2就是,用來優化程序用的,好比 gcc test.c -O2 -o test 優化獲得的程序比沒優化的要小,執行速度可能也有所提升。關於編譯原理、優化的相關知識又是另外一類龐大的體系了,之後有時間能夠研究一下 13) -shared參數 編譯動態庫時要用到,好比gcc -shared test.c -o libtest.so
4. GCC編譯中遇到的常見錯誤
0x1: undefined reference to 'xxxxx'錯誤
這是連接(Linking)錯誤,不是編譯(Linking)錯誤,已經到了第4步出錯的,也就是說若是隻有這個錯誤,說明你的程序源碼自己沒有問題,是你用編譯器編譯時參數用得不對
reason:
多是因爲沒有指定連接程序要用到得庫,好比你的程序裏用到了一些數學函數,那麼你就要在編譯參數裏指定程序要連接數學庫
solution: 在編譯命令行里加入程序要用到的庫,
0x2: libxxxx.so未創建軟連接報錯
大部分libxxxx.so只是一個連接,好比
libm.so它連接到/lib/libm.so.x /lib/libm.so.6連接到/lib/libm-2.3.2.so
若是沒有這樣的連接,仍是會出錯,由於gcc ;loader只會找libxxxx.so,因此若是你要用到xxxx庫,而只有libxxxx.so.x或者libxxxx-x.x.x.so,作一個連接就能夠了,例如
ln -s libxxxx-x.x.x.so libxxxx.so
不少庫開發包提供了生成連接參數的程序,名字通常叫xxxx-config,通常放在/usr/bin目錄下,好比gtk1.2的連接參數生成程序是gtk-config,執行gtk-config --libs就能獲得如下輸出
"-L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic -lgmodule -lglib -ldl -lXi -lXext -lX11 -lm"
這就是編譯一個gtk1.2程序所需的gtk連接參數
5. Makefile
0x1: Makefile規則格式
makefile關係到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則(rules)來指定
1. 哪些文件須要先編譯 2. 哪些文件須要後編譯 3. 哪些文件須要從新編譯 4. 甚至於進行更復雜的功能操做,由於makefile就像一個Shell腳本同樣,其中也能夠執行操做系統的命令
makefile帶來的好處就是"自動化編譯",一旦寫好,只須要一個make命令,整個工程徹底自動編譯,極大的提升了軟件開發的效率
make是一個linux下的命令工具,是一個解釋makefile中指令的命令工具,通常來講,大多數的IDE都有這個命令,好比:Delphi的make,Visual C++的nmake,Linux下GNU的make
/* 1. 變量聲明 在Makefile中聲明變量 var = value; 在須要使用value的地方直接使用$(var)就能夠了 */ /* 2. Makefile的隱含規則 在使用make編譯.c源文件時,編譯.c源文件規則的命令能夠不用明確給出。這是由於make自己存在一個默認的規則,可以自動完成對.c文件的編譯並生成對應的.o文件。它執行命令"cc -c"來編譯.c源文件。在Makefile中咱們只須要
給出須要重建的目標文件名(一個.o文件),make會自動爲這個.o文件尋找合適的依賴文件(對應的.c文件。對應是指:文件名除後綴外,其他都相同的兩個文件),並且使用正確的命令來重建這個目標文件 */ TARGET... : PREREQUISITES... COMMAND ... ... /* 3. 清理工做 Makefile規則除了完成源代碼編譯以外,也能夠完成其它任務。例如實現清除當前目錄中編譯過程當中產生的臨時文件 1) 經過".PHONY"特殊目標將"clean"目標聲明爲僞目標。避免當磁盤上存在一個名爲"clean"文件時,目標"clean"所在規則的命令沒法執行 2) 在命令行以前使用"-",意思是忽略命令"rm"的執行錯誤 值得注意的是,目標"clean"沒有出如今終極目標"target"依賴關係中(終極目標的直接依賴或者間接依賴),因此咱們執行"make"時,目標"clean"所在的規則將不會被處理。當須要執行此規則,要在make的命令行選項中明確指定這
個目標,即執行"make clean" */ .PHONY:clean clean: -rm target $(var) 1. target: 規則的目標 一般是最後須要生成的文件名或者爲了實現這個目的而必需的中間過程文件名。能夠是: 1) .o文件 2) 最後的可執行程序的文件名 3) 一個make執行的動做的名稱,如目標"clean",咱們稱這樣的目標是"僞目標" 2. prerequisites: 規則的依賴 生成規則目標所須要的文件名列表。一般一個目標依賴於一個或者多個文件。當規則的目標是一個文件,在它的任何一個依賴文件被修改之後,在執行"make"時這個目標文件將會被從新編譯或者從新鏈接 3. command: 規則的命令行 規則所要執行的動做(任意的shell命令或者是可在shell下執行的程序),它限定了make執行這條規則時所須要的動做。一個規則能夠有多個命令行,每一條命令佔一行。 須要注意的是:每個命令行必須以[Tab]字符開始,[Tab]字符告訴make此行是一個命令行。make按照命令完成相應的動做 命令(command)就是在任何一個目標的依賴文件發生變化後重建目標的動做描述。一個目標能夠沒有依賴而只有動做(指定的命令)。好比Makefile中的目標"clean",此目標沒有依賴,只有命令。它所定義的命令用來刪除make過程產生
的中間文件(進行清理工做)
從概念上來講,一個Makefile文件是由一個個的規則組成,每一個規則包括: target、prerequisites、command
make程序根據規則的依賴關係,決定是否執行規則所定義的命令的過程咱們稱之爲"執行規則"
0x2: Makefile運行流程
1. 當在shell提示符下輸入"make"命令之後。make讀取當前目錄下的Makefile文件,並將Makefile文件中的第一個目標做爲其執行的"終極目標",開始處理第一個規則,默認的狀況下,make執行的是Makefile中的第一個規則,此
規則的第一個目標稱之爲"最終目的"或者"終極目標"。結合Makefile的工做原理就會很好理解爲何第一個規則會是"最終規則",由於Makefile的規則是遞歸依賴的,最終規則遞歸地依賴於下面的其餘規則 2. make在執行這個規則所定義的命令以前,首先處理目標"終極目標"的全部的依賴目標,多是: 1) 僞目標 2) .so文件 對.o文件爲目標的規則處理有下列三種狀況: 2.1) 目標.o文件不存在,使用其描述規則建立它 2.2) 目標.o文件存在,目標.o文件所依賴的.c源文件、.h文件中的任何一個比目標.o文件"更新"(在上一次make以後被修改),則根據規則從新編譯生成它 2.3) 目標.o文件存在,目標.o文件比它的任何一個依賴文件的(c源文件、.h文件)"更新"(它的依賴文件在上一次make以後沒有被修改),則什麼也不作 3) 可執行文件 對於Makefile的工做流程的理解,咱們必定要牢記遞歸的概念,即make會將終極目標的依賴目標從右到左一次回退所有執行完,出如今"終極目標"的依賴列表中的目標必定要所有執行完以後,終極目標才能被最終完成 3. 若是在處理終極目標的依賴目標的時候,又出現了新的依賴目標,則繼續遞歸地進行處理(相似函數棧調用原理) 4. . 在Makefile中一個規則的目標若是不是"終極目標"所依賴的(或者"終極目標"的依賴文件所依賴的),那麼這個規則將不會被執行,除非明確指定執行這個規則(能夠經過make的命令行指定重建目標,那麼這個目標所在的規則就會
被執行,例如"make clean") 5. 更新(或者建立)終極目標的過程當中,若是任何一個規則執行出現錯誤make就當即報錯並退出。 整個過程make只是負責執行規則,而對具體規則所描述的依賴關係的正確性、規則所定義的命令的正確性不作任何判斷。 就是說,一個規則的依賴關係是否正確、描述重建目標的規則命令行是否正確,make不作任何錯誤檢查。 所以,須要正確的編譯一個工程。須要在提供給make程序的Makefile中來保證其依賴關係的正確性、和執行命令的正確性。
0x3: Example
edit : main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o /*註釋:若是後面這些.o文件比edit可執行文件新,那麼纔會去執行下面這句命令*/ cc -o edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h buffer.h cc -c display.c insert.o : insert.c defs.h buffer.h cc -c insert.c search.o : search.c defs.h buffer.h cc -c search.c files.o : files.c defs.h buffer.h command.h cc -c files.c utils.o : utils.c defs.h cc -c utils.c clean : rm edit main.o kbd.o command.o display.o \ insert.o search.o files.o utils.o
Relevant Link:
http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:MakeFile%E4%BB%8B%E7%BB%8D#.E4.B8.80.E4.B8.AA.E7.A4.BA.E4.BE.8B
6. Makefile Example
hellomake.c | hellofunc.c | hellomake.h |
---|---|---|
#include <hellomake.h> int main() { // call a function in another file myPrintHelloMake(); return(0); } |
#include <stdio.h> #include <hellomake.h> void myPrintHelloMake(void) { printf("Hello makefiles!\n"); return; } |
/* example include file */ void myPrintHelloMake(void); |
0x1: Makefile 1
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c -I.
0x2: Makefile 2
CC=gcc CFLAGS=-I. hellomake: hellomake.o hellofunc.o $(CC) -o hellomake hellomake.o hellofunc.o -I.
0x3: Makefile 3
CC=gcc CFLAGS=-I. DEPS = hellomake.h %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hellomake: hellomake.o hellofunc.o gcc -o hellomake hellomake.o hellofunc.o -I.
0x4: Makefile 4
CC=gcc CFLAGS=-I. DEPS = hellomake.h OBJ = hellomake.o hellofunc.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hellomake: $(OBJ) gcc -o $@ $^ $(CFLAGS)
0x5: Makefile 5
IDIR =../include CC=gcc CFLAGS=-I$(IDIR) ODIR=obj LDIR =../lib LIBS=-lm _DEPS = hellomake.h DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) _OBJ = hellomake.o hellofunc.o OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) $(ODIR)/%.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) hellomake: $(OBJ) gcc -o $@ $^ $(CFLAGS) $(LIBS) .PHONY: clean clean: rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
Relevant Link:
http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/
Copyright (c) 2014 LittleHann All rights reserved