Makefile基础

基本规则

除了Hello World这种极简单的程序之外,一般的程序都是由多个源文件编译链接而成的,这些源文件的处理步骤通常用Makefile来管理。

Makefile每条规则的格式

make命令会自动读取当前目录下的Makefile文件,完成相应的编译步骤。Makefile由一组规则(Rule)组成,每条规则的格式是:

target ... : prerequisites ... 
	command1
	command2
	...

例如:

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main是这条规则的目标(Target),main.o、stack.o和maze.o是这条规则的条件(Prerequisite)。目标和条件之间的关系是:欲更新目标,必须首先更新它的所有条件;所有条件中只要有一个条件被更新了,目标也必须随之被更新。所谓“更新”就是执行一遍规则中的命令列表,命令列表中的每条命令必须以一个Tab开头,注意不能是空格,Makefile的格式不像C语言的缩进那么随意,对于Makefile中的每个以Tab开头的命令,make会创建一个Shell进程去执行它

make命令的执行步骤

对于下面这个例子

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main.o: main.c main.h stack.h maze.h
	gcc -c main.c

stack.o: stack.c stack.h main.h
	gcc -c stack.c

maze.o: maze.c maze.h main.h
	gcc -c maze.c

make的执行步骤如下:

  1. 尝试更新Makefile中第一条规则的目标main,第一条规则的目标称为缺省目标,只要缺省目标更新了就算完成任务了,其它工作都是为这个目的而做的。由于我们是第一次编译,main文件还没生成,显然需要更新,但规则说必须先更新了main.ostack.omaze.o这三个条件,然后才能更新main
  2. 所以make会进一步查找以这三个条件为目标的规则,这些目标文件也没有生成,也需要更新,所以执行相应的命令(gcc -c main.cgcc -c stack.cgcc -c maze.c)更新它们。
  3. 最后执行gcc main.o stack.o maze.o -o main更新main

make如何会自动选择那些受影响的源文件重新编译,不受影响的源文件则不重新编译

make会自动选择那些受影响的源文件重新编译,不受影响的源文件则不重新编译,这是怎么做到的呢?

  1. make仍然尝试更新缺省目标,首先检查目标main是否需要更新,这就要检查三个条件main.ostack.omaze.o是否需要更新。
  2. make会进一步查找以这三个条件为目标的规则,然后发现main.omaze.o需要更新。因为它们都有一个条件是maze.h,而这个文件的修改时间比main.omaze.o晚,即条件的修改时间比目标晚。所以执行相应的命令更新main.omaze.o
  3. 既然main的三个条件中有两个被更新过了,那么main也需要更新,所以执行命令gcc main.o stack.o maze.o -o main更新main

Makefile的clean规则

通常Makefile都会有一个clean规则,用于清除编译过程中产生的二进制文件,保留源文件:

clean:
	@echo "cleanning project"
	-rm main *.o
	@echo "clean completed"

把这条规则添加到我们的Makefile末尾,然后执行这条规则:

$ make clean 
cleanning project
rm main *.o
clean completed

和前面介绍的规则不同,clean目标不依赖于任何条件,并且执行它的命令列表不会生成clean这个文件,刚才说过,只要执行了命令列表就算更新了目标,即使目标并没有生成也算。

如果在make的命令行中指定一个目标(例如执行命令make clean),则更新这个目标;如果不指定目标(即执行命令make),则更新Makefile中第一条规则的目标(缺省目标)。

在这个例子还演示了命令前面加@和-字符的效果

  • 如果make执行的命令前面加了**@字符**,则不显示命令本身而只显示它的结果;通常make执行的命令如果出错(该命令的退出状态非0)就立刻终止,不再执行后续命令.
  • 如果命令前面加了**-号**,即使这条命令出错,make也会继续执行后续命令。

通常rm命令和mkdir命令前面要加-号,因为rm要删除的文件可能不存在,mkdir要创建的目录可能已存在,这两个命令都有可能出错,但这种错误是应该忽略的。例如上面已经执行过一遍make clean,再执行一遍就没有文件可删了,这时rm会报错,但make忽略这一错误,继续执行后面的echo命令:

$ make clean 
cleanning project
rm main *.o
rm: cannot remove `main': No such file or directory
rm: cannot remove `*.o': No such file or directory
make: [clean] Error 1 (ignored)
clean completed

