Makefile语法——以gcc为例

时间:2021-05-18 07:04:26

一、编译和链接

编译是用编译程序把源代码文件代码译成目标代码(ObjectFile文件)的过程。链接是用链接程序把目标文件合成执行文件的过程。在编译是,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成目标文件。而在链接时,链接器会在所有的目标文件中找寻函数的实现,如果找不到,就会链接错误码。


二、命令行编译

例如我利用4个文件main.c:

#include<stdio.h>
int main()
{  int sum;
   sum = add(1, 2);
   show(sum);
   return 0;
}


f_add.c:

int add(int a, int b)
{
   return a + b;
}


f_show.c:

void show(int value)
{
   printf("sum = %d\n", value);
   stamp();
}


和f_stamp.c:

void stamp()
{
  printf("This is a stamp!\n");
}

可直接生成可执行文件a.out

gcc main.c f_add.c f_show.c f_stamp.c

如果想生成可执行文件calc,可以有如下过程:

首先生成目标文件*.o

$ gcc -c main.c f_add.c f_show.c f_stamp.c

如果没有其它多余文件,可以使用通配符(gcc -c *.c)。再链接成为可执行文件

$ gcc -o calc main.o f_add.o f_show.o f_stamp.o -lm

现在我们就可以通过./calc运行calc文件了。



三、Makefile规则

target: prerequisites...

<tab>command

其中target就是一个(或多个)目标文件。可以是ObjectFile,也可以是执行文件,还可以是一个标(Label)。prerequisites是生成那个target所需要的文件。Command是生成target时,make要执行的命令(任意的shell命令)。如果prerequisites中有一个以上的文件target文件要新的话,command所定义的命令就会被执行。这是Makefile的核心内容。


因此我们可以写出如下的Makefile

calc: main.o f_add.o f_show.o f_stamp.o
       gcc -o calc main.o f_add.o f_show.o f_stamp.o -lm

main.o: main.c f_add.c f_show.c
       gcc -c main.c f_add.c f_show.c
f_add.o: f_add.c
       gcc -c f_add.c
f_show.o: f_show.c f_stamp.c
       gcc -c f_show.c f_stamp.c
f_info: f_info.c
       gcc -c f_stamp.c
clean:
       rm calc main.o f_add.o f_show.o \
            f_stamp.o

注意每行需要执行的命令要以<tab>开头。反斜杠\是换行符。可以用#开头进行注释。clean不是一个文件,只是一个动作。


我们输入make命令后,系统会:

a、读入Makefile或makefile文件

b、读入被include的其他Makefile

c、初始化文件中的变量

d、推到隐晦规则,并分析所有规则

e、为所有目标文件(包括target)创建依赖关系链

f、决定那些目标要重新生成。即当目标文件不存在或其依赖文件的修改时间比目标文件的更新。

g、执行生成命令

这就是make的分层次文件依赖性。如果我们修改了一个源文件如f_stamp.c,那么f_stamp.o将被重新编译,进而clac也会被重新链接生成。在Makefile中向clean这种没被target直接或间接关联的,其后所定义的命令将不被自动执行。但可以通过make clean来执行。



四、自动推导

make会把每一个目标文件file.o文件就会把其对应的file.c文件加入到file.o的依赖文件中。


五、Makefile中使用变量

可以用变量来简化Makefile,如在前例可简化为

Objects = main.o f_add.o f_show.o f_stamp.o


calc: $(Objects)

gcc -o calc $(objects)


clean:
       rm calc $(Objects)

参考:陈皓. 《跟我一起写Makefile》

声明:A 包含B 

B 来自C 

 C 是怎么来的。

就像一棵树,开枝散叶

再来:



21、makefile常用语法讲解(1)

1、make是一个解释makefile中指令的命令工具。Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。

Makefile 里主要包含了五种类型的语句/行:显式规则、隐式规则、变量定义、文件指示和注释。

make命令格式:make [-f Makefile] [option] [target] 

2、编译和链接规则

1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。

2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。

3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

3、makefile的构成

1)需要由make工具创建的目标体(target),通常是目标文件或可执行文件

2)要创建的目标体所依赖的文件(dependency_file)。

3)创建每个目标体时需要运行的命令(command)。

格式如下:

target:dependency_files

<TAB>command

在这里面,变量一般都是字符串,他有点像c语言的宏。

makefile中的文件指示,包含3部分,一个是在一个Makefie中引用另一个Makefile,就像c语言的include一样;另一个是根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。

注释:注释符用"#",可以用反斜框进行转义,如输入注释,“\#”。

4、makefile的书写

1)所有文件都在一个目录中

示例

st_work : main.o  st_work.o  fun.o

gcc  main.o  st_work.o  fun.o  -o  st_work main (命令以Tab开头)

st_work.o : st_work.c

