【C/C++】从零开始的Make教程(上篇)

C/C++】从零开始的Make教程

如果你有过在linux系统上源码安装某款软件的经历,那么肯定对这三部曲一点都不会陌生——配置(configure)、编译(make)、安装(make install)。几乎每次都是机器人般的操作,这背后其实是make(准确地说应该是GNU Make)在默默为你干了不少活。

 

1.编译hello.c——单一源文件的编译

//hello.c
#include <stdio.h>

int main(){
    puts("hello, world!");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

为了编译生成对应的可执行文件,你可能会使用下面的命令:

$ cc -o hello hello.c
$ ./hello
hello, world!
  • 1
  • 2
  • 3

但是,如果使用make(前提是你的操作系统已经安装了GCC和GNU Make),会显得更清爽一些。

$ make hello
cc     hello.c   -o hello
$ ./hello
hello, world!
  • 1
  • 2
  • 3
  • 4

 

1.1编写Makefile

什么?你连“make hello”都懒得写?看完这部分,你的“妄念”应该就能实现了,到时候你只需要慢悠悠地打出4个字母——”make”,然后按下回车键,比图形界面IDE还要方便(至少你不用到处去找那个该死的“运行”按钮在哪。

  
这时候你只要在hello.c的同一个目录下新建一个文件Makefile作为make命令的配置文件即可。它的内容很简单:

hello:
  • 1

 

1.2设定编译器

什么?你不想使用默认的cc,而想使用gcc来编译程序?那还不简单,只用在Makefile文件中把CC变量的值赋为gcc就可以了。

CC := gcc

hello:
  • 1
  • 2
  • 3

如果你这时候想运行make试下效果,请注意:make根本就不会重新编译生成hello。为什么啊?因为make很“懒”,因为它检测到hello.c和上一次编译时一模一样,再重新编译生成的可执行文件肯定也一样啊,那就没有运行的必要了,直接返回结果了。这时候可以用上些“小手段”,反正make很好骗。输入下面的命令,更新下hello.c的最近修改日期。

$ touch hello.c
  • 1

或者干脆直接把hello文件删掉。但是删文件的方式也有高招和低招之分,如果你使用的是下面的命令:

$ rm -f hello
  • 1

那么这就是低招了,因为这很可能误删了其他很重要的源文件,造成十分严重的后果。那么高招是什么呢?那就是在Makefile中添加下面的内容:

clean:
    $(RM) hello
  • 1
  • 2

运行方式也很简单,运行make clean命令即可。

 

1.3增加编译选项

如果你想为gcc增加-g -Wall -Wextra选项,那么只要设定变量CFLAGS的值即可。

CC := gcc
CFLAGS := -g -Wall -Wextra

hello:

clean:
    $(RM) hello
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

这时候,运行make clean和make的结果如下所示:

$ make clean
rm -f hello
$ make
gcc -g -Wall -Wextra    hello.c   -o hello
  • 1
  • 2
  • 3
  • 4

 

2.分块编译——编译有多个源文件的程序

如果程序不再只有一个源文件,那么结合Make的内置编译规则,也可以很简洁地编写Makefile文件完成编译任务。下面是一个简单的例子:

LDLIBS := -lncurses

block: block.o function.o

block.o function.o: function.h

clean:
    $(RM) *.o
    $(RM) block
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中程序需要用到ncurses,它是一个字符终端下屏幕控制的基本库,因此在编译时需要在最后增加-lncurses选项。这时候,可能你已经发现了,其实编写Makefile主要内容就是编写依赖关系,block: block.o function.o就是表示由block.o和function.o链接生成可执行文件block。同时bolck.ofunction.o就是根据需要从bolck.cfunction.c编译生成,因为make有如下内置规则:*.o由同名的c源文件生成,因此不必写多余的bolck.o:bolck.cfunction.o:function.c。运行结果如下

$ make
cc    -c -o block.o block.c
cc    -c -o function.o function.c
cc   block.o function.o  -lncurses -o block
  • 1
  • 2
  • 3
  • 4

 

2.Make的内置规则

输入make -p命令,可以查看所有的make的内置规则,比如上面提到的*.o由同名的c源文件生成,在make -p的输出结果中显示如下:

%.o: %.c
#  recipe to execute (内置):
    $(COMPILE.c) $(OUTPUT_OPTION) $<
  • 1
  • 2
  • 3

其中,%为通配符,$(COMPILE.c)是取COMPILE.c这个变量的值,如果你好奇这个值到底是什么,可以再继续查找,发现下面的语句

# 默认
COMPILE.C = $(COMPILE.cc)
  • 1
  • 2

在进一步找,得到:

# 默认
COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
  • 1
  • 2

上面的变量可以像之前的实例那样重新赋值(默认为空),从而自定义编译方式。

 

自动变量

除了像$(COMPILE.c)和$(COMPILE.cc)这样形式的变量,make中还存在一类很常用、无比重要的变量——自动变量。 
下面列出最常用的自动变量:

  • $@ 规则的生成目标
  • $% 档案文件成员结构中的文件名元素
  • $< 第一个依赖文件名
  • $^ 所有的依赖文件名(已经消重),以空格分隔
  • $+ 所有的依赖文件名(未经消重),以空格分隔
  • $* 所有除掉后缀的依赖文件名,以空格分隔,仅适用于模式规则。注:文件名包含stem和suffix,去掉suffix就剩下了stem。比如hello.cpp的stem是hello,suffix就是cpp。
  • $? 比目标文件新的依赖文件。

比如下面这条规则:

 %: %.c
#  commands to execute (built-in):
    $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
  • 1
  • 2
  • 3

LINK.c的定义如下:

LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
  • 1

CC的定义如下:

CC = cc
  • 1

而且CFLAGS、CPPFLAGS、LDFLAGS、TARGET_ARCH默认都为空。 
最后原来的规则等同于:

%: %.c
    cc $^   -o $@
  • 1
  • 2

所以本文开头的编译hello,只需简简单单的Makefile:

hello:
  • 1

就可得到最终的编译命令:

cc hello.c   -o hello
  • 1

所以,参考make -p命令输出的make内置规则,编写自己程序的Makefile是个不错的编程习惯和学习如何熟练使用Make的途径。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gg_18826075157/article/details/72780010
阅读更多
个人分类: c++
上一篇CMake简要教程,什么是 CMake,【C/C++】从零开始的cmake教程
下一篇【C/C++】从零开始的cmake教程(下篇)
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