「Tech初见」简单实用Makefile教程

#Motivation

在我们初学编程时,通常只写一个 .c 文件就能完成相应的计算(力扣 or 牛客算法题)。此时要运行写好的程序,有两种方式,

  1. 借助 IDE ,比如 Visual Studio ,点击运行按钮,自动帮我们编译,之后顺利运行
  2. 文本编辑器 + GCC ,通常是在 Linux 平台下,这时候就需要我们自己手动编译。但也就一条 gcc 命令就能搞定,
gcc nowcoder01.c -o newcoder01

上述是初学的状态,工程量很小,涉及到的文件也不多,编译也不耗时。所以,这样做完全没有问题

但是,如果工程量变得很大,工程有很多 .c 和 .h 文件。此时,再采用手动输入 gcc 编译命令的方式,效率就未免太低下了。理由我认为主要有两点,

  1. 每次重新编译都要在终端中输入冗长的编译命令且易出错。设想工程中有7个 .c 和 2个 .h ,那么每次的编译命令长这样,
gcc main.c create.c read.c update.c delete.c data.c
  1. 每次的命令输入都会将所有文件都重新编译一次,哪怕只修改了一个文件(比如,仅仅修改了 create.c ,但却把其他6个 .c 一并也重新编译了)。这样做,很耗时。最正常的想法,是没改动的文件就不需要,也不应该被重新编译

基于以上两点,提出了 make 的想法。在 Windows 平台下,Visual C++ 帮我们做了此类自动化编译的工作。但在 Linux 下,就没有这种好事了,需要我们自己手写一个叫做 Makefile 的文件,来间接地完成自动化编译工作

#Solution

我们都知道,程序从 .c 到可执行,这期间有两大步要走。这第一步,就是要将 .c 转为 .obj ,这个过程叫做编译( complie );这第二步,需将生成的 .obj 中一些函数和全局变量重定位,这叫链接( link )。完事之后,才能翻译成可执行的二进制代码


在清楚基本编译链接常识之后,就可以着手开始编写 Makefile 啦

假设,现在工程里有7个 .c 和 2个 .h ,分别为程序的入口 main.c ,业务模块 create.c 、read.c 、update.c 和 delete.c ,数据模块 data.c 及 data.h ,还有掌管绝大多数变量和函数声明的 defs.h 和工具包 utils.c

各文件之间的关系,大概就是,main.c 会调用业务模块的各个功能,而业务模块又依赖于 data.h 中定义的数据结构。当然啦,要想程序正常编译运行,自然离不开 defs.h 。有些功能还会需要 utils.c

这里姑且贴下各模块文件的大致代码,待会写 Makefile 时也好心中有数。代码在文末,可查阅


好,文件依赖关系分析清楚之后,就可以动手开始写 Makefile 了。但是,在此之前,还要注意两点,

  1. Makefile 的书写基本规则
target ... : prerequisites ...
	command
	...

其中 target 是目标文件,可以是 Object File ,也可以是可执行文件。prerequisites 是生成 target 所需的文件。command 就是 make 要执行的命令,请注意 command 在第二行,它不能顶格写,要 Tab 回退才行

  1. GCC 的编译选项
gcc -o testmk main.o ...

-o 可以理解成链接命令,把后面跟着的所有 .o 重定向,生成 testmk 可执行文件

gcc -c main.c

-c 可以理解成编译命令,将 main.c 翻译成 Object File

#Example 1

注意点就如以上这么多,下面开始书写第一版本(入门级),

# 第一版本(入门级)
testmk : main.o utils.o data.o \
	create.o read.o update.o delete.o
	gcc -o testmk main.o utils.o data.o \
		create.o read.o update.o delete.o

main.o : main.c defs.h
	gcc -c main.c

utils.o : utils.c defs.h
	gcc -c utils.c

data.o : data.c data.h
	gcc -c data.c

create.o : create.c defs.h
	gcc -c create.c

read.o : read.c defs.h
	gcc -c read.c

update.o : update.c defs.h
	gcc -c update.c

delete.o : delete.c defs.h
	gcc -c delete.c

clean :
	rm testmk main.o utils.o data.o \
		create.o read.o update.o delete.o

Makefile 一开始最好就要表明最终需要的可执行文件 testmk 的依赖关系,从上面的代码可以看出 testmk 需要 main.o 等全部 Object Files 。其中 \ 表示换行符,紧接着就是输入命令,我们是想通过链接所有的 Object Files 生成可执行文件,所以用,

gcc -o testmk main.o utils.o data.o \
		create.o read.o update.o delete.o

往下的各个 target 都是 Object Files ,生成 Object File 需要 .c or .h 等文件依赖,这个很容易理解(自己可以细细体会)

main.o : main.c defs.h
	gcc -c main.c

最后的 clean 是 Makefile 一般都会有的一个环节,通常也是放在文件的最后。主要是用来清除之前生成的 Object Files 和可执行文件

