文章目录
1. 前言
首先我们先简单介绍一下程序编译的过程:
对于一个.c文件,code.c-> bin(可执行程序)
的过程被称为程序的翻译(编译),有以下四个步骤:
- 预处理 (①头文件展开 ②去注释 ③宏替换 ④条件编译)
- 编译 (把C变成汇编语言
- 汇编 (把汇编语言变为.0目标二进制文件,且.o文件不可执行)
- 链接 (本质引入代码中使用的第三方库-c库)
在使用gcc指令时:
2. make/makefile
2.1 相关概念
-
make
是一条命令,makefile
是一个文件。 -
makefile
带来的好处就是——“自动化编译”。一旦写好,只需要一个make
命令,整个工程就会完全自动编译,极大地提高了软件开发的效率。 -
make
是一个命令工具,用于解释makefile
中的指令。一般来说,大多数 IDE 都支持这个命令,例如 Delphi 的make
、Visual C++ 的nmake
以及 Linux 下 GNU 的make
。由此可见,makefile
已成为一种广泛应用的工程编译方法。 -
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都有哪些成分
- 目标(Targets)变量定义
变量用于定义编译器、编译选项、源文件等,这样可以方便地在整个 Makefile
中使用和修改。
CC = gcc
CFLAGS = -Wall -I.
CC
定义了编译器。CFLAGS
定义了编译选项,如警告选项和包含目录。
- 目标(Targets)
目标是 make
命令要生成的文件(例如可执行文件),或者是需要执行的命令(如清理中间文件)。目标通常有两个主要部分:目标名称和依赖文件。
all: main
all
是一个目标,表示生成main
。
- 规则(Rules)
规则定义了如何从源文件生成目标。规则包括三部分:
- 目标:要生成的文件名。
- 依赖:生成目标所需的文件。
- 命令:用于生成目标的命令,通常以制表符(Tab)开始。
main: main.o add.o sub.o
$(CC) -o $@ $^
main
是目标,依赖于main.o
、add.o
和sub.o
。- 命令
$(CC) -o $@ $^
用于链接目标文件生成可执行文件。
- 模式规则(Pattern Rules)
模式规则用于定义如何生成一类目标。例如,下面的规则定义了如何将每个 .c
文件编译成 .o
文件:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
%.o
是目标模式。%.c
是源文件模式。$<
表示第一个依赖文件,$@
表示目标文件。
- 伪目标(Phony Targets)
伪目标是不会生成实际文件的目标。它们用于执行特定命令,如清理构建文件。
.PHONY: clean
clean:
rm -f *.o main
.PHONY
声明clean
不是一个文件名,clean
目标会删除所有编译生成的文件。
- 示例
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 | 只进行编译,不进行链接,生成目标文件 .o | gcc -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 |