gcc  -c st_work.c  -o st_work

main.o : main.c  st_work.h

gcc -c main.c -o  main.o

fun.o : fun.c fun.h

gcc -c  fun.c -o fun.o

clean:

rm -f st_work *.o

2)多目录的写法

我们这里,在工作目录下有4个文件夹  分别是 sources(源文件) obj (中间文件)headers(头文件) bin(目标文件)

sources里面有 main.c  st_work.c fun.c

obj 里面最初没有文件

headers 里面有 fun.h st_work.h

最终目标取名为 st_work,它应存放到bin里面

预备知识:

gcc 的3个参数:

1. -o 指定目标文件

gcc sources/main.c -o bin/main

2. -c 编译的时候只生产目标文件不链接

gcc -c sources/main.c -o obj/main.o

3. -I 主要指定头文件的搜索路径

gcc -I headers -c main.c -o main.o

4. -l 指定静态库

gcc -lpthread ...

示例

bin/st_work : obj/main.o  obj/st_work.o  obj/fun.o  

   gcc  obj/main.o obj/st_work.o  obj/fun.o  -o bin/st_work  (命令以Tab开头)

obj/st_work.o : sources/st_work.c

gcc  -I  headers -c sources/st_work.c  -o  obj/st_work.o

obj/main.o : sources/main.c

gcc  -I  headers -c sources/main.c    -o  obj/main.o

obj/fun.o  : sources/fun.c

gcc  -I  headers -c sources/fun.c     -o  obj/fun.o

clean:

rm -f bin/st_work obj/*.o

3)隐式规则的引入

    3个预定义变量介绍:

1.  $@     表示要生成的目标

2.  $^     表示全部的依赖文件

3.  $<     表示第一个依赖文件

bin/st_work : obj/main.o  obj/st_work.o  obj/fun.o  

gcc  $^  -o $@  (命令一定要用以Tab开头)

obj/st_work.o : sources/st_work.c

gcc  -I  headers   -c $< -o  $@

obj/main.o : sources/main.c

gcc  -I  headers   -c $< -o  $@

obj/fun.o  : sources/fun.c

gcc  -I  headers   -c $< -o  $@

clean:

rm -f bin/st_work obj/*.o

目录

[隐藏]
跟我一起写Makefile:使用条件判断  

[编辑]使用条件判断

使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。

[编辑]示例

下面的例子,判断$(CC)变量是否“gcc”,如果是的话,则使用GNU函数编译目标。

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif

可见,在上面示例的这个规则中,目标“foo”可以根据变量“$(CC)”值来选取不同的函数库来编译程序。

我们可以从上面的示例中看到三个关键字:ifeq、else和endif。ifeq的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条件语句的结束,任何一个条件表达式都应该以 endif结束。

当我们的变量$(CC)值是“gcc”时,目标foo的规则是:

foo: $(objects)
$(CC) -o foo $(objects) $(libs_for_gcc)

而当我们的变量$(CC)值不是“gcc”时(比如“cc”),目标foo的规则是:

foo: $(objects)
$(CC) -o foo $(objects) $(normal_libs)

当然,我们还可以把上面的那个例子写得更简洁一些:

libs_for_gcc = -lgnu
normal_libs =

ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif

foo: $(objects)
$(CC) -o foo $(objects) $(libs)


[编辑]语法

条件表达式的语法为:

<conditional-directive>
<text-if-true>
endif

以及:

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

其中<conditional-directive>;表示条件关键字,如“ifeq”。这个关键字有四个。

第一个是我们前面所见过的“ifeq”

ifeq (<arg1>, <arg2>) 
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

比较参数“arg1”和“arg2”的值是否相同。当然,参数中我们还可以使用make的函数。如:

ifeq ($(strip $(foo)),)
<text-if-empty>
endif

这个示例中使用了“strip”函数,如果这个函数的返回值是空(Empty),那么<text-if-empty>;就生效。

第二个条件关键字是“ifneq”。语法是:

ifneq (<arg1>, <arg2>) 
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

其比较参数“arg1”和“arg2”的值是否相同,如果不同,则为真。和“ifeq”类似。

第三个条件关键字是“ifdef”。语法是:

ifdef <variable-name> 

如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。当然,<variable- name>同样可以是一个函数的返回值。注意,ifdef只是测试一个变量是否有值,其并不会把变量扩展到当前位置。还是来看两个例子:

示例一:

bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif

示例二:

foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif

第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。

第四个条件关键字是“ifndef”。其语法是:

ifndef <variable-name>

这个我就不多说了,和“ifdef”是相反的意思。

在<conditional-directive>这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)。而注释符“#”同样也是安全的。“else”和“endif”也一样,只要不是以[Tab]键开始就行了。

特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。

而且,为了避免混乱,make不允许把整个条件语句分成两部分放在不同的文件中。