一般会在Makefile添加一条特殊规则,把clean声明为一个伪目标:

clean:
	@echo "cleanning project"
	-rm main *.o
	@echo "clean completed"

.PHONY: clean

在C语言中要求变量和函数先声明后使用,而Makefile不太一样,这条规则写在clean规则的后面也行,也能起到声明clean是伪目标的作用,当然写在前面也行。

make处理Makefile的两个阶段

gcc处理一个C程序分为预处理和编译两个阶段,类似地,make处理Makefile的过程也分为两个阶段:

  • 第一阶段:从前到后读取所有规则,建立起一个完整的依赖关系图,如下图

在这里插入图片描述

  • 第二阶段:从缺省目标或者命令行指定的目标开始,根据依赖关系图选择适当的规则执行,执行Makefile中的规则和执行C代码不一样,并不是从前到后按顺序执行,也不是所有规则都要执行一遍,例如make缺省目标时不会更新clean目标,因为从上图可以看出,它跟缺省目标没有任何依赖关系。

clean目标是一个约定俗成的名字,在所有软件项目的Makefile中都表示清除编译生成的文件,类似这样的约定俗成的目标名字有:

  • all,执行主要的编译工作,通常用作缺省目标。
  • install,执行编译后的安装工作,把可执行文件、配置文件、文档等分别拷到不同的安装目录。
  • clean,删除编译生成的二进制文件。
  • distclean,不仅删除编译生成的二进制文件,也删除其它生成的文件,例如配置文件和格式转换后的文档,执行make distclean之后应该清除所有这些文件,只留下源文件。

隐含规则和模式规则

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main.o: main.c main.h stack.h maze.h
	gcc -c main.c

stack.o: stack.c stack.h main.h
	gcc -c stack.c

maze.o: maze.c maze.h main.h
	gcc -c maze.c
	
clean:
	-rm main *.o

.PHONY: clean

上面的Makefile可以改写成,下面的这个

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h

main.o: main.c
	gcc -c main.c

stack.o: stack.c
	gcc -c stack.c

maze.o: maze.c
	gcc -c maze.c

clean:
	-rm main *.o

.PHONY: clean

为什么可以这样改写?一个目标依赖的所有条件不一定非得写在一条规则中,也可以拆开写。如果一个目标拆开写多条规则,其中只有一条规则允许有命令列表,其它规则应该没有命令列表,否则make会报警告并且采用最后一条规则的命令列表。

但是这样改写岂不是更繁琐了?可以继续改写成下面这个

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h

clean:
	-rm main *.o

.PHONY: clean

可是现在main.ostack.omaze.o这三个目标连编译命令都没有了,怎么编译的呢?试试看:

$ make
cc    -c -o main.o main.c
cc    -c -o stack.o stack.c
cc    -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main

现在解释一下前三条编译命令是怎么来。如果一个目标在Makefile中的所有规则都没有命令列表,make会尝试在内建的**隐含规则(Implicit Rule)**数据库中查找适用的规则。make的隐含规则数据库可以用make -p命令打印,打印出来的格式也是Makefile的格式,包括很多变量和规则,其中和我们这个例子有关的隐含规则有:

# default
OUTPUT_OPTION = -o $@

# default
CC = cc

# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

%.o: %.c
#  commands to execute (built-in):
        $(COMPILE.c) $(OUTPUT_OPTION) $<
  • #号在Makefile中表示单行注释,就像C语言的//注释一样。
  • CC是一个Makefile变量,用CC = cc定义和赋值,用$(CC)取它的值,其值应该是cc。Makefile变量像C的宏定义一样,代表一串字符,在取值的地方展开。cc是一个符号链接,通常指向gcc,在有些UNIX系统上可能指向另外一种C编译器。
  • CFLAGS这个变量没有定义, ( C F L A G S ) 展开是空, C P P F L A G S 和 T A R G E T A R C H 也是如此。这样 (CFLAGS)展开是空,CPPFLAGS和TARGET_ARCH也是如此。这样 (CFLAGS)展开是空,CPPFLAGSTARGETARCH也是如此。这样(COMPILE.c)展开应该是cc␣空␣空␣空␣-c,去掉所有的“空”得到cc␣␣␣␣-c,注意中间留下4个空格,所以%.o: %.c规则的命令 ( C O M P I L E . c ) ␣ (COMPILE.c)␣ (COMPILE.c)(OUTPUT_OPTION)␣ < 展开之后是 c c ␣␣␣␣ − c ␣ − o ␣ <展开之后是cc␣␣␣␣-c␣-o␣ <展开之后是cc␣␣␣␣co@␣$<,和上面的编译命令已经很接近了。
  • @ 和 @和 @<是两个特殊的变量,** @ ∗ ∗ 的取值为规则中的目标, ∗ ∗ @**的取值为规则中的目标,** @的取值为规则中的目标,<**的取值为规则中的第一个条件。
  • %.o: %.c是一种特殊的规则,称为模式规则(Pattern Rule)

