1、寫在前面
對于Windows下開發(fā),很多IDE都集成了編譯器,如Visual Studio,提供了“一鍵編譯”,編碼完成后只需一個操作即可完成編譯、鏈接、生成目標文件。
Linux開發(fā)與Windows不同,Linux下一般用的的gcc/g++編譯器,如果是開發(fā)ARM下的Linux程序,還需用到arm-linux-gcc/arm-linux-g++交叉編譯器。
Linux下也可以實現(xiàn)“一鍵編譯”功能,此時需要一個編譯腳本“Makefile”,Makefile可以手動編寫,也可以借助自動化構建工具(如scons、CMake)生成。手動編寫Makefile是Linux和Windows程序員的區(qū)別之一,一般地一個通用的Makefile能夠適合大部分Linux項目程序。
2、3個Makefile模板
2.1 編譯可執(zhí)行文件Makefile
?
VERSION??=1.00 CC???=gcc DEBUG???=-DUSE_DEBUG CFLAGS??=-Wall SOURCES???=$(wildcard?./source/*.c) INCLUDES???=-I./include LIB_NAMES??=-lfun_a?-lfun_so LIB_PATH??=-L./lib OBJ???=$(patsubst?%.c,?%.o,?$(SOURCES)) TARGET??=app #links $(TARGET):$(OBJ) ?@mkdir?-p?output ?$(CC)?$(OBJ)?$(LIB_PATH)?$(LIB_NAMES)?-o?output/$(TARGET)$(VERSION) ?@rm?-rf?$(OBJ) ? #compile %.o:?%.c ?$(CC)?$(INCLUDES)?$(DEBUG)?-c?$(CFLAGS)?$-o?$@ .PHONY:clean clean: ?@echo?"Remove?linked?and?compiled?files......" ?rm?-rf?$(OBJ)?$(TARGET)?output?
?
【要點說明】
【1】程序版本
開發(fā)調試過程可能產生多個程序版本,可以在目標文件后(前)增加版本號標識。
?
VERSION?=?1.00 $(CC)?$(OBJ)?$(LIB_PATH)?$(LIB_NAMES)?-o?output/$(TARGET)$(VERSION)
?
【2】編譯器選擇
Linux下為gcc/g++;arm下為arm-linux-gcc;不同CPU廠商提供的定制交叉編譯器名稱可能不同,如Hisilicon“arm-hisiv300-linux-gcc”。
?
CC?=?gcc
?
【3】宏定義
開發(fā)過程,特殊代碼一般增加宏條件來選擇是否編譯,如調試打印輸出代碼。-D是標識,后面接著的是“宏”。
?
DEBUG?=-DUSE_DEBUG
?
【4】編譯選項
可以指定編譯條件,如顯示警告(-Wall),優(yōu)化等級(-O)。
?
CFLAGS?=-Wall?-O
?
【5】源文件
指定源文件目的路徑,利用“wildcard”獲取路徑下所有依賴源文件。
?
SOURCES?=$(wildcard?./source/*.c)
?
【6】頭文件
包含依賴的頭文件,包括源碼文件和庫文件的頭文件。
?
INCLUDES?=-I./include
?
【7】庫文件名稱
指定庫文件名稱,庫文件有固定格式,靜態(tài)庫為libxxx.a;動態(tài)庫為libxxx.so,指定庫文件名稱只需寫“xxx”部分,
?
LIB_NAMES?=-lfun_a?-lfun_so
?
【8】庫文件路徑
指定依賴庫文件的存放路徑。注意如果引用的是動態(tài)庫,動態(tài)庫也許拷貝到“/lib”或者“/usr/lib”目錄下,執(zhí)行應用程序時,系統(tǒng)默認在該文件下索引動態(tài)庫。
?
LIB_PATH?=-L./lib
?
【9】目標文件
調用“patsubst”將源文件(.c)編譯為目標文件(.o)。
?
OBJ?=$(patsubst?%.c,?%.o,?$(SOURCES))
?
【10】執(zhí)行文件
執(zhí)行文件名稱
?
TARGET?=app
?
【11】編譯
?
%.o:?%.c ?$(CC)?$(INCLUDES)?$(DEBUG)?$(CFLAGS)?$-o?$@
?
【12】鏈接
可創(chuàng)建一個“output”文件夾存放目標執(zhí)行文件。鏈接完輸出目標執(zhí)行文件,可以刪除編譯產生的臨時文件(.o)。
?
$(TARGET):$(OBJ) ?@mkdir?-p?output ?$(CC)?$(OBJ)?$(LIB_PATH)?$(LIB_NAMES)?-o?output/$(TARGET).$(VERSION) ?@rm?-rf?$(OBJ)
?
【13】清除編譯信息
執(zhí)行“make clean”清除編譯產生的臨時文件。
?
.PHONY:clean clean: ?@echo?"Remove?linked?and?compiled?files......" ?rm?-rf?$(OBJ)?$(TARGET)?output?
?
2.2 編譯靜態(tài)庫Makefile
?
VERSION?????= CC??????????=gcc DEBUG???= CFLAGS??=-Wall AR???=ar ARFLAGS?????=rv SOURCES???=$(wildcard?*.c) INCLUDES????=-I. LIB_NAMES???= LIB_PATH??= OBJ?????????=$(patsubst?%.c,?%.o,?$(SOURCES)) TARGET??????=libfun_a #link $(TARGET):$(OBJ) ?@mkdir?-p?output ?$(AR)?$(ARFLAGS)?output/$(TARGET)$(VERSION).a?$(OBJ) ?@rm?-rf?$(OBJ) #compile %.o:?%.c ?$(CC)?$(INCLUDES)?$(DEBUG)?-c?$(CFLAGS)?$-o?$@ ?? .PHONY:clean clean: ?@echo?"Remove?linked?and?compiled?files......" ?rm?-rf?$(OBJ)?$(TARGET)?output?
?
【要點說明】
基本格式與“編譯可執(zhí)行Makefile”一致,不同點包括以下。
【1】使用到“ar”命令將目標文件(.o)鏈接成靜態(tài)庫文件(.a)。靜態(tài)庫文件固定命名格式為:libxxx.a。
2.3 編譯動態(tài)庫Makefile
?
VERSION???= CC????????=gcc DEBUG?????= CFLAGS????=-fPIC?-shared? LFLAGS???=-fPIC?-shared? SOURCES???=$(wildcard?*.c) INCLUDES??=-I. LIB_NAMES?= LIB_PATH??= OBJ???????=$(patsubst?%.c,?%.o,?$(SOURCES)) TARGET????=libfun_so #link $(TARGET):$(OBJ) ?@mkdir?-p?output ?$(CC)?$(OBJ)?$(LIB_PATH)?$(LIB_NAMES)?$(LFLAGS)?-o?output/$(TARGET)$(VERSION).so ?@rm?-rf?$(OBJ) ? #compile %.o:?%.c ?$(CC)?$(INCLUDES)?$(DEBUG)?-c?$(CFLAGS)?$-o?$@ .PHONY:clean clean: ?@echo?"Remove?linked?and?compiled?files......" ?rm?-rf?$(OBJ)?$(TARGET)?output?
?
【要點說明】
基本格式與“編譯可執(zhí)行Makefile”一致,不同點包括以下。
【1】編譯選項和鏈接選項增加“-fPIC -shared ”選項。動態(tài)庫文件固定命名格式為libxxx.so。
3、Demo
3.1 編譯應用程序
編寫測試例程,文件存放目錄結構如下,頭文件存放在“include”目錄,庫文件存放在“l(fā)ib”目錄,源文件存放在“source”目錄,Makefile在當前目錄下。
源碼1:
?
/*頭文件*/ #ifndef?_FUN0_H_ #define?_FUN0_H_ #endif extern?void?fun0_printf(void); extern?void?fun1_printf(void); /*源文件*/ #include?#include?"fun0.h" void?fun0_printf(void) { ????printf("Call?'fun0'.? "); }
?
源碼2:
?
/*頭文件*/ #ifndef?_FUN1_H_ #define?_FUN1_H_ #endif extern?void?fun1_printf(void); /*源文件*/ #include?#include?"fun1.h" void?fun1_printf(void) { ????printf("Call?'fun1'. "); }
?
主函數(shù)源碼:
?
/*源文件*/ #include?#include?"fun0.h" #include?"fun1.h" #include?"fun_lib_a.h" #include?"fun_lib_so.h" int?main(void) { ????#ifdef?USE_DEBUG ????????printf("Debug?Application?startup. "); ????#endif ???? ????????fun0_printf(); ????????fun1_printf(); ????????fun_lib_a_printf(); ????????fun_lib_so_printf(); ????????return?0; }
?
庫文件,“./lib”目錄下存放兩個庫文件,一個靜態(tài)庫libfun_a.a,一個動態(tài)庫libfun_so.so。
Makefile文件即為“2.1節(jié)”的Makefile模板。
測試運行:
【如果執(zhí)行文件提示無“l(fā)ibfun_so.so”,則需拷貝“l(fā)ibfun_so.so”到根目錄下的“/lib”或者“/usr/lib”目錄下,因為系統(tǒng)執(zhí)行程序,默認從該路徑引腳動態(tài)庫】
3. 2 生成靜態(tài)庫
編寫測試例程,生產的庫文件即為“3.1節(jié)”調用的庫文件(libfun_a.a)。文件存放目錄結構如下:
源文件:
?
/*頭文件*/ #ifndef?_FUN_LIB_A_H_ #define?_FUN_LIB_A_H_ #endif extern?void?fun_lib_a_printf(void); /*源文件*/ #include?#include?"fun_lib_a.h" void?fun_lib_a_printf(void) { ????printf("Call?'fun_lib_a'. "); }
?
Makefile文件即為“2.2節(jié)”的Makefile模板。
編譯生成靜態(tài)庫:
3. 3 生成動態(tài)庫
編寫測試例程,生產的庫文件即為“3.1節(jié)”調用的庫文件(libfun_so.so)。文件存放目錄結構如下:
源文件:
?
/*頭文件*/ #ifndef?_FUN_LIB_SO_H_ #define?_FUN_LIB_SO_H_ #endif extern?void?fun_lib_so_printf(void); /*頭文件*/ #include?#include?"fun_lib_so.h" void?fun_lib_so_printf(void) { ????printf("Call?'fun_lib_so'. "); }
?
編譯生成動態(tài)庫:
評論
查看更多