当前位置: 澳门新濠3559 > 操作系统 > 正文

.c 文件都依赖于,我们发现所有.c都会被编译成相

时间:2019-11-08 15:11来源:操作系统
#obj = main.o sub.o add.o div.o mul.o src = $(wildcard *.c) #搜索.c文件 可以加路径 在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。 前面已经分享了单目录项下多文件的makefile的

#obj = main.o sub.o add.o div.o mul.o
src = $(wildcard *.c)
#搜索.c文件 可以加路径

在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。

前面已经分享了单目录项下多文件的makefile的编写,现在来看看多目录下多文件makefile的编写;

一、make 是啥?

make 是软件构造工具,通过读取 Makefile文件自动化完成构建。

obj = $(patsubst %.c, %.o, $(src))
#字符匹配替换,将src中所有.c为后缀的字符串替换为.o为后缀的

target ... : prerequisites ...

        在做项目时,一般文件都会分几个目录来存放;基本的是  include/  bin/ src/ obj/ lib/ tools/,这几个文件;我先说下我的文件存放目录,用ls -R可以查看到所有文件:./include/common.h    ./src/main.c    ./src/printStatus.c  就三个文件,其中*.c 文件都依赖于 *.h文件;

二、make 核心?

target ... : prerequisites ...
    (TAB)command

  1. target 名为目标文件,实际是Object File,或执行文件,以及标签(Label)。
  2. .c 文件都依赖于,我们发现所有.c都会被编译成相同名称的.o文件。prerequisites 是target所依赖的文件或是目标。
    如果 prerequisites 的修改时间比 target 更新,或者 target 文件不存在,command 被执行。首次执行 make 创建了 target 文件是因为 target 文件不存在,之后执行 make 会根据修改时间的属性进行判断,如更新了源码内容,那么 command 还会再次执行。
    这就是 Makefile 的规则和核心。

target = app

command

        同样的先上第一版makefile:

三、make 实例

标准的编译过程:源文件先被编译成目标文件,然后再由目标文件连接成可执行文件。

cc = gcc   
prom = calc   
deps = calc.h
obj = main.o getch.o getop.o stack.o 

$(prom): $(obj)       
    $(cc) -o $(prom) $(obj)    

main.o: main.c $(deps)        
    $(cc) -c main.c

getch.o: getch.c $(deps)       
    $(cc) -c getch.c

getop.o: getop.c $(deps)       
    $(cc) -c getop.c

stack.o: stack.c $(deps)       
    $(cc) -c stack.c

我们发现所有.c都会被编译成相同名称的.o文件,可以根据该特点再对其做进一步的简化:

cc = gcc
prom = calc
deps = calc.h
obj = main.o getch.o getop.o stack.o

$(prom): $(obj)
    $(cc) -o $(prom) $(obj)

%.o: %.c $(deps)
    $(cc) -c $< -o $@

1. %.o:%.c,这是一个模式规则,表示所有的.o目标都依赖于与它同名的.c文件(当然还有deps中列出的头文件)。

  1. $< 是依赖关系表中的第一项(如果我们想引用的是整个关系表,那么就应该使用$^),具体到我们这里就是%.c。
  2. $@ 是当前语句的目标,即%.o。这样一来,make命令就会自动将所有的.c源文件编译成同名的.o文件。不用我们一项一项去指定了。整个代码自然简洁了许多。

如果我们需要往工程中添加一个.c或.h,可能同时就要再手动为obj常量再添加一个.o文件,如果这列表很长,代码会非常难看,为此,我们需要用到Makefile中的函数,再次简化:

cc = gcc   
prom = calc   
deps = $(shell find ./ -name"*.h")   
src = $(shell find ./ -name"*.c")   
obj = $(src:%.c=%.o)    

$(prom): $(obj)       
    $(cc) -o $(prom) $(obj)    

%.o: %.c $(deps)       
    $(cc) -c $< -o $@    

