【Linux基础】make / Makefile的使用与编写(多文件的编写)

本文介绍了程序编译过程,特别是通过gcc指令和make/makefile实现自动化编译。通过一个.c文件的例子,详细解析了依赖关系与依赖方法,并展示了如何编写makefile文件。此外,提供了两个使用makefile的实例,演示了如何生成及清理目标文件。通过理解makefile,可以提升软件开发效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 前言

首先我们先简单介绍一下程序编译的过程:

对于一个.c文件,code.c-> bin(可执行程序) 的过程被称为程序的翻译(编译),有以下四个步骤:

  1. 预处理 (①头文件展开 ②去注释 ③宏替换 ④条件编译)
  2. 编译 (把C变成汇编语言
  3. 汇编 (把汇编语言变为.0目标二进制文件,且.o文件不可执行)
  4. 链接 (本质引入代码中使用的第三方库-c库)

在使用gcc指令时:
在这里插入图片描述


2. make/makefile

2.1 相关概念

  1. make 是一条命令,makefile 是一个文件。

  2. makefile 带来的好处就是——“自动化编译”。一旦写好,只需要一个 make 命令,整个工程就会完全自动编译,极大地提高了软件开发的效率。

  3. make 是一个命令工具,用于解释 makefile 中的指令。一般来说,大多数 IDE 都支持这个命令,例如 Delphi 的 make、Visual C++ 的 nmake 以及 Linux 下 GNU 的 make。由此可见,makefile 已成为一种广泛应用的工程编译方法。

  4. makefile 的本质是在编写依赖关系和依赖方法


2.2 关于 依赖关系 与 依赖方法

实例分析

对于下面的c代码:

#include <stdio.h>
int main()
{
 printf("hello Makefile!\n");
 return 0; 
}

其对应的makefile文件:

hello:hello.o
 gcc hello.o -o hello
hello.o:hello.s
 gcc -c hello.s -o hello.o
hello.s:hello.i
 gcc -S hello.i -o hello.s
hello.i:hello.c
 gcc -E hello.c -o hello.i .PHONY:clean
clean:
 rm -f hello.i hello.s hello.o hello

依赖关系

上面的文件 hello ,它依赖 hell.o
hello.o , 它依赖 hello.s hello.s , 它依赖 hello.i
hello.i , 它依赖 hello.c

依赖方法

gcc hello.* -option hello.* ,就是与之对应的依赖关系


2.3 makefile都有哪些成分

  1. 目标(Targets)变量定义

变量用于定义编译器、编译选项、源文件等,这样可以方便地在整个 Makefile 中使用和修改。

CC = gcc
CFLAGS = -Wall -I.
  • CC 定义了编译器。
  • CFLAGS 定义了编译选项,如警告选项和包含目录。

  1. 目标(Targets)

目标是 make 命令要生成的文件(例如可执行文件),或者是需要执行的命令(如清理中间文件)。目标通常有两个主要部分:目标名称和依赖文件。

all: main
  • all 是一个目标,表示生成 main

  1. 规则(Rules)

规则定义了如何从源文件生成目标。规则包括三部分:

  • 目标:要生成的文件名。
  • 依赖:生成目标所需的文件。
  • 命令:用于生成目标的命令,通常以制表符(Tab)开始。
main: main.o add.o sub.o
	$(CC) -o $@ $^
  • main 是目标,依赖于 main.oadd.osub.o
  • 命令 $(CC) -o $@ $^ 用于链接目标文件生成可执行文件。

  1. 模式规则(Pattern Rules)

模式规则用于定义如何生成一类目标。例如,下面的规则定义了如何将每个 .c 文件编译成 .o 文件:

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@
  • %.o 是目标模式。
  • %.c 是源文件模式。
  • $< 表示第一个依赖文件,$@ 表示目标文件。

  1. 伪目标(Phony Targets)

伪目标是不会生成实际文件的目标。它们用于执行特定命令,如清理构建文件。

.PHONY: clean
clean:
	rm -f *.o main
  • .PHONY 声明 clean 不是一个文件名,clean 目标会删除所有编译生成的文件。

  1. 示例 Makefile

结合以上部分,如果要编写以一个完整的makefile大概是下面的代码:

# 变量定义
CC = gcc
CFLAGS = -Wall -I.

# 源文件和目标文件
SRCS = main.c add.c sub.c mul.c div.c
OBJS = $(SRCS:.c=.o)
EXEC = main

# 默认目标
all: $(EXEC)

# 链接目标文件生成可执行文件
$(EXEC): $(OBJS)
	$(CC) -o $@ $^

# 编译每个源文件生成目标文件
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# 清理生成的文件
clean:
	rm -f $(OBJS) $(EXEC)

.PHONY: all clean

2.4 使用实例(通用写法)

首先创建Makefile/makefile文件(两者无区别)和所需要的多文件,
这里我们使用比较简单的例子

在这里插入图片描述

Version1:

在这里插入图片描述

此时执行后直接生成mybin,若想生成其他的临时文件可以仿照分析实例中makefile来写

此时我们的目录文件为
在这里插入图片描述
make后生成mybin文件
在这里插入图片描述
此时目录文件
在这里插入图片描述
make clean快速删除生成文件
在这里插入图片描述


Version2:

在这里插入图片描述
此时目录文件为
在这里插入图片描述
make后生成mybin和相应.o文件(.o是我们在Makefile中自己指定的,可以选择不生成或是生成别的)
在这里插入图片描述
make clean删除生成的文件
在这里插入图片描述

我们在使用make clean的时候,系统会把相应内容打印出来,如果不需要打印,在 clean: 下加一个 @即可
这里是引用
在这里插入图片描述

2.5 对于稍复杂的项目

如果遇到一些项目实际需要的可执行程序不止一个,在不同的文件夹下,应该怎么做?

我们来看下面的代码:

.PHONY: all
all:
	@cd compile_server;\
	make;\
	cd -;\
	cd oj_server;\
	make;\
	cd -;

.PHONY:output
output:
	@mkdir -p output/compile_server;\
	mkdir -p output/oj_server;\
	cp -rf compile_server/compile_server output/compile_server;\
	cp -rf compile_server/temp output/compile_server;\
	cp -rf oj_server/conf output/oj_server/;\
	cp -rf oj_server/lib output/oj_server/;\
	cp -rf oj_server/questions output/oj_server/;\
	cp -rf oj_server/template_html output/oj_server/;\
	cp -rf oj_server/wwwroot output/oj_server/;\
	cp -rf oj_server/oj_server output/oj_server/;

.PHONY:clean
clean:
	@cd compile_server;\
	make clean;\
	cd -;\
	cd oj_server;\
	make clean;\
	cd -;\
	rm -rf output;

下面来解释上面的代码:

当我们保证每个子项目内的代码结构没有问题且makefile正确编写的前提下:

all:这个目标在执行时会依次进入 compile_server 和 oj_server 目录,分别运行 make 命令来构建这两个子项目。

output:随后通过该目标创建输出目录,并将编译后的文件和其他必要的文件复制到 output 目录中。

最后可以通过clean一键将子项目生成的文件删除。


2.6 gcc 相关选项

我们在使用makefile进行程序编译的过程中,一般会使用gcc / g++,这里简单介绍一下其相关的选项,直接总结出下表:

选项用途示例
-o <file>指定输出文件的名称gcc hello.c -o hello
-c只进行编译,不进行链接,生成目标文件 .ogcc -c hello.c
-S生成汇编代码,输出 .s 文件gcc -S hello.c
-E只进行预处理,输出预处理后的源代码gcc -E hello.c
-O0关闭优化(适合调试)gcc -O0 hello.c -o hello
-O1基本优化gcc -O1 hello.c -o hello
-O2常见的优化级别,增加执行效率gcc -O2 hello.c -o hello
-O3最大化优化,可能增加代码大小,适用于性能敏感程序gcc -O3 hello.c -o hello
-Os优化代码以减少文件大小gcc -Os hello.c -o hello
-Ofast更激进的优化,可能违反标准以提高性能gcc -Ofast hello.c -o hello
-funroll-loops启用循环展开优化gcc -funroll-loops hello.c -o hello
-g生成调试信息,以便于使用 gdb 调试gcc -g hello.c -o hello
-ggdb生成 GDB 特定的调试信息gcc -ggdb hello.c -o hello
-Wall启用所有常见的警告gcc -Wall hello.c -o hello
-Werror将警告视为错误,编译时遇到警告则停止编译gcc -Wall -Werror hello.c -o hello
-D<macro>定义预处理宏gcc -DDEBUG hello.c -o hello
-U<macro>取消定义宏gcc -UDEBUG hello.c -o hello
-std=c99指定 C99 标准(C 语言标准)gcc -std=c99 hello.c -o hello
-std=c++11指定 C++11 标准(C++ 语言标准)g++ -std=c++11 hello.cpp -o hello
-fno-rtti禁用运行时类型识别(RTTI)g++ -fno-rtti hello.cpp -o hello
-fexceptions启用 C++ 异常处理g++ -fexceptions hello.cpp -o hello
-L<dir>指定库文件搜索目录gcc hello.c -L/usr/local/lib -lmylib -o hello
-l<library>链接指定的库文件g++ hello.cpp -o hello -lm
-static强制进行静态链接gcc -static hello.c -o hello
-shared生成共享库文件(动态库)gcc -shared -o libhello.so hello.c
-fPIC生成位置无关代码,通常用于生成共享库gcc -fPIC -shared -o libhello.so hello.c
-v显示详细的编译过程gcc -v hello.c -o hello
-pipe使用管道传输中间文件,加速编译过程gcc -pipe hello.c -o hello
-fno-asm禁用汇编代码生成gcc -fno-asm hello.c -o hello
-M生成 Makefile 所需的依赖文件gcc -M hello.c
-MM生成 Makefile 依赖,仅包含系统头文件依赖gcc -MM hello.c
-fstack-protector启用栈保护功能,防止栈溢出攻击gcc -fstack-protector hello.c -o hello
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值