之前GCC那部分我提到过,gcc啥啥啥啥傻傻的那个指令只能够编译简单的代码,如果要干大事(例如突然心血来潮写个c开头的神经网络库之类的),还是要写Makefile来编译。其实在Windows下通常用IDE,例如Visual Studio,那个所谓的“项目”就有点像Makefile。通常Windows系统下这类IDE会自动帮你配置了编译时需要的东西,而Linux环境下我们需要自己来写Makefile来实现IDE的效果,听起来会麻烦点,实际上掌握了技巧之后就那样。

这部分实验指导书里面写的东西不多,但是我觉得有必要详细拿出来讲讲,毕竟很有用。

前提与假设

这里假设使用的make是GNU的make(不同厂商的make对应的makefile写法不一样,make可以理解为根据makefile来编译链接程序的工具)。下面我们通过一个简单的例子来看makefile的具体作用、功能,以及使用方式。

简单的例子

假设当前目录下有以下文件:

当你想要编译的时候,就会使用下面的指令:

gcc -o hellomake hellomake.c hellofunc.c -I.

上面指令就是指定输出结果名字为hellomake,编译两个*.c文件,而-I用来高数gcc在同一个目录下寻找用到的链接文件(.h头文件),其实这个选项还可以指定gcc去别的目录下寻找别的库,详情可以看这里。上面的-I.不是误写,不要忽略掉.,这是指定在当前目录搜索链接库的意思。

但是这只是在写小工程的时候才可以这么干,如果你要编译一个有20+个.c文件,链接需要30+个库的工程呢?很显然直接使用gcc相关指令来编译是不现实的,而且每次编译都要重新输入那么一长串指令,很麻烦。那么,我们这时候想到的第一个办法就是,直接将这个指令保存为一个文件,用的时候直接复制出来运行不就简单很多了?

而makefile恰好就有这个功能,你只需要将这个指令直接输入到makefile中,在直接用到的时候直接使用指令make,工具make就会直接帮你运行makefile中的这个命令。此时makefile应该这样写:

hellomake: hellomake.c hellofunc.c hellomake.h
gcc -o hellomake hellomake.c hellofunc.c -I.

当直接使用指令make并且没有提供任何参数的时候,make就会直接执行第一条规则,因为我们这里只有一条,所以使用指令make就会直接执行我们想要让他执行的编译指令了。注意gcc前面有一个tab,不能少了这个tab

既然提到了规则,那么就介绍下makefile中的规则写法:

在规则中,如果是第一次执行规则或者执行规则对应的prerequisites中的文件被更新了,那么在执行规则的时候才会运行规则对应的command,否则就不会执行。例如已经执行过一次hellomake规则了,如果没有更改hellomake.c、hellofunc.c、hellomake.h的话,那么再执行一次hellomake规则是不会执行下面的gcc ...指令的;如果更改了hellomake.c,例如修改了printf里面的内容,那么再执行一次make,就会调用规则对应的指令gcc ...,更新编译出来的程序。

基本上到这里就可以明白Makefile的基本规则了,后面的就基本上是锦上添花的功能而已。

引入变量

假设我们突然想要换一个编译器,或者改一改编译时候的参数设置,那么我们最好定义一些变量来实现这样的功能,直接上makefile内容:

CC=gcc
CFLAGS=-I. hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o

这个还是很容易懂得,可能你会觉得CFLAGS那里有点奇怪,其实这是默认给C编译器(这里是gcc)指定额外配置用的,是make工具规定的。另外还有CXXFLAGSCPPFLAGS,前者用来给C++编译器指定额外参数,后者同时给C和C++编译器指定额外参数。不过有些时候CPPFLAGS只给C++编译器指定额外参数,所以具体情况还是要具体分析。注意-o后面的名字,一定是.o的,这和上面有点不一样,但是结果和上面一样。之所以放在规则里面(prerequisites部分)以及command里面,是因为这样可以让make知道在编译出hellomake之前要先编译后面的.o文件对应的.c部分,即能够让编译器理解它们之间的依赖关系。

但是这个makefile其实有一个问题,那就是如果修改了.h文件,那么再一次make的时候是不会编译的,因为make此时没有追踪相关的.h文件的变化。所以我们需要加上相关的规则来实现对.h文件的追踪。

CC=gcc
CFLAGS=-I.
DEPS = hellomake.h %.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS) hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o

重点在那个DEPS和后面的%.o ...那部分,主要讲解%.o这部分规则。首先,我们这里定义的是一个适用于所有.o为结尾的规则,我们将对应的.c结尾的文件二号$(DEPS)对应的文件放在prerequisites那部分,这样make就会去追踪这些文件的变化。最后在command部分,-c意思是让编译器编译出.o文件,-o $@意思是将编译出来的文件用规则左侧的名字规则来命名(例如hellomake.o),最后的$<指的是DEPS中的第一个文件,CFLAGS用来指示额外的参数。

但是还是麻烦,所以我们进一步“抽象”,把hellomake那一条规则改改

CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o %.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS) hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)

$@代表:左边,$^代表右边(看键盘上的位置就知道哪个左哪个右了,这个不用记)。这样我们只需要改OBJ就可以应付更加复杂的项目了。

引入目录

但是上面那样虽然makefile看起来还好,在项目的目录里面就显得比较杂乱,各种头文件和源代码混杂在一起显得比较没条理,所以我们通常都会将头文件集中放在一个文件夹里面,将源代码集中放在一个文件夹里面。

IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR) ODIR=obj
LDIR =../lib LIBS=-lm _DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) _OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) $(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS) hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS) .PHONY: clean clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~

注意,这个makefile文件时放在项目文件夹下的src文件夹里面的。大概是xx(项目名)/src这样子。LIBS用来引入其他的库(通常安装在系统中,这里可以直接引用),例如这里用了-lm来引用数学的库。patsubst用来替换通配符,即变量中的%,最终会被替换成具体的文件名。

.PHONY用来避免文件使用make clean的时候真的去make一个名为clean的文件,用来确保确实跑的是定义好了的clean规则。这里的clean规则用来删掉编译出来的东西。其他的基本和上面的一样,所以就不多说了。

大概就这些,其实这个要深入进去的话还有很多可以说的,但是今天就记到这里。

参考

跟我一起写 Makefile(一)

A Simple Makefile Tutorial

Makefile cheatsheet速成用的cheatsheet,不过不建议一开始就看这个

CFLAGS, CCFLAGS, CXXFLAGS - what exactly do these variables control?

Makefile之patsubst

最新文章

  1. asp.net Application、 Session、Cookie、ViewState、Cache、Hidden 的区别
  2. nginx android app 慢网络请求超时
  3. eclipse美化
  4. BurpSuite之HTTP brute暴力破解
  5. centos7配置mono和jexus5.6.2
  6. Eclipse设置、调优、使用
  7. Design Thinking BrainWalk
  8. 本教程针对HBuilder5.0.0,制作日期2014-12-31(从HBuilder工具上获得)
  9. js继承之组合继承(结合原型链继承 和 借用构造函数继承)
  10. linux系统下完全卸载Jenkins
  11. zabbix监控实战&lt;3&gt; 之自定义监控实例
  12. UVA 10815 Andy&#39;s First Dictionary【set】
  13. spark Kryo serialization failed: Buffer overflow 错误
  14. Android-Kotlin-set/get方法的使用
  15. Google的开源C++单元测试框架Google Test
  16. 例说hg(五)————创建repository
  17. Spring Data JPA之@Query注解
  18. MysqL中的Show Index From Table_Name命令说明
  19. 【并发编程】Executor架构介绍
  20. ZOJ 3256 Tour in the Castle 插头DP 矩阵乘法

热门文章

  1. 22_3mybaits——连接池
  2. Atcoder Regular 098 区间Pre=Xor Q询问区间连续K去最小值最小极差
  3. mysql主从库配置读写分离以及备份
  4. 重大更新:DeepFaceLab更新至2019.12.20
  5. 枚举类enum应用以及注解@transient应用
  6. 安装python3之后,yum用不了
  7. day02 计算机基础部分作业题
  8. HBase 权限控制
  9. F12谷歌开发者工具preserve log
  10. 上传200G文件