现在回顾一下整个过程,在我们的Makefile中以main.o为目标的规则都没有命令列表,所以make会查找隐含规则,发现隐含规则中有这样一条模式规则适用,main.o符合%.o的模式,现在%就代表main(称为main.o这个名字的Stem),再替换到%.c中就是main.c。所以这条模式规则相当于:

main.o: main.c
	cc    -c -o main.o main.c

随后,在处理stack.o目标时又用到这条模式规则,maze.o也同样处理。这三条规则可以由make的隐含规则推导出来,所以不必写在Makefile中。

先前我们写Makefile都是以目标为中心,一个目标依赖于若干条件,现在换个角度,以条件为中心,Makefile还可以这么写:

main: main.o stack.o maze.o
	gcc main.o stack.o maze.o -o main

main.o stack.o maze.o: main.h
main.o maze.o: maze.h
main.o stack.o: stack.h

clean:
	-rm main *.o

.PHONY: clean

我们知道,写规则的目的是让make建立依赖关系图,不管怎么写,只要把所有的依赖关系都描述清楚了就行。对于多目标的规则,make会拆成几条单目标的规则来处理,例如

target1 target2: prerequisite1 prerequisite2
	command $< -o $@

这样一条规则相当于:

target1: prerequisite1 prerequisite2
	command prerequisite1 -o target1

target2: prerequisite1 prerequisite2
	command prerequisite1 -o target2

注意两条规则的命令列表是一样的,但$@的取值不同。

变量

= 不立即展开

这一节我们详细看看Makefile中关于变量的语法规则。

先看一个简单的例子:

foo = $(bar) 
bar = Huh

all: 
	@echo $(foo)

我们执行make将会打出Huh。当make读到foo = ( b a r ) 时,确定 f o o 的值是 (bar)时,确定foo的值是 (bar)时,确定foo的值是(bar),但并不立即展开 ( b a r ) ,然后读到 b a r = H u h ,确定 b a r 的值是 H u h ? ,然后在执行规则 a l l : 的命令列表时才需要展开 (bar),然后读到bar = Huh,确定bar的值是Huh?,然后在执行规则all:的命令列表时才需要展开 (bar),然后读到bar=Huh,确定bar的值是Huh?,然后在执行规则all:的命令列表时才需要展开(foo),得到 ( b a r ) ,再展开 (bar),再展开 (bar),再展开(bar),得到Huh?。因此,虽然bar的定义写在foo之后, ( f o o ) 展开还是能够取到 (foo)展开还是能够取到 (foo)展开还是能够取到(bar)的值。

这种特性有好处也有坏处。

  • 好处:我们可以把变量的值推迟到后面定义

  • 坏处:有可能写出无穷递归的定义,例如CFLAGS = $(CFLAGS) -O,或者

    A = $(B)
    B = $(A)
    

    当然,make有能力检测出这样的错误而不会陷入死循环。

:= 立即展开

有时候我们希望make在遇到变量定义时立即展开,可以用:=运算符,例如:

x := foo
y := $(x) bar

all: 
	@echo "-$(y)-"

make读到y := $(x) bar定义时,立即把$(x)展开,使变量y的取值是foo bar,如果把这两行颠倒过来:

y := $(x) bar
x := foo

那么当make读到y := $(x) bar时,x还没有定义,展开为空值,所以y的取值是␣bar,注意bar前面有个空格。

变量定义的开始与结束

