Makefile简单入门基础

本文章是参考韦东山第一期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/

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值