本文讲解 Linux 中 gcc/g++ 编译 C/C++ 代码的用法,以及 make 与 Makefile 工程构建,包括依赖规则、伪目标、自动变量,并且介绍批量编译的实现方式,方便管理多文件 C 语言项目。
目录
[演示 1:一次性编译](#演示 1:一次性编译)
[演示 2:分步编译(多文件工程原理)](#演示 2:分步编译(多文件工程原理))
[演示 3:预处理(-E)](#演示 3:预处理(-E))
[演示 4:生成汇编代码(-S)](#演示 4:生成汇编代码(-S))
[演示 5:带调试信息编译(gdb 调试用)](#演示 5:带调试信息编译(gdb 调试用))
[1.make 是什么](#1.make 是什么)
[2.Makefile 是什么](#2.Makefile 是什么)
[3.核心概念:依赖关系 & 依赖方法](#3.核心概念:依赖关系 & 依赖方法)
[1. 依赖关系](#1. 依赖关系)
[2. 多规则联动(Makefile 会自动递归推导依赖)](#2. 多规则联动(Makefile 会自动递归推导依赖))
[4.伪目标 .PHONY](#4.伪目标 .PHONY)
[5.Make 自动变量](#5.Make 自动变量)
一、gcc/g++
1.简介
gcc:C 语言编译器 ,用于编译 .c 源码
g++:C++ 编译器 ,用于编译 .cpp/.cc/.cxx,内部底层依然调用 gcc,自动链接 C++ 标准库
.c 文件推荐用 gcc
.cpp 文件推荐用 g++
核心用途
注:该部分可结合上一篇博客:Linux基础开发工具(二)程序编译与动静态库篇,深入理解。
将 C/C++ 源代码,经过预处理 → 编译 → 汇编 → 链接,生成可执行程序
支持生成目标文件、静态库、动态库
支持调试、优化、警告检查
2.基础语法格式
gcc 选项 源文件 -o 输出文件名
g++ 选项 源文件 -o 输出文件名
常用选项汇总
表格
| 参数 | 作用 |
|---|---|
-o filename |
指定输出文件名,不加默认生成 a.out |
-c |
只编译汇编,不链接 ,生成 .o 目标文件 |
-E |
只做预处理,输出预处理后的代码 |
-S |
生成汇编代码 .s,不汇编、不链接 |
-g |
添加调试信息,gdb 调试必须加 |
-O0 / -O1 / -O2 / -O3 |
代码优化级别,O0 不优化(调试用),O3 最高优化 |
-Wall |
打开所有警告,强烈推荐,帮你发现代码隐患 |
-I路径 |
指定头文件搜索目录(大写 i) |
-L路径 |
指定库文件搜索目录 |
-lxxx |
链接库,如 -lpthread 线程库 |
3.示例演示
gcc 示例(g++同理)
新建 3 个文件:main.c、calc.c、calc.h
calc.h
cpp
#pragma once
int add(int a, int b);
int sub(int a, int b);
calc.c
cpp
#include "calc.h"
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
main.c
cpp
#include <stdio.h>
#include "calc.h"
int main(void)
{
int a = 10, b = 3;
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", sub(a, b));
return 0;
}
演示 1:一次性编译
bash
gcc main.c calc.c -o calc_demo -Wall
运行
./calc_demo
输出:
a + b = 13
a - b = 7
演示 2:分步编译(多文件工程原理)
- 只编译,不链接,生成
.o目标文件
bash
gcc -c main.c -o main.o -Wall
gcc -c calc.c -o calc.o -Wall
-
链接目标文件,生成可执行程序
gcc main.o calc.o -o calc_demo
演示 3:预处理(-E)
gcc -E main.c -o main.i
打开 main.i,可以看到头文件展开、宏处理后的完整源码。
演示 4:生成汇编代码(-S)
gcc -S main.c
生成 main.s 汇编文件。
演示 5:带调试信息编译(gdb 调试用)
bash
gcc -g main.c calc.c -o calc_demo -Wall
二、make/makefile
1.make 是什么
make 是 Linux 下工程构建工具 。 当项目源码文件很多时,如果每次修改代码都手动敲很长的 gcc 编译命令,非常麻烦。 make 会读取 Makefile 文件,自动根据文件的修改时间,判断哪些文件需要重新编译,只编译改动过的文件,避免全部源码重复编译,提升编译效率。
make 本身不是编译器,它只是工具,底层依然调用 gcc/g++。
2.Makefile 是什么
Makefile 是make工具读取的配置脚本文件 (文件名固定:Makefile 或者 makefile)。 在这个文件里,我们写清楚工程的文件依赖关系、编译命令 。 在终端直接输入 make,make 就会自动解析 Makefile,执行对应的编译指令。
3.核心概念:依赖关系 & 依赖方法
1. 依赖关系
规则格式:
bash
目标: 依赖文件列表
依赖方法(命令,**必须Tab缩进,不能用空格**)
目标 :你想要生成的文件(比如可执行程序、.o目标文件)
依赖文件:生成这个目标,所需要的源码 / 头文件。如果依赖文件的修改时间比目标新,代表文件改动,需要重新执行命令。
依赖方法:生成目标要执行的 shell 命令(gcc 编译命令)
想要生成【目标】,必须保证【依赖文件】存在;依赖文件更新了,就执行【依赖方法】重新构建目标。
重要:Makefile 中命令行前面必须是 Tab 缩进,不能使用空格 ,否则会报missing separator语法错误。
示例:
bash
calc_demo: main.o calc.o
gcc main.o calc.o -o calc_demo -Wall
目标:calc_demo(最终可执行程序)
依赖:main.o 和 calc.o
依赖方法:gcc main.o calc.o -o calc_demo -Wall
含义:生成calc_demo需要main.o、calc.o;只要任意一个.o文件改动,就执行这条链接命令。
2. 多规则联动(Makefile 会自动递归推导依赖)
bash
calc_demo: main.o calc.o
gcc main.o calc.o -o calc_demo -Wall
main.o: main.c calc.h
gcc -c main.c -Wall
calc.o: calc.c calc.h
gcc -c calc.c -Wall
执行make时的逻辑:
找目标
calc_demo,需要main.o、calc.o发现
main.o需要main.c calc.h,判断源码是否更新,更新就执行gcc -c编译生成 main.o同理处理 calc.o
两个.o 全部准备好,执行链接命令,生成可执行程序
优势:只改动calc.c,只会重新编译calc.o,不会重新编译 main.o
4.伪目标 .PHONY
什么是伪目标
伪目标不是文件 ,只是一个标签,用来执行一组命令,不会生成对应的文件。 最常用的就是 clean:用来删除编译产物(.o、可执行程序)。
如果不声明伪目标: 如果你的目录里刚好存在一个名叫clean的文件,make 会认为 clean 文件已经存在,就不会执行 clean 对应的删除命令,出现 bug。
用 .PHONY 声明伪目标,告诉 make:clean不是文件,永远执行命令。
bash
.PHONY: clean
clean:
rm -f *.o calc_demo
使用命令:
bash
make clean
执行效果:删除所有.o目标文件和可执行程序,清理工程。
5.Make 自动变量
表格
| 自动变量 | 含义 |
|---|---|
$@ |
当前规则的目标文件 |
$^ |
当前规则全部依赖文件 |
$< |
当前规则第一个依赖文件 |
bash
$(BIN): $(OBJ)
$(CC) $(LFLAGS) $@ $^
$@ = proc.exe(目标)
$^ = myproc.o(所有依赖)
bash
%.o : %.c
$(CC) $(FLAGS) $<
%.o : %.c:模式规则,匹配任意 xxx.o,依赖同名 xxx.c
$<:代表对应的.c源文件
6.批量编译
为什么要批量编译?
不用手动写大量重复编译命令
如果项目有很多源码,比如 main.c calc.c func.c log.c。 不使用批量编译: 你需要手动一条一条写 gcc 编译指令,文件越多,Makefile 越长,新增源码就要新增一条规则。
如何批量编译?
bash
BIN = proc.exe
CC = gcc
# SRC = $(wildcard *.c)
SRC = myproc.c # SRC = $(shell ls *.c) 列出当前所有.c文件
OBJ = myproc.o # OBJ = $(SRC:.c=.o) 替换,把.c改成.o
LFLAGS = -o
FLAGS = -c
RM = rm -f
$(BIN): $(OBJ)
$(CC) $(LFLAGS) $@ $^
%.o : %.c
$(CC) $(FLAGS) $<
.PHONY: clean
clean:
$(RM) $(OBJ) $(BIN)
.PHONY: test
test:
@echo $(SRC)
@echo $(OBJ)
变量解析
bash
BIN = proc.exe # 最终生成的可执行程序名称
CC = gcc # 指定编译器
SRC = myproc.c # 源文件
OBJ = myproc.o # 目标文件
LFLAGS = -o # 链接参数,用来指定输出文件名
FLAGS = -c # 编译参数:只编译,不链接,生成.o目标文件
RM = rm -f # 删除命令,-f 强制删除,不提示
批量获取文件的两种写法
1. SRC = $(wildcard *.c)``wildcard是 Make 内置函数,自动扫描当前目录,获取所有后缀为.c 的源文件,实现批量搜集源码。
2. OBJ = $(SRC:.c=.o)后缀替换语法 ,把 SRC 列表中所有文件后缀.c批量替换为.o,自动生成目标文件列表。
开启批量编译:注释掉SRC = myproc.c、OBJ = myproc.o,启用下面两行:
SRC = $(wildcard *.c)
OBJ = $(SRC:.c=.o)
开启后,目录下所有.c文件会被自动批量编译,新增 c 文件无需修改 Makefile。
三、总结
gcc/g++ :C/C++ 编译器,源码需要经过预处理、编译、汇编、链接四步生成可执行程序;掌握
-o、-c、-Wall、-g等常用编译选项,支持一次性编译和分步编译两种方式。make & Makefile :
make是工程构建工具,底层调用 gcc,依靠文件时间戳实现增量编译,只编译改动过的文件;Makefile是配置脚本,核心由目标、依赖关系、依赖方法构成,命令行必须 Tab 缩进。伪目标.PHONY:用来定义标签(如 clean 清理、test 调试),不是实体文件,避免和本地文件重名导致规则失效。
自动变量 :
$@、$^、$<简化 Makefile 书写;借助wildcard和后缀替换语法可以实现批量编译,自动收集全部源码,新增 C 文件无需修改 Makefile,适合多文件项目。