一、gcc编译过程
学习Makefile基本语法之前,我们有必要复习一下gcc的基本编译,常见的gcc选项
如下:
选项 | 功能 |
---|---|
-v | 查看gcc编译器的版本,显示gcc执行时的详细过程 |
-o | 指定输出文件名为file,这个名称不能跟源文件名同名 |
-E | 只预处理,不会编译、汇编、链接t |
-S | 只编译,不会汇编、链接 |
-c | 编译和汇编,不会链接 |
一个c/c++文件要经过预处理、编译、汇编和链接
才能变成可执行文件。
1)预处理
C/C++源文件中,以#开头的命令被称为预处理命令,如包含命令#include、宏定义命令#define、条件编译命令#if、#ifdef等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个.i文件中等待进一步处理。
2)编译
编译就是把C/C++代码(比如上述的.i文件)翻译成汇编代码。
3)汇编
汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件)。反汇编是指将机器代码转换为汇编代码,这在调试程序时常常用到。常使用的指令为objdump -d *.o(目标文件)对目标文件进行反汇编。
4)链接
链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件。
整个过程为:hello.c(预处理)->hello.i(编译)->hello.s(汇编)->hello.o(链接)->hello
,每个过程具体命令如下:
gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
gcc -o hello hello.o
//上述总体可简化为
gcc -c -o hello.o hello.c
gcc -o hello hello.o
另外需要补充2点,也是比较常用的就是静态库
和动态库
的生成。
静态库
一般使用cr命令
生成,常用的参数如下:
选项 | 功能 |
---|---|
c | 生成新的库文件 |
r | 替换现有的文件或插入新的文件 |
t | 显示归档中的目录表 |
举个例子,我们需要将a.c b.c c.c 这3个源文件编译成一个api.a的静态库:
ar -cr -o api.a a.c b.c c.c
依旧使用上面的例子进行动态库
的生成:
gcc -shared -o api.so -fPIC a.c b.c c.c
其中api.so就是我们的动态库,动态库一般是.so文件
,静态库后缀是.a
文件,另外-fPIC
就是告诉编译器去创建一个与地址无关的程序,这样才能够让动态库可以被多个应用程序共享。
二、Makefile的前世今生
-
先解答一下
Makefile是用来干什么的
:Makefile 文件描述了整个工程的编译、连接等规则。其中包括:工程中的哪些源文件需要编译以及如何编译、需要创建哪些库文件以及如何创建这些库文件、如何最后产生我们想要的可执行文件。 -
如何使用Makefile
:一般有Makefile文件后我们直接使用make命令
就可以完成编译,这里需要知道在 Linux(unix )环境下使用GNU 的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个make命令就可以完成编译、链接。
三、Makfile初级语法
基本语法规则
:
目标(target):依赖(prerequisites)
[Tab]命令(command)
1)target:需要生成的目标文件
2)prerequisites:生成该target所依赖的一些文件
3)command:生成该目标需要执行的命令
举个例子:
main:main.c
gcc main.c -o main
注意:Makefile文件里的命令必须要使用Tab
,不能使用空格
目标文件生成规则
:
1)检查规则中的依赖文件是否存在。
2)若依赖文件不存在,则寻找是否有规则用来生成该依赖文件。
目标文件更新规则
:
1)检查目标的所有依赖,任何一个依赖有更新时,就要重新生成目标。
2)目标文件比依赖文件更新时间晚,则需要更新。
举个例子:目标文件output依赖main.o add.o sub.o,而这3个obj文件又依赖各自的.c文件,则对于这个依赖关系我们可以使用Makefile文件表述为:
output: main.o add.o sub.o
gcc -o output main.o add.o sub.o
main.o: main.c
gcc -c main.c
add.o: add.c
gcc -c add.c
sub.o: sub.c
gcc -c sub.c
clean:
rm *.o output
通配符
:上面的示例中依赖文件只有3个,而且生成依赖文件的源C文件也只有3个,当工程比较复杂的时候.c .o文件将会变得非常多,这时需要使用通配符,来解决这些问题。
%.o:表示所用的.o文件
%.c:表示所有的.c文件
$@:表示目标
$<:表示第1个依赖文件
$^:表示所有依赖文件
有了通配符,上面的示例可以简化为:
output: main.o add.o sub.o
gcc -o output $^
%.o: %.c
gcc -c -o $@ $<
clean:
rm *.o output
四、Makefile其他常见语法
1)简单变量
A := xxx # A的值即刻确定,在定义时即确定
2)延时变量
B = xxx # B的值使用到时才确定
想使用变量的时候使用“$”来引用,如果不想看到命令时可以在命令的前面加上"@"符号,常用的变量的定义如下:
:= # 即时变量
= # 延时变量
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
+= # 附加, 它是即时变量还是延时变量取决于前面的定义
?=: #如果这个变量在前面已经被定义了,这句话就不会起效果
3)系统自带变量
系统自定义了一些变量,通常都是大写,比如CC,PWD,CLFAG等等,有些有默认值,有些没有,如下:
1)CPPFLAGS:预处理器需要的选项,如:-l
2)CFLAGS:编译的时候使用的参数,-Wall -g -c
3)LDFLAGS:链接库使用的选项,-L -l
4)Wildcard函数
:
$(wildcard pattern) # pattern定义了文件名的格式, wildcard取出其中存在的文件
例如目录下面有三个文件:a.c b.c c.c,则如下写法file的值为a.c b.c c.c:
files = $(wildcard *.c)
5)patsubst函数
$(patsubst pattern,replacement,$(var))
patsubst函数是从var变量里面取出每一个值,如果这个符合pattern格式,把它替换成replacement格式。
例如如下写法,dep_files 的值为a.d b.d c.d d.d e.d abc
files2 = a.c b.c c.c d.c e.c abc
dep_files = $(patsubst %.c,%.d,$(files2))
all:
@echo dep_files = $(dep_files)
Makefile模板
###指定编译器,当前你也可以不指定使用系统默认的
CC = $(ARM_GCC)gcc
###添加调试信息,生成告警信息
CFLAGS = -g -Wall
###指定链接库
LD_LIBRARY = -lxxx
###添加文件搜索目录,一般.h文件对应的目录
INCLUDE = -I./aaa -I./bbb -I./ccc -I./ddd
###添加源码文件
SOURCES = $(wildcard ./aaa/*.c ./bbb/*.c ./ccc/*.c ./ddd/*.c ./*.c)
###指定目标文件
TARGET= output
OBJECTS = $(patsubst %.c,%.o,$(SOURCES))
$(TARGET) : $(OBJECTS)
$(CC) $(CFLAGS) $^ -o $@
rm -rf ./src/*.o
$(OBJECTS) : %.o : %.c
$(CC) -c $(CFLAGS) $< -o $@
.PHONY: clean
clean:
rm -rf ./src/*.o $(TARGET)