一个变量的定义从=后面的第一个非空白字符开始(从$(x)$开始),包括后面的所有字符,直到注释或换行之前结束。如果要定义一个变量的值是一个空格,可以这样:

nullstring := 
space := $(nullstring) # end of the line

nullstring的值为空,space的值是一个空格,后面写个注释是为了增加可读性,如果不写注释就换行,则很难看出$(nullstring)后面有个空格。

?= 运算符

还有一个比较有用的赋值运算符是?=,例如foo ?= $(bar)的意思是:如果foo没有定义过,那么?=相当于=,定义foo的值是$(bar),但不立即展开;如果先前已经定义了foo,则什么也不做,不会给foo重新赋值。

+= 运算符

+=运算符

  • 变量还没有定义过就直接用+=赋值,那么+=相当于=

  • 变量是用=定义的,+=仍然保持=的特性,objects的值是main.o $(foo)(注意$(foo)前面自动添一个空格),但不立即展开,等到后面需要展开$(objects)时会展开成main.o foo.o bar.o

    objects = main.o
    objects += $(foo)
    foo = foo.o bar.o
    
  • object是用:=定义的,+=保持:=的特性,objects的值是main.o $(foo),立即展开得到main.o (这时foo还没定义),注意main.o后面的空格仍保留。

    objects := main.o
    objects += $(foo)
    foo = foo.o bar.o
    

特殊变量

  • $@,表示规则中的目标。
  • $<,表示规则中的第一个条件。
  • $?,表示规则中所有比目标新的条件,组成一个列表,以空格分隔。
  • $^,表示规则中的所有条件,组成一个列表,以空格分隔。

常用变量

make的隐含规则数据库中用到了很多变量,有些变量没有定义(例如CFLAGS),有些变量定义了缺省值(例如CC),我们写Makefile时可以重新定义这些变量的值,也可以在缺省值的基础上追加。以下列举一些常用的变量:

  • AR

    静态库打包命令的名字,缺省值是ar

  • ARFLAGS

    静态库打包命令的选项,缺省值是rv

  • AS

    汇编器的名字,缺省值是as

  • ASFLAGS

    汇编器的选项,没有定义。

  • CC

    C编译器的名字,缺省值是cc

  • CFLAGS

    C编译器的选项,没有定义。

  • CXX

    C++编译器的名字,缺省值是g++

  • CXXFLAGS

    C++编译器的选项,没有定义。

  • CPP

    C预处理器的名字,缺省值是$(CC) -E

  • CPPFLAGS

    C预处理器的选项,没有定义。

  • LD

    链接器的名字,缺省值是ld

  • LDFLAGS

    链接器的选项,没有定义。

  • TARGET_ARCH

    和目标平台相关的命令行选项,没有定义。

  • OUTPUT_OPTION

    输出的命令行选项,缺省值是-o $@

  • LINK.o

    .o文件链接在一起的命令行,缺省值是$(CC) $(LDFLAGS) $(TARGET_ARCH)

  • LINK.c

    .c文件链接在一起的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)

  • LINK.cc

    .cc文件(C++源文件)链接在一起的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)

  • COMPILE.c

    编译.c文件的命令行,缺省值是$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

  • COMPILE.cc

    编译.cc文件的命令行,缺省值是$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c

  • RM

    删除命令的名字,缺省值是rm -f

补充:预处理器、编译器、汇编器、链接器

现有两个源文件main.c和sum.c

// main.c
int sum(int *a, int n);

int aray[2] = {1, 2};

int main()
{
    int val = sum(array, 2);
    return val;
}
// sum.c
int sum(int *a, int n)
{
    int i, s = 0;
    
    for(i = 0; i < n; i++){
        s += a[i];
    }
}

两源文件从源文件到可执行文件的步骤如下图所示:

在这里插入图片描述

两源文件依次要经过 预处理器、编译器、汇编器和链接器,最终生成可执行文件

  • 首先,运行C预处理器(cpp),将C的源程序main.c翻译成一个ASCII码的中间文件main.i;
  • 接下来,运行C编译器(ccl),将main.i翻译成一个ASCII码的汇编语言文件main.s;
  • 然后,运行汇编器(as),将main.s翻译成一个可重定位目标文件main.o;
    sum.c同样经过上述三个步骤生成sum.o;
  • 最后,运行链接器(ld),将main.o和sum.o以及一些必要的系统文件组合起来,创建一个可执行目标文件prog。