clean:       
    rm -rf $(obj) $(prom)

shell函数主要用于执行shell命令,具体到这里就是找出当前目录下所有的.c和.h文件。而$(src:%.c=%.o)则是一个字符替换函数,它会将src所有的.c字串替换成.o,实际上就等于列出了所有.c文件要编译的结果。

四、make 执行过程

man make:
... ...
-d  Print  debugging  information in addition to normal processing.  The debugging information says which files are being considered for remaking, which file-times are being compared and with what results, which files actually need to be remade, which implicit rules are considered and which are applied---everything interesting about how make decides what to do.

基本信息输出:make --debug=b

详细信息输出:make -d

#目标:依赖
$(target):$(obj)
  gcc $^ -o $@
  #gcc $(obj) -o $(target)

...

 

%.o:%.c
  gcc -c $< -o $@

...

[cpp] view plaincopy

# $<:规则中的第一个依赖
# $^:规则中的所有依赖
# $@:规则中的目标

    target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。

 

#匿名目标 防止名字冲突
.PHONY:clean
clean:
rm $(obj) $(target) -f

    prerequisites就是,要生成那个target所需要的文件或是目标。

  1. CC=gcc  
  2. objects=obj/main.o obj/printStatus.o  
  3.   
  4. bin/main:$(objects)  
  5.    $(CC) -o bin/main $(objects)  
  6.   
  7. obj/main.o:src/main.c include/common.h  
  8.    $(CC) -o obj/main.o -c src/main.c -Iinclude  
  9.   
  10. obj/printStatus.o:src/printStatus.c include/common.h  
  11.    $(CC) -o obj/printStatus.o -c src/printStatus.c -Iinclude  
  12.   
  13. clean:  
  14.    rm -rf $(objects) bin/main  

搜索

    command也就是make需要执行的命令。(任意的Shell命令)

        上面的makefile就是gcc命令的拼凑起来的,下面来分享下比较通用的多目录下的makefile;

复制

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

 

cc = gcc

prom = calc

deps = calc.h

obj = main.o getch.o getop.o stack.o$(prom): $(obj)

    $(cc) -o $(prom) $(obj)

%.o: %.c $(deps)

    $(cc) -c $< -o $@

        在看多目录的makefile时,先来理解下几个函数和变量;

在这里,我们用到了几个特殊的宏。首先是%.o:%.c,这是一个模式规则,表示所有的.o目标都依赖于与它同名的.c文件(当然还有deps中列出的头文件)。再来就是命令部分的$<和$@,其中$<代表的是依赖关系表中的第一项(如果我们想引用的是整个关系表,那么就应该使用$^),具体到我们这里就是%.c。而$@代表的是当前语句的目标,即%.o。这样一来,make命令就会自动将所有的.c源文件编译成同名的.o文件。不用我们一项一项去指定了。整个代码自然简洁了许多。

函数:

这些都是makefile的内建命令,含义如下:

        wildcard 这是扩展通配符函数,功能是展开成一列所有符合由其参数描述的文 件名,文件间以空格间隔;比如:罗列出src下的所有.c文件:$(wildcard ${SRC}/*.c)

$@  target

        patsubst 这是匹配替换函数, patsubst ( 需要匹配的文件样式,匹配替换成什么文件,需要匹配的源文件)函数。比如:用src下的*.c替换成对应的 *.o文件存放到obj中:$(patsubst  %.c, ${OBJ}/%.o, $(notdir $(SOURCE)))

$%  file name of an archive member

        notdir 这是去除路径函数,在上面patsubst函数中已经使用过,去除SOURCE中文件的所有目录,只留下文件名;

$<  first prerequisite

 

$?  prerequisites newer than target

变量:

$^  prerequisites

        $@:表示目标文件;一般是在规则中这么用:gcc  -o $@  $(object);

$+  similar to $^, including duplicates

        $^:表示所有依赖文件;一般是在规则中这么用:gcc -o $@  $^  ;用所有依赖文件链接成目的文件;

$*  stem of the target filename

        $<:表示第一个依赖文件;在规则中使用:gcc -o $@ -c $< ;其实这个时候就是每个依赖文件生成一个目的文件;

到目前为止,我们已经有了一个不错的makefile,至少用来维护这个小型工程是没有什么问题了。当然,如果要进一步增加上面这个项目的可扩展性,我们就会需要用到一些Makefile中的伪目标和函数规则了。例如,如果我们想增加自动清理编译结果的功能就可以为其定义一个带伪目标的规则

 

cc = gcc

prom = calc

deps = calc.h

obj = main.o getch.o getop.o stack.o$(prom): $(obj)

    $(cc) -o $(prom) $(obj)

%.o: %.c $(deps)

    $(cc) -c $< -o $@clean:

    rm -rf $(obj) $(prom)

第二版makefile

有了上面最后两行代码,当我们在终端中执行make clean命令时,它就会去删除该工程生成的所有编译文件。

 

另外,如果我们需要往工程中添加一个.c或.h,可能同时就要再手动为obj常量再添加第一个.o文件,如果这列表很长,代码会非常难看,为此,我们需要用到Makefile中的函数,这里我们演示两个:

[cpp] 澳门新濠3559,view plaincopy

cc = gcc

prom = calc

deps = $(shell find ./ -name "*.h")

src = $(shell find ./ -name "*.c")

obj = $(src:%.c=%.o) $(prom): $(obj)

    $(cc) -o $(prom) $(obj)

%.o: %.c $(deps)

    $(cc) -c $< -o $@clean:

    rm -rf $(obj) $(prom)

 

其中,shell函数主要用于执行shell命令,具体到这里就是找出当前目录下所有的.c和.h文件。而$(src:%.c=%.o)则是一个字符替换函数,它会将src所有的.c字串替换成.o,实际上就等于列出了所有.c文件要编译的结果。有了这两个设定,无论我们今后在该工程加入多少.c和.h文件,Makefile都能自动将其纳入到工程中来。

  1. #把所有的目录做成变量,方便修改和移植   
  2. BIN = ./bin  
  3.  SRC = ./src  
  4.  INC = ./include  
  5.  OBJ = ./obj  
  6.    
  7. #提前所有源文件(即:*.c文件)和所有中间文件(即:*.o)  
  8.  SOURCE = $(wildcard ${SRC}/*.c)  
  9.  OBJECT = $(patsubst %.c,${OBJ}/%.o,$(notdir ${SOURCE}))  
  10.    
  11. #设置最后目标文件  
  12.  TARGET = main  
  13.  BIN_TARGET = ${BIN}/${TARGET}  
  14.    
  15.  CC = gcc   
  16.  CFLAGS = -g -Wall -I${INC}   
  17.    
  18. #用所有中间文件生成目的文件,规则中可以用 $^替换掉 ${OBJECT}  
  19.  ${BIN_TARGET}:${OBJECT}  
  20.      $(CC) -o $@ ${OBJECT}  
  21.    
  22. #生成各个中间文件  
  23.  ${OBJ}/%.o:${SRC}/%.c   
  24.      $(CC) $(CFLAGS) -o $@ -c $<  
  25.    
  26.  .PHONY:clean  
  27.  clean:  
  28.      find $(OBJ) -name *.o -exec rm -rf {} ; #这个是find命令,不懂的可以查下资料  
  29.      rm -rf $(BIN_TARGET)  

到这里,我们就基本上将日常会用到的Makefile写法介绍了一遍。如果你想了解更多关于makefile和make的知识,请参考GNU Make Manual。

        这个makefile的好处就是通用性,里面不涉及到具体的文件名,当你往src目录中添加新文件时,可以不需要修改makefile,所以这是个非常好的工具;

编辑:操作系统 本文来源:.c 文件都依赖于,我们发现所有.c都会被编译成相

关键词: