之前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工具规定的。另外还有CXXFLAGS
、CPPFLAGS
,前者用来给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),最后的
但是还是麻烦,所以我们进一步“抽象”,把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