利用Makfile给多文件、多目录C源码建立工程

系统 Linux
我们将该项目的所有功能函数放到以该函数名命名的c文件,同时放到对应名称的子目录中。比如函数allfree(),存放到 allfree/allfree.c中。

[[429525]]

0. 前言

粉丝留言,想知道如何使用Makefile给多个文件和多级目录建立一个工程,必须安排!

关于Makefile的入门参考文章,可以先看这篇文章:

《Makefile入门教程》

为了让大家有个更加直观的感受,一口君将之前写的一个小项目,本篇在该项目基础上进行修改。

该项目详细设计和代码,见下文:

《从0写一个《电话号码管理系统》的C入门项目【适合初学者】》

一、文件

好了,开始吧!

我们将该项目的所有功能函数放到以该函数名命名的c文件,同时放到对应名称的子目录中。

比如函数allfree(),存放到 allfree/allfree.c中

最终目录结构如下图所示:

  1.  peng@ubuntu:/mnt/hgfs/code/phone$ tree . 
  2. ├── allfree 
  3. │   ├── allfree.c 
  4. │   └── Makefile 
  5. ├── create 
  6. │   ├── create.c 
  7. │   └── Makefile 
  8. ├── delete 
  9. │   ├── delete.c 
  10. │   └── Makefile 
  11. ├── display 
  12. │   ├── display.c 
  13. │   └── Makefile 
  14. ├── include 
  15. │   ├── Makefile 
  16. │   └── phone.h 
  17. ├── init 
  18. │   ├── init.c 
  19. │   └── Makefile 
  20. ├── login 
  21. │   ├── login.c 
  22. │   └── Makefile 
  23. ├── main 
  24. │   ├── main.c 
  25. │   └── Makefile 
  26. ├── Makefile 
  27. ├── menu 
  28. │   ├── Makefile 
  29. │   └── menu.c 
  30. ├── scripts 
  31. │   └── Makefile 
  32. └── search 
  33.     ├── Makefile 
  34.     └── search.c 
  35.  
  36. 11 directories, 22 files 