各阶段的主要功能

  • 预处理主要用于C语言编译器对各种预处理命令进行处理,包括对头文件的包含、宏定义的扩展、条件编译的选择等。例如,对# include指示的处理结果,就是将对应.h文件的内容插入到源程序文件中。
  • 编译会先对源程序进行词法分析、语法分析和语义分析,然后根据分析的结果进行代码优化和存储分配,最终会把C语言源程序翻译成汇编语言程序。
  • 汇编的功能是将编译生成的汇编语言代码转换为机器语言代码。
  • 链接的功能是将所有关联的可重定位目标文件组合起来,以生成一个可执行文件。

自动处理头文件的依赖关系

gcc -MM *.c

可以用gcc-M选项自动生成目标文件和源文件的依赖关系:

$ gcc -M main.c
main.o: main.c /usr/include/stdio.h /usr/include/features.h \
  /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \
  /usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \
  /usr/lib/gcc/i486-linux-gnu/4.3.2/include/stddef.h \
  /usr/include/bits/types.h /usr/include/bits/typesizes.h \
  /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \
  /usr/lib/gcc/i486-linux-gnu/4.3.2/include/stdarg.h \
  /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h main.h \
  stack.h maze.h

-M选项把stdio.h以及它所包含的系统头文件也找出来了,如果我们不需要输出系统头文件的依赖关系,可以用-MM选项:

$ gcc -MM *.c
main.o: main.c main.h stack.h maze.h
maze.o: maze.c maze.h main.h
stack.o: stack.c stack.h main.h

GNU make的官方手册建议的Makefile写法

GNU make的官方手册建议这样写:

all: main

main: main.o stack.o maze.o
	gcc $^ -o $@

clean:
	-rm main *.o

.PHONY: clean

sources = main.c stack.c maze.c

include $(sources:.c=.d)

%.d: %.c
	set -e; rm -f $@; \
	$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$
  • sources变量包含我们要编译的所有.c文件,$(sources:.c=.d)是一个变量替换语法,把sources变量中每一项的.c替换成.d,所以include这一句相当于:

    include main.d stack.d maze.d
    

    类似于C语言的#include指示,这里的include表示包含三个文件main.dstack.dmaze.d,这三个文件也应该符合Makefile的语法。

  • 如果现在你的工作目录是干净的,只有.c文件、.h文件和Makefile,运行make的结果是:

    Makefile:13: main.d: No such file or directory
    Makefile:13: stack.d: No such file or directory
    Makefile:13: maze.d: No such file or directory
    set -e; rm -f maze.d; \
    	cc -MM  maze.c > maze.d.$$; \
    	sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
    	rm -f maze.d.$$
    set -e; rm -f stack.d; \
    	cc -MM  stack.c > stack.d.$$; \
    	sed 's,\(stack\)\.o[ :]*,\1.o stack.d : ,g' < stack.d.$$ > stack.d; \
    	rm -f stack.d.$$
    set -e; rm -f main.d; \
    	cc -MM  main.c > main.d.$$; \
    	sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.$$ > main.d; \
    	rm -f main.d.$$
    cc    -c -o main.o main.c
    cc    -c -o stack.o stack.c
    cc    -c -o maze.o maze.c
    gcc main.o stack.o maze.o -o main
    

    一开始找不到.d文件,所以make会报警告。但是make会把include的文件名也当作目标来尝试更新,而这些目标适用模式规则%.d: %c,所以执行它的命令列表,比如生成maze.d的命令:

    set -e; rm -f maze.d; \
    	cc -MM  maze.c > maze.d.$$; \
    	sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
    	rm -f maze.d.$$
    

    虽然在Makefile中这个命令写了四行,但其实是一条命令,make只创建一个Shell进程执行这条命令,这条命令分为5个子命令,用;号隔开,并且为了美观,用续行符\拆成四行来写。执行步骤为:

    • set -e命令设置当前Shell进程为这样的状态:如果它执行的任何一条命令的退出状态非零则立刻终止,不再执行后续命令。
    • 把原来的maze.d删掉。
    • 重新生成maze.c的依赖关系,保存成文件maze.d.1234(假设当前Shell进程的id是1234)。注意,在Makefile中$有特殊含义,如果要表示它的字面意思则需要写两个 ,所以 M a k e f i l e 中的四个 ,所以Makefile中的四个 ,所以Makefile中的四个传给Shell变成两个 ,两个 ,两个 ,两个在Shell中表示当前进程的id,一般用它给临时文件起名,以保证文件名唯一。
    • 这个sed命令比较复杂,就不细讲了,主要作用是查找替换。maze.d.1234的内容应该是maze.o: maze.c maze.h main.h,经过sed处理之后存为maze.d,其内容是maze.o maze.d: maze.c maze.h main.h
    • 最后把临时文件maze.d.1234删掉。
  • 不管是Makefile本身还是被它包含的文件,只要有一个文件在make过程中被更新了,make就会重新读取整个Makefile以及被它包含的所有文件,现在main.dstack.dmaze.d都生成了,就可以正常包含进来了(假如这时还没有生成,make就要报错而不是报警告了),相当于在Makefile中添了三条规则:

    main.o main.d: main.c main.h stack.h maze.h
    maze.o maze.d: maze.c maze.h main.h
    stack.o stack.d: stack.c stack.h main.h
    