如此写完,其实已经可以正常工作了。但是,这种写法还是存在很多问题的。比如,没有将众多 Object Files 合并管理,当 Object File 越来越多时,Makefile 也更难修改

#Example 2

针对 Example 1 的缺点,想出了第二版本(中级版),

# 第二版本(中级版)
OBJ = main.o utils.o data.o \
	create.o read.o update.o delete.o

testmk : $(OBJ)
	gcc -o testmk $(OBJ)

main.o : main.c defs.h
	gcc -c main.c

utils.o : utils.c defs.h
	gcc -c utils.c

data.o : data.c data.h
	gcc -c data.c

create.o : create.c defs.h
	gcc -c create.c

read.o : read.c defs.h
	gcc -c read.c

update.o : update.c defs.h
	gcc -c update.c

delete.o : delete.c defs.h
	gcc -c delete.c

clean :
	rm testmk $(OBJ)

这就很好的管理了众多的 Object Files ,用 OBJ 标签来替代。但是,我们还是觉得有提升的空间,从何处突破呢?

在编译各个 .c 和 .h 时,之前的两种写法都是把依赖关系写得很清晰。其实大可不必,因为 Makefile 会自己帮我们找与 Object File 同名的 .c 和 .h 作为依赖关系。所以,写法还可以简化

#Example 3

第三版本(精简版),

# 第三版本(精简版)
OBJ = main.o utils.o data.o \
	create.o read.o update.o delete.o

testmk : $(OBJ)
	gcc -o testmk $(OBJ)

main.o : defs.h
utils.o : defs.h
create.o : defs.h
read.o : defs.h
update.o : defs.h
delete.o : defs.h

clean :
	rm testmk $(OBJ)

这就省去了各个同名的 .c 文件,没有出现 data.o 的原因,是 data.o 仅仅依赖于 data.c 和 data.h ,这都是同名

以上就完成了 Makefile 大致的基本写法

#Source

如需深入学习 Makefile ,了解其更多的细节,可以查阅资料,

#Code

/** data.h */ 
typedef int pkey_t;

typedef struct {
    int pkey;
} data_t;

#define DATASIZE 1024

/** data.c */ 
#include "data.h"

data_t datas[DATASIZE];

/** defs.h */ 
#include "data.h"

#define IN
#define OUT

// utils.c
void	  func();

// create.c
int     create_table(IN data_t*, OUT data_t**);

// read.c
int     read_data(IN pkey_t, IN data_t*, OUT data_t*);

// update.c
int     update_data(IN pkey_t, IN data_t*, IN data_t*);

// delete.c
int     delete_data(IN pkey_t, IN data_t*);

/** utils.c */
#include <stdio.h>
#include "defs.h"

void func()
{
    printf("in func\n");
    printf("out func\n");
}

/** create.c */
#include "defs.h"

int 
create_table(IN data_t* data, OUT data_t** tbl)
{
    // 新建tbl
    // 并把data塞到tbl中
}

/** read.c */
#include "defs.h"

int
read_data(IN pkey_t pkey, IN data_t* tbl, OUT data_t* result)
{
    // 在tbl中寻找主键为pkey的data
    // 并将其写回至result中
}

/** update.c */
#include "defs.h"

int     
update_data(IN pkey_t pkey, IN data_t* new_data, IN data_t* tbl)
{
    // 先在tbl中定位到主键为pkey的data
    // 然后将其替换为new_data
}

/** delete.c */
#include "defs.h"

int
delete_data(IN pkey_t pkey, IN data_t* tbl)
{
    // 在tbl中定位到主键为pkey的data
    // 并删除
}

/** main.c */ 
#include <stdio.h>
#include "defs.h"

extern data_t datas[];

int main()
{
    // 抽象逻辑,别当真
    // 悟出其中的道,就可以啦
    printf("in main\n");
    // func();
    // create_table(...);
    // read_data(...);
    // update_data(...);
    // delete_data(...);
    printf("out main\n");
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
回答: makefile是一种用于自动化编译的工具,它定义了一系列规则来指定源文件的编译顺序和操作。在Unix下的软件编译中,makefile起着至关重要的作用。一个好的程序员应该了解makefile的含义和使用方法,因为它关系到整个工程的编译规则。makefile可以指定哪些文件需要先编译,哪些文件需要后编译,甚至可以执行操作系统的命令。通过make命令,整个工程可以自动编译,大大提高了软件开发的效率。make是一个命令工具,用于解释makefile中的指令。在不同的开发环境中,可以使用不同的make命令,比如Delphi的make、Visual C++的nmake和Linux下的GNU make。因此,掌握makefile的知识对于完成大型工程具备重要的能力。\[3\] #### 引用[.reference_title] - *1* *2* [Makefile教程](https://blog.csdn.net/qq_43603677/article/details/125430488)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Makefile教程(绝对经典,所有问题看这一篇足够了)](https://blog.csdn.net/weixin_38391755/article/details/80380786)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值