【前言】在我所接觸到的Linux嵌入式開發中,大多使用的是C語言,采用makefile文件對源文件進行編譯后生成可執行文件。本文即從個人經歷上介紹小型的C項目如何編寫makefile文檔。
一、gcc命令
從目的上看,gcc命令和makefile的功能是一樣,即是把源文件編譯后生成可執行文件或.o二進制文件。gcc命令中有許多的額外的參數,本文僅介紹以下幾種最簡單和常用的方法:
有helloworld.c文件如下:
#include <stdio.h> int main() { printf("helloworld!\n"); return 0; }
使用如下gcc命令,將在同一文件目錄下生成a.out可執行文件,在terminal中運行該文件即可在屏幕中打印出helloworld!:
$ gcc helloworld.c
使用如下gcc命令,將在同一文件目錄下生成helloworld.o二進制文件,它可以被用來連接文件:
$ gcc -c helloworld.c
如果要生成特定名稱的可執行文件,可以使用如下gcc命令,它將生成helloworld可執行文件,區別於a.out可執行文件,在termianl執行它可以打印出helloworld!:
$ gcc helloworld.c -o helloworld
以上三種gcc命令,主要區別是參數不同,-c和-o的區別在於,-c只編譯不連接,故只生成.o的二進制文件,-o即指定名稱輸出可執行文件。對於單個或者幾個的源文件,使用gcc命令即可,但是一旦文件具有依賴關系,gcc命令的執行順序是需要規定的,否則無法順序編譯項目,這是gcc命令的缺點,而makefile區別於gcc命令的地方正在於它能規定好文件間的依賴關系,以及加入依賴的庫文件和頭文件。
如果要引用其它目錄下的頭文件,其gcc命令如下,采用-I的參數:
$ gcc -I /ycl/include/ helloworld.c -o helloworld
如果要引用其它的目錄下的庫文件,如libmlib.so,其gcc命令如下,采用-L的參數:
$ gcc -L /ycl/lib –lmlib helloworld.c -o helloworld
二、makefile簡要語法
makefile的用處是告訴編輯器,本項目以怎樣的規則進行編譯和鏈接,運行make命令需要makfile文件作為支撐。
要弄明白makefile,其關鍵是理解它的核心語法(源自博文http://blog.csdn.net/liang13664759/article/details/1771246 ):
target ... : prerequisites ...
command
...
...
target也就是一個目標文件,可以是Object File,也可以是執行文件。還可以是一個標簽(Label),對於標簽這種特性,在后續的“偽目標”章節中會有敘述。
prerequisites就是,要生成那個target所需要的文件或是目標。
command也就是make需要執行的命令。(任意的Shell命令)
這是一個文件的依賴關系,也就是說,target這一個或多個的目標文件依賴於prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容。
示例代碼如下:
CXX = g++ CXXFLAGS = -O2 -g -Wall -fmessage-length=0 CFLAGS = `pkg-config --cflags lcm` LDFLAGS=`pkg-config --libs lcm`\ -lpthread OBJS = DSRC_module.o \ LCMHandler.o \ UDPHandler.o TARGET = DSRC_module $(TARGET): $(OBJS) $(CXX) -o $@ $^ $(LDFLAGS)
DSRC_module.o: DSRC_module.cpp
$(CXX) $(CFLAGS) -I. -o $@ -c $<
LCMHandler.o: LCMHandler.cpp
$(CXX) $(CFLAGS) -I. -o $@ -c $<
UDPHandler.o: UDPHandler.cpp
$(CXX) $(CFLAGS) -I. -o $@ -c $<
all: $(TARGET)
clean:
rm -f $(OBJS) $(TARGET)
示例代碼中出現了變量,makefile中變量采用$(變量)的方法使用,如$(TARGET)即表示DSRC_module。該makefile的目的是生成名為DSRC_ module的可執行文件,即$(TARGET),它的依賴文件是$(OBJS)所代表的DSRC_module.o、LCMHandler.o、UDPHandler.o三個文件。由於這三個文件都未生成,故針對每一個文件都需要進行編譯生成,所以有三個.o文件為目標的makefile語句。
文中特殊字符說明如下:$@--目標文件,$^--所有的依賴文件,$<--第一個依賴文件。比如在如下的代碼中,$@代表DSRC_module.cpp,$<代表第一個依賴文件,也就是DSRC_module.cpp。
DSRC_module.o: DSRC_module.cpp $(CXX) $(CFLAGS) -I. -o $@ -c $<
三、makefile編寫
使用裝用CDT插件的eclipse可以直接生成helloworld項目的makefile項目,makefile內容如下:
CXXFLAGS = -O2 -g -Wall -fmessage-length=0 OBJS = example.o LIBS = TARGET = example $(TARGET): $(OBJS) $(CXX) -o $(TARGET) $(OBJS) $(LIBS) all: $(TARGET) clean: rm -f $(OBJS) $(TARGET)
其中CXXFLAGS是編譯參數變量,CXX是makefile內置變量,CXX默認表示g++(C++編譯器)。這個makefile的有兩個功能:①編譯代碼,將example.c編譯成example.o並鏈接成example可執行文件;②清除項目,當項目需要重新編譯或整理時,使用make clean命令即可清除生的OBJS和TARGET變量中的名稱。
在上述代碼中,之所以依賴文件沒有.c文件,這是因為此處使用了makefile的自動推導功能,只要依賴關系中有.o文件,它會自動添加同名稱的.c文件作為依賴文件,並由$(CXX) -o example.o可以自動推導出$(CXX) -o example.c。
$(TARGET): $(OBJS)
$(CXX) -o $(TARGET) $(OBJS) $(LIBS)
基於上述的makefile文件,如果此時需要添加新的.c和.h文件,則改變OBJS的內容即可,比如填加test.c文件,則代碼如下(注意test.o前的不能加空格,需用tab鍵):
CXXFLAGS = -O2 -g -Wall -fmessage-length=0 OBJS = example.o\ test.o LIBS = TARGET = example $(TARGET): $(OBJS) $(CXX) -o $(TARGET) $(OBJS) $(LIBS) all: $(TARGET) clean: rm -f $(OBJS) $(TARGET)
如果需要添加頭文件,那么注意盡量不要使用makefile的自動推導功能,否則可能找不到頭文件,則只需添加和修改如下代碼:
CXXFLAGS = -O2 -g -Wall -fmessage-length=0
OBJS = example.cpp\ test.cpp
HEADER_DIR = -I/home/duser/dot3
LIBS = TARGET = example $(TARGET): $(OBJS) $(CXX) -o $(TARGET) $(OBJS) $(LIBS) all: $(TARGET) clean: rm -f $(TARGET)
$(TARGET): $(OBJS) $(CXX) $(HEADER_DIR) $(OBJS) -o $(TARGET) $(LIBS)
如果需要添加庫文件,需要兩個變量,LDFLAGS---相當於庫所在路徑,LIBS---要鏈接的庫文件。則只需添加和修改如下代碼:
LDFLAGS += -L/lib LIB += -ltest $(TARGET): $(OBJS)
$(CXX) $(HEADER_DIR) $(OBJS) -o $(TARGET) $(LIBS) $(LDFLGS)$(LIBS)