举例说明

如果我在main.c中加了一行#include “foo.h”,那么:

  1. main.c的修改日期变了,根据规则main.o main.d: main.c main.h stack.h maze.h要重新生成main.o和main.d。生成main.o的规则有两条:

    main.o: main.c main.h stack.h maze.h
    %.o: %.c
    #  commands to execute (built-in):
            $(COMPILE.c) $(OUTPUT_OPTION) $<
    

    第一条是把规则main.o main.d: main.c main.h stack.h maze.h拆开写得到的,第二条是隐含规则,因此执行cc命令重新编译main.o。生成main.d的规则也有两条:

    main.d: main.c main.h stack.h maze.h
    %.d: %.c
    	set -e; rm -f $@; \
    	$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
    	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    	rm -f $@.$$$$
    

    因此main.d的内容被更新为main.o main.d: main.c main.h stack.h maze.h foo.h。

  2. 由于main.d被Makefile包含,main.d被更新又导致make重新读取整个Makefile,把新的main.d包含进来,于是新的依赖关系生效了。

常用的make命令行选项

  • -n选项只打印要执行的命令,而不会真的执行命令,这个选项有助于我们检查Makefile写得是否正确,由于Makefile不是顺序执行的,用这个选项可以先看看命令的执行顺序,确认无误了再真正执行命令。

  • -C选项可以切换到另一个目录执行那个目录下的Makefile,比如先退到上一级目录再执行我们的Makefile(假设我们的源代码都放在testmake目录下):

    $ cd ..
    $ make -C testmake
    make: Entering directory `/home/akaedu/testmake'
    cc    -c -o main.o main.c
    cc    -c -o stack.o stack.c
    cc    -c -o maze.o maze.c
    gcc main.o stack.o maze.o -o main
    make: Leaving directory `/home/akaedu/testmake'
    

    一些规模较大的项目会把不同的模块或子系统的源代码放在不同的子目录中,然后在每个子目录下都写一个该目录的Makefile,然后在一个总的Makefile中用make -C命令执行每个子目录下的Makefile。例如Linux内核源代码根目录下有Makefile,子目录fsnet等也有各自的Makefile,二级子目录fs/ramfsnet/ipv4等也有各自的Makefile

  • make命令行也可以用=:=定义变量,如果这次编译我想加调试选项-g,但我不想每次编译都加-g选项,可以在命令行定义CFLAGS变量,而不必修改Makefile编译完了再改回来:

    $ make CFLAGS=-g
    cc -g   -c -o main.o main.c
    cc -g   -c -o stack.o stack.c
    cc -g   -c -o maze.o maze.c
    gcc main.o stack.o maze.o -o main
    

    如果在Makefile中也定义了CFLAGS变量,则命令行的值覆盖Makefile中的值。

参考文章

《Linux C编程一站式学习》 第 22 章 Makefile基础 https://docs.huihoo.com/c/linux-c-programming/ch22.html

《Linux C编程一站式学习》 https://docs.huihoo.com/c/linux-c-programming/

编译器、汇编器、链接器https://blog.csdn.net/qq_39918677/article/details/120372053

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值