本文章是参考韦东山第一期ARM裸机加强Makefile相关内容。
参考链接:百问科技wiki教程Makefile
一、gcc编译过程详解
1、gcc的使用方法:gcc [选项] 文件名
2、gcc常用选项
一个c/c++文件要经过预处理、编译、汇编和链接才能变成可执行文件。
(1)预处理
C/C++源文件中,以#开头的命令被称为预处理命令,如包含命令#include、宏定义命令#define、条件编译命令#if、#ifdef等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个.i文件中等待进一步处理。
(2)编译
编译就是把C/C++代码(比如上述的.i文件)翻译成汇编代码。
(3)汇编
汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件)。反汇编是指将机器代码转换为汇编代码,这在调试程序时常常用到。
(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文件默认进行预处理操作,使用-c再来指明了编译、汇编,从而得到.o文件, 再将.o文件进行链接,得到可执行应用程序。简化如下:
gcc -c -o hello.o hello.c
gcc -o hello hello.o
3、深入讲解链接过程
前面编译出来的可执行文件比源代码大了很多,这是什么原因呢?
我们从链接过程来分析,链接将汇编生成的OBJ文件、系统库的OBJ文件、库文件链接起来,crt1.o、crti.o、crtbegin.o、crtend.o、crtn.o这些都是gcc加入的系统标准启动文件,它们的加入使最后出来的可执行文件相原来大了很多。
-lc:链接libc库文件,其中libc库文件中就实现了printf等函数。
gcc -v -nostdlib -o hello hello.o
会提示因为没有链接系统标准启动文件和标准库文件,而链接失败。
这个-nostdlib选项常用于裸机bootloader、linux内核等程序,因为它们不需要启动文件、标准库文件。
一般应用程序才需要系统标准启动文件和标准库文件。 裸机/bootloader、linux内核等程序不需要启动文件、标准库文件。
动态链接使用动态链接库进行链接,生成的程序在执行的时候需要加载所需的动态库才能运行。
动态链接生成的程序体积较小,但是必须依赖所需的动态库,否则无法执行。
gcc -c -o hello.o hello.c
gcc -o hello_shared hello.o
静态链接使用静态库进行链接,生成的程序包含程序运行所需要的全部库,可以直接运行,
不过静态链接生成的程序体积较大。
gcc -c -o hello.o hello.c
gcc -static -o hello_static hello.o
二、Makefile的基础入门知识
1、Makefile的引入及其规则
我们写一个程序:
a.c:
#include "stdio.h"
void printf_hello_world(void)
{
printf("hello world\r\n");
}
main.c:
#include "stdio.h"
extern void printf_hello_world(void);
int main(void)
{
printf_hello_world();
return 0;
}
编译:
gcc -o test a.c main.c
运行:
./test
结果:
hello world
gcc -o test a.c mian.c
这条命令虽然简单,但是它完成的功能不简单。 我们来看看它做了哪些事情。
我们知道.c程序 --> 得到可执行程序
它们之间要经过四个步骤:
(1).预处理
(2).编译
(3).汇编
(4).链接
我们经常把前三个步骤统称为编译了。我们具体分析:gcc -o test a.c mian.c这条命令 它们要经过下面几个步骤:
1).对于a.c执行:预处理 编译 汇编 的过程,a.c -->xxx.s -->xxx.o 文件。
2).对于mian.c执行:预处理 编译 汇编 的过程,mian.c -->yyy.s -->yyy.o 文件。
3).最后:xxx.o和yyy.o链接在一起得到一个test应用程序。
缺点:对所有的文件都会再处理一次,即使main.c没有经过修改,main.c也会重新编译一次, 当文件比较少时,这没有没有什么问题,当文件非常多的时候,就会带来非常多的效率问题。
将编译和连接分开执行;
编译:
gcc -o a.o a.c
gcc -o main.o main.c
链接:
gcc -o test a.o main.o
我们想办法,当我们修改a.c之后,a.c会重现编译然后再把它们链接在一起就可以了。,mian.c 就不需要重新编译,那么如何确定哪个有修改哪没修改呢?
比较时间:比较a.o和a.c的时间,如果a.c的时间比a.o的时间更加新的话,就表明a.c被修改了,同理main.o和main.c也会进行同样的比较。比较test和a.o, main.o的时间,如果a.o或者main.o的时间比test更加新的话,就表明应该重新生成test。Makefile 就是这样做的。
于是我们得出简单的Makefile规则:
目标 : 依赖1 依赖2 ...
[TAB]命令
按照此规则,我们将相应的编译链接等命令写在一个命名为Makefile的文件:
test:a.o main.o
gcc -o test a.o main.o
a.o : a.c #a.o依赖于a.c,当a.c更加新的话,执行下面的命令来生成a.o
gcc -c -o a.o a.c
main.o : main.c #main.o依赖于main.c,当main.c更加新的话,执行下面的命令,来生成main.o
gcc -c -o main.o main.c
然后执行make
命令就能够生成test目标文件。
2、Makefile的语法
通配符
%.o:表示所用的.o文件
%.c:表示所有的.c文件
$@:表示目标
$<:表示第1个依赖文件
$^:表示所有依赖文件
根据这些通配符,我们可以将上面的Makefile修改为:
test: a.o main.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
下面我们增加一个b.c文件:
b.c:
#include "stdio.h"
void printf_hello_makefile(void)
{
printf("hello makefile\r\n");
}
修改Makefile:
test: a.o b.o main.o
gcc -o test $^
%.o : %.c
gcc -c -o $@ $<
执行:
make
结果:
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -c -o main.o main.c
gcc -o test a.o b.o main.o
假想目标: .PHONY
我们想清除文件,我们在Makefile的结尾添加如下代码就可以了:
clean:
rm *.o test
(1).执行make:生成第一个可执行文件。
(2).执行make clean: 清除所有文件,即执行:rm *.o test。
make后面可以带上目标名,也可以不带,如果不带目标名的话它就想生成第一个规则里面的第一个目标。
使用Makefile
执行:make [目标]
也可以不跟目标名,若无目标默认第一个目标。我们直接执行make的时候,会在makefile里面找到第一个目标然后执行下面的指令生成第一个目标。当我们执行make clean的时候,就会在Makefile里面找到clean这个目标,然后执行里面的命令,这个写法有些问题,原因是我们的目录里面没有clean这个文件,这个规则执行的条件成立,他就会执行下面的命令来删除文件。
如果:该目录下面有名为clean文件怎么办呢?
我们在该目录下创建一个名为“clean”的文件,然后重新执行:make然后make clean,结果(会有下面的提示:):
make: `clean' is up to date.
它根本没有执行我们的删除操作,这是为什么呢?
我们之前说,一个规则能过执行的条件:
(1).目标文件不存在
(2).依赖文件比目标新。
现在我们的目录里面有名为“clean”的文件,目标文件是有的,并且没有依赖文件,没有办法判断依赖文件的时间。这种写法会导致:有同名的"clean"文件时,就没有办法执行make clean操作。解决办法:我们需要把目标定义为假想目标,用关键字PHONY。
.PHONY: clean //把clean定义为假想目标。他就不会判断名为“clean”的文件是否存在
然后在Makfile结尾添加.PHONY: clean语句,重新执行:make clean,就会执行删除操作。
变量
在makefile中有两种变量:
1)简单变量(即时变量):
A := xxx # A的值即刻确定,在定义时即确定
对于即时变量使用“:=”表示,它的值在定义的时候已经被确定了
2)延时变量
B = xxx # B的值使用到时才确定
对于延时变量使用“=”表示。它只有在使用到的时候才确定,在定义/等于时并没有 确定下来。
想使用变量的时候使用“$”来引用,如果不想看到命令是,可以在命令的前面加上"@"符号,就不会显示命令本身。当我们执行make命令的时候,make这个指令本身,会把整个Makefile读进去,进行全部分析,然后解析里面的变量。常用的变量的定义如下:
:= # 即时变量
= # 延时变量
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句
+= # 附加, 它是即时变量还是延时变量取决于前面的定义
?=: #如果这个变量在前面已经被定义了,这句话就不会起效果
实例:
A := $(C)
B = $(C)
C = abc
#D = 100ask
D? = weidongshan
all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)
C += 123
执行:
make
结果:
A =
B = abc 123
D = weidongshan
分析:
(1) A := $©: A为即时变量,在定义时即确定,由于刚开始C的值为空,所以A的值也为空。
(2)B = $©: B为延时变量,只有使用到时它的值才确定,当执行make时,会解析Makefile里面的所用变量,所以先解析C = abc,然后解析C += 123,此时,C = abc 123,当执行:@echo B = $(B) B的值为 abc 123。
(3)D ?= weidongshan: D变量在前面没有定义,所以D的值为weidongshan,如果在前面添加D = 100ask,最后D的值为100ask。
我们还可以通过命令行存入变量的值 例如:
执行:
make D=123456
里面的D ?= weidongshan
这句话就不起作用了。
结果:
A =
B = abc 123
D = 123456
三、Makefile函数
makefile 里面可以包含很多函数,这些函数都是make本身实现的,下面我们来几个常用的函数。
引用一个函数用“$”。
函数foreach
函数foreach语法如下:
$(foreach var,list,text)
前两个参数,‘var’和‘list’,将首先扩展,注意最后一个参数‘text’此时不扩展;接着,对每一个‘list’扩展产生的字,将用来为‘var’扩展后命名的变量赋值;然后‘text’引用该变量扩展;因此它每次扩展都不相同。结果是由空格隔开的‘text’ 在‘list’中多次扩展的字组成的新的‘list’。‘text’多次扩展的字串联起来,字与字之间由空格隔开,如此就产生了函数foreach的返回值。
实例:
A = a b c
B = $(foreach f, &(A), $(f).o)
all:
@echo B = $(B)
结果:
B = a.o b.o c.o
函数filter/filter-out
函数filter/filter-out语法如下:
$(filter pattern...,text) # 在text中取出符合patten格式的值
$(filter-out pattern...,text) # 在text中取出不符合patten格式的值
实例:
C = a b c d/
D = $(filter %/, $(C))
E = $(filter-out %/, $(C))
all:
@echo D = $(D)
@echo E = $(E)
结果:
D = d/
E = a b c
Wildcard函数
函数Wildcard语法如下:
$(wildcard pattern) # pattern定义了文件名的格式, wildcard取出其中存在的文件
这个函数wildcard会以pattern这个格式,去寻找存在的文件,返回存在文件的名字。
实例:
在该目录下创建三个文件:a.c b.c c.c
files = $(wildcard *.c)
all:
@echo files = $(files)
结果:
files = a.c b.c c.c
我们也可以用wildcard函数来判断,真实存在的文件 实例:
files2 = a.c b.c c.c d.c e.c abc
files3 = $(wildcard $(files2))
all:
@echo files3 = $(files3)
结果:
files3 = a.c b.c c.c
patsubst函数
函数patsubst语法如下:
$(patsubst pattern,replacement,$(var))
patsubst函数是从var变量里面取出每一个值,如果这个符合pattern格式,把它替换成replacement格式。
实例:
files2 = a.c b.c c.c d.c e.c abc
dep_files = $(patsubst %.c,%.d,$(files2))
all:
@echo dep_files = $(dep_files)
结果:
dep_files = a.d b.d c.d d.d e.d abc
最后推荐:
学习Makefile书籍:《跟我一起写Makefile》
学习Makefike参考文献:
a. 百度搜 “gnu make 于凤昌”。
b. 查看官方文档: http://www.gnu.org/software/make/manual/