直接看下编译结果吧:

  1. peng@ubuntu:/mnt/hgfs/code/phone$ make 
  2. make[1]: Entering directory '/mnt/hgfs/code/phone/allfree' 
  3. make[1]: Nothing to be done for 'all'
  4. make[1]: Leaving directory '/mnt/hgfs/code/phone/allfree' 
  5. make[1]: Entering directory '/mnt/hgfs/code/phone/create' 
  6. make[1]: Nothing to be done for 'all'
  7. make[1]: Leaving directory '/mnt/hgfs/code/phone/create' 
  8. make[1]: Entering directory '/mnt/hgfs/code/phone/delete' 
  9. make[1]: Nothing to be done for 'all'
  10. make[1]: Leaving directory '/mnt/hgfs/code/phone/delete' 
  11. make[1]: Entering directory '/mnt/hgfs/code/phone/display' 
  12. make[1]: Nothing to be done for 'all'
  13. make[1]: Leaving directory '/mnt/hgfs/code/phone/display' 
  14. make[1]: Entering directory '/mnt/hgfs/code/phone/init' 
  15. make[1]: Nothing to be done for 'all'
  16. make[1]: Leaving directory '/mnt/hgfs/code/phone/init' 
  17. make[1]: Entering directory '/mnt/hgfs/code/phone/login' 
  18. make[1]: Nothing to be done for 'all'
  19. make[1]: Leaving directory '/mnt/hgfs/code/phone/login' 
  20. make[1]: Entering directory '/mnt/hgfs/code/phone/menu' 
  21. make[1]: Nothing to be done for 'all'
  22. make[1]: Leaving directory '/mnt/hgfs/code/phone/menu' 
  23. make[1]: Entering directory '/mnt/hgfs/code/phone/search' 
  24. make[1]: Nothing to be done for 'all'
  25. make[1]: Leaving directory '/mnt/hgfs/code/phone/search' 
  26. make[1]: Entering directory '/mnt/hgfs/code/phone/main' 
  27. make[1]: Nothing to be done for 'all'
  28. make[1]: Leaving directory '/mnt/hgfs/code/phone/main' 
  29. gcc -Wall -O3 -o phone allfree/*.o create/*.o delete/*.o display/*.o init/*.o login/*.o menu/*.o search/*.o main/*.o -lpthread 
  30. phone make done!  

运行结果如下:

二、Makefile常用基础知识点

[0] 符号'@' '$' '$$' '-' '-n '的说明

1.'@'

通常makefile会将其执行的命令行在执行前输出到屏幕上。如果将‘@’添加到命令行前,这个命令将不被make回显出来。例如:

  1. @echo  --compiling module----;  // 屏幕输出  --compiling module---- 
  2. echo  --compiling module----;  // 没有@ 屏幕输出echo  --compiling module----    

2.' - '

通常删除,创建文件如果碰到文件不存在或者已经创建,那么希望忽略掉这个错误,继续执行,就可以在命令前面添加 -,

  1. -rm dir; 
  2. -mkdir aaadir; 

3.' $ '美元符号$,主要扩展打开makefile中定义的变量

4.' $$ '$$ 符号主要扩展打开makefile中定义的shell变量

[1] wildcard

说明: 列出当前目录下所有符合模式“ PATTERN”格式的文件名,并且以空格分开。“ PATTERN”使用shell可识别的通配符,包括“ ?”(单字符)、“ *”(多字符)等。示例:

  1. $(wildcard *.c)  

返回值为当前目录下所有.c 源文件列表。

[2] patsubst

说明:把字串“ x.c.c bar.c”中以.c 结尾的单词替换成以.o 结尾的字符。示例:

  1. $(patsubst %.c,%.o,x.c.c bar.c) 

函数的返回结果 是

  1. x.c.o bar.o 

[3] notdir

说明:去除文件名中的路径信息 示例:

  1. SRC = ( notdir ./src/a.c )  

去除文件a . c 的路径信息 , 使用 (notdir ./src/a.c) 去除文件a.c的路径信息,使用 (notdir./src/a.c)去除文件a.c的路径信息,使用(SRC)得到的是不带路径的文件名称,即a.c。

[4] 包含头文件路径

使用-I+头文件路径的方式可以指定编译器的头文件的路径 示例:

  1. INCLUDES = -I./inc 
  1. $(CC) -c $(INCLUDES) $(SRC) 

[5] addsuffix

函数名称:加后缀函数—addsuffix。语法:

  1. $(addsuffix SUFFIX,NAMES…)  

函数功能:为“NAMES…”中的每一个文件名添加后缀“SUFFIX”。参数“NAMES…” 为空格分割的文件名序列,将“SUFFIX”追加到此序列的每一个文件名 的末尾。返回值:以单空格分割的添加了后缀“SUFFIX”的文件名序列。函数说明:示例:

  1. $(addsuffix .c,foo bar)  

返回值为

  1. foo.c bar.c 

[6] 包含另外一个文件:include

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。比如命令

  1. include file.dep 

即把file.dep文件在当前Makefile文件中展开,亦即把file.dep文件的内容包含进当前Makefile文件

在 include前面可以有一些空字符,但是绝不能是[Tab]键开始。

[7] foreach

foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的 语法是:

  1. $(foreach <var>,<list>,<text> ) 

这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。

每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

所以,最好是一个变量名,可以是一个表达式,而中一般会使用这个参数来依次枚举中的单词。

举例:

  1. names := a b c d 
  2. files := $(foreach n,$(names),$(n).o) 

上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。

注意,foreach中的参数是一个临时的局部变量,foreach函数执行完后,参数的变量将不在作用,其作用域只在foreach函数当中。

[8] call

“ call”函数是唯一一个可以创建定制化参数函数的引用函数。使用这个函数可以实现对用户自己定义函数引用。我们可以将一个变量定义为一个复杂的表达式,用“ call”函数根据不同的参数对它进行展开来获得不同的结果。

函数语法:

  1. $(call variable,param1,param2,...) 

函数功能:在执行时,将它的参数“ param”依次赋值给临时变量“ $(1)”、“ $(2)” call 函数对参数的数目没有限制,也可以没有参数值,没有参数值的“ call”没有任何实际存在的意义。执行时变量“ variable”被展开为在函数上下文有效的临时变量,变量定义中的“ $(1)”作为第一个参数,并将函数参数值中的第一个参数赋值给它;变量中的“ $(2)”一样被赋值为函数的第二个参数值;依此类推(变量**$(0)**代表变量“ variable”本身)。之后对变量“ variable” 表达式的计算值。

返回值:参数值“ param”依次替换“ $(1)”、“ $(2)”…… 之后变量“ variable”定义的表达式的计算值。

函数说明:

  1. 函数中“ variable”是一个变量名,而不是变量引用。因此,通常“ call”函数中的“ variable”中不包含“ $”(当然,除非此变量名是一个计算的变量名)。
  2. 当变量“ variable”是一个 make 内嵌的函数名时(如“ if”、“ foreach”、“ strip”等),对“ param”参数的使用需要注意,因为不合适或者不正确的参数将会导致函数的返回值难以预料。
  3. 函数中多个“ param”之间使用逗号分割。
  4. 变量“ variable”在定义时不能定义为直接展开式!只能定义为递归展开式。

函数示例:

  1. reverse = $(2)$(1) 
  2. foo = $(call reverse,a,b) 
  3. all
  4.  @echo "foo=$(foo)" 

执行结果:

  1. foo=ba 

即a替代了替代了(2)

三、编译详细说明

我们在根目录下执行make命令后,详细步骤如下:

1.include scripts/Makefile :将文件替换到当前位置,

2.使用默认的目标all,该目标依赖于$(Target)$(Target) 在scripts/Makefile中定义了,即phone

3.而$(Target)依赖于mm

4.mm这个目标会执行

  1. @ $(foreach n,$(Modules),$(call modules_make,$(n))) 

Modules是所有的目录名字集合, foreach 会遍历字符串$(Modules)中每个词语, 每个词语会赋值给n, 同时执行语句:

  1. call modules_make,$(n) 

5.modules_make 被$(MAKE) -C $(1)所替代,

$(MAKE) 有默认的名字make -C:进入子目录执行make$(1) :是步骤4中$(n),即每一个目录名字

最终步骤4的语句就是进入到每一个目录下,执行每一个目录下的Makefile

6.进入某一个子目录下,执行Makefile 默认目标是all,依赖Objs

  1. Objs := $(patsubst %.c,%.o,$(Source)) 

patsubst 把字串$ource中以.c 结尾的单词替换成以.o 结尾的字符 而

  1. Source := $(wildcard ./*.c) 

wildcard 会列举出当前目录下所有的.c文件

所以第6步最终就是将子目录下的所有的.c文件,编译生成对应文件名的.o文件

  1. $(CC) $(CFLAGS) -o $(Target) $(AllObjs) $(Libs) 

这几个变量都在文件scripts/Makefile中定义$(CC) :替换成gcc,制定编译器$(CFLAGS) :替换成-Wall -O3,即编译时的优化等级-o $(Target):生成可执行程序phone$(AllObjs) :

  1. AllObjs := $(addsuffix /*.o,$(Modules)) 

addsuffix 会将 /*.o追加到$(Modules)中所有的词语后面,也就是我们之前在子目录下编译生成的所有的.o文件$(Libs) :替换为-lpthread,即所需要的动态库

大家可以根据这个步骤,来分析一下执行make clean时,执行步骤

本文转载自微信公众号「一口Linux」

 

责任编辑:姜华 来源: 一口Linux
相关推荐

2020-03-18 12:23:49

C语言编程语言

2020-03-17 17:49:58

C语言编程语言

2024-08-20 07:55:03

2024-01-16 11:43:38

C++HashMap

2010-08-26 13:17:27

DHCP服务器

2021-12-15 11:30:04

无人驾驶智能技术

2009-02-03 09:48:00

DHCPVlAN

2021-04-12 18:14:56

鸿蒙HarmonyOS应用开发

2010-01-15 18:06:58

CentOS文件

2022-04-08 07:52:00

架构多机房多活

2009-06-16 10:20:05

多继承C#

2024-12-06 15:11:34

Python文件夹目录

2009-08-07 13:38:18

C#文件相对路径

2019-07-04 14:40:28

FacebookRAT恶意软件

2012-05-08 13:24:45

负载均衡带宽锐捷网络

2020-10-23 10:10:59

Promise前端代码

2018-08-20 16:00:23

MySQL并发控制MVCC

2009-05-26 12:23:56

Linux变量文件命名

2022-03-28 10:25:27

前端文件编译器

2017-09-08 15:34:01

点赞
收藏

51CTO技术栈公众号