c语⾔函数依赖关系⽣成,⾃动⽣成依赖关系(⼗)
我们在之前的 makefile 学习中,其⽬标⽂件(.o)只依赖于源⽂件(.c)。那么如果在源⽂件中还包含有头⽂件,此时编译器如何编译源⽂件和头⽂件呢?我们来看看编译⾏为带来的缺陷:1、预处理器将头⽂件中的代码直接插⼊源⽂件;2、编译器只通过预处理后的源⽂件产⽣⽬标⽂件;3、规则中以源⽂件为依赖,命令就可能⽆法执⾏。
我们来看看下⾯的 makefile 有没有问题
makefile 源码OBJS := func.o main.o
hello.out : $(OBJS)
@gcc -o $@ $^
@echo "Target File ==> $@"
$(OBJS) : %.o : %.c
@gcc -o $@ -c $^
func.h 源码#ifndef _FUNC_H_
#define _FUNC_H_
c语言编译器app怎么用#define HELLO "Hello D.T."
void foo();
#endif
func.c 源码#include
#include "func.h"
void foo()
{
printf("void foo() : %s\n", HELLO);
}
main.c 源码#include
#include "func.h"
int main()
{
foo();
return 0;
}
我们来看看编译结果
我们看到已经正确实现了字符串的打印,那么我们接下来在 func.h 源⽂件中想要改掉这个字符串为 Software 呢?试试看能不能修改成功
我们看到在重新编译的时候,它并没有因为头⽂件的改变⽽改变,我们在 makefile 中⼜没有进⾏头⽂件的相关添加,改掉头⽂件中的内容肯定是不动的。下来我们在模式规则中加上头⽂件,在 %.c 后加上 func.h,再来看看编译结果
我们看到直接添加之后,编译出错了。因为 -c 后⾯的⽬标中含有头⽂件,所以不能直接进⾏编译。我们可以只编译 %.o 后⾯的第⼀依赖%.c,这样就不会去编译 func.h 头⽂件了,将下⾯的 $^ 改为 $< ,我们来看看效果
我们看到已经正确改过来了。经过上⾯的实验,我们看到:头⽂件作为依赖条件出现于每个⽬标对应的
规则中,当头⽂件改动时,任何源⽂件都将会被重新编译(编译低效);当项⽬中头⽂件巨⼤时,makefile 将很难维护。那么我们的头脑中不禁会冒出这么个想法:通过命令对⾃动⽣成对头⽂件的依赖;将⽣成的依赖⾃动包含进 makefile 中;当头⽂件改动后,⾃动确认需要重新编译的⽂件。那么此时我们还需要知道⼀个命令,Linux 中的 sed 命令。sed 是⼀个流编辑器,⽤于流⽂本的修改(增、删、查、改);它可⽤于流⽂本中的字符串替换,其字符串替换⽅式为:sed 's:src:des:g',具体格式如下
sed 同样也⽀持正则表达式,在 sed 中可以⽤正则表达式匹配替换⽬标,并且可以使⽤匹配的⽬标⽣成替换结果。格式如下
下来我们以代码为例来看看 sed 命令是如何使⽤的
再来看看 gcc 关键编译选项,获取⽬标的完整依赖关系:gcc -M test.c;获取⽬标的部分依赖关系:gcc -MM test.c。makefile 如
下.PHONY : test
test :
gcc -M main.c
编译结果如下
我们看到 -M 是获取了它的所有依赖关系,再来试试 -MM 呢
我们看到 -MM 后,它只依赖与 main.c func.h。我们可以拆分⽬标的依赖,即将⽬标的完整依赖差分为多个部分依赖。格式如下
我们来做个实验.PHONY : a b c
test : a b
test : b c
test :
@echo "$^"
我们来打印看看⽬标 test 的依赖都有哪些,编译结果如下
那么我们思考下:如何将 sed 和 gcc -MM ⽤于 makefile,并⾃动⽣成依赖关系呢?
我们再来看看 makefile 中的 include 关键字,它类似于 C 语⾔中的 include,是将其它⽂件的内容原封不动的搬⼊当前⽂件。make 对include 关键字的处理⽅式是在当前⽬录下搜索或指定搜索⽬标⽂件。如果搜索⼀成功,便将⽂件内容搬⼊当前 makefile 中;如果搜索失败,将会产⽣警告,以⽂件名作为⽬标查并执⾏对应规则,当⽂件名对应的规则不存在时,最终产⽣错误。格式如下
下来还是以代码为例来进⾏说明.PHONY : test
all :
@echo "this is $@"
< :
@echo ""
@
我们在第 3 ⾏包含 ,可是当前⽬录下并没有 ,然后触发 的规则。因⽽会打印出 ,然后再创建 ,我们来看看编译结果
我们看到确实是创建了⼀个 ⽂件。那么在 makefile 中命令的执⾏是:1、规则中的每个命令默认是在⼀个新的进程中执⾏(Shell);2、可以通过接续符(;)将多个命令组合成⼀个命令;3、组合的命令
依次在同⼀个进程中被执⾏;4、set -e 指定发⽣错误后⽴即退出执⾏。那么我们看看下⾯的代码会实现想要的功能吗?.POHONY : all
all :
mkdir test
cd test
mkdir subtest
我们来看看编译结果
我们看到在当前⽬录下创建了⽬录,但是 subtest ⽬录却不是在 test ⽬录下创建的,这是怎么回事呢?在第⼀条命令执⾏时创建了⽬录test,此时这个进程已经关闭了;在第⼆条命令执⾏时,执⾏的是另⼀
个进程,虽然它已经进⼊到⽬录 test 中,但是随着这个进程的关闭,⼜回到了当前⽬录;第三个进程是重新创建了⽬录 subtest。那么如何解决这个问题呢?直接利⽤ set -e 和 接续符来解
决.PHONY : test
all :
set -e; \
mkdir test; \
cd test; \
mkdir subtest
看看编译结果
那么我们之前思考问题的初步思路是:1、通过 gcc -MM 和 sed 得到 .dep 依赖⽂件(⽬标的部分依赖),技术点是规则中命令的连续执⾏;2、通过 include 指令包含所有的 .dep 依赖⽂件。技术点是当 .dep 依赖⽂件不存在时,使⽤规则⾃动⽣成。下⾯我们来看看解决⽅案是怎样的ONY : all clean
MKDIR := mkdir
RM := rm -fr
CC := gcc
SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)
include $(DEPS)
all :
@echo "all"
%.dep : %.c
@echo "Creating $@ ..."
@set -e; \
$(CC) -MM -E $^ | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@
clean :
$(RM) $(DEPS)
我们来看看编译结果
我们先来分析下,在执⾏ make all 前,它先通过 include 包含 $(DEPS),通过 $(DEPS) 触发模式规则,进⽽创建⽂件夹。我们看到在前⾯出现两个没有⽂件夹的信息,其实这条信息是可以隐藏的。我们在 include 前⾯加上 - 就 OK,来看看效果