目录
1 make 与 Makefile 自动化构建
1.1 为什么需要 Makefile
1.2 Makefile 基础规则
1.3 make 工具推演执行逻辑
1.4 伪目标 .PHONY
1.5 Makefile 进阶语法
自定义变量
三大自动变量(高频面试)
wildcard 通配符 + 后缀替换
模式规则 %.o:%.c
1.6 多文件项目通用 Makefile 模板
编辑
2 实战:Linux 终端进度条程序
2.1 \r回车 与 \n换行
2.2 完整进度条代码
编辑
3 Git 版本控制基础
3.1 版本控制解决什么痛点
3.2 Git 简单历史
3.3 安装与基础配置
3.4 Github 基础三板斧
编辑
4 gdb 调试器基础
4.1 debug 版本与 release 版本
gdb 常用核心命令
5 本篇小结
6 面试 & 实操高频问答
Q1 make 和 Makefile 关系?
Q2 .PHONY 伪目标作用?
Q3 $@ $^ $< 分别是什么?
Q4 \r 和 \n 区别,fflush 作用?
Q5 gdb 调试为什么编译要加‑g?
1 make 与 Makefile 自动化构建
1.1 为什么需要 Makefile
小项目只有一两个.c文件,可以直接手动敲 gcc 命令。 当项目源文件数量变多,每次修改源码手动敲编译命令非常麻烦。
make:命令行工具,用来解析 Makefile 文件规则Makefile:文本文件,记录项目编译的依赖规则。
价值:写好 Makefile 之后,只输入make一条命令就自动编译;make 会对比文件修改时间,只重新编译被修改过的文件,不会全部重编译,提升编译效率。
1.2 Makefile 基础规则
每条规则格式:
目标: 依赖文件列表 Shell命令 # !!必须Tab制表符开头,不能用空格- 目标 (target):想要生成的文件,例如可执行程序
- 依赖:生成目标所需要的文件
- 依赖方法:Tab 开头 shell 指令,描述如何由依赖生成目标
简单示例,test.c源码:
#include <stdio.h> int main() { printf("hello Makefile!\n"); return 0; }Makefile内容:
test:test.c gcc -o test test.c终端执行:
make ./test1.3 make 工具推演执行逻辑
- make 在当前目录寻找
Makefile或者makefile; - 读取文件,取第一条规则的目标作为终极目标;
- 判断目标文件是否存在:
- 如果目标不存在:执行命令生成目标;
- 如果目标已经存在:对比目标文件修改时间 和 所有依赖文件修改时间。
- 任意依赖文件比目标新 → 执行命令重新编译;
- 全部依赖比目标旧 → 什么都不做,提示
is up to date。
- 如果依赖本身是另一条规则的目标,则递归先执行这条规则,生成依赖文件。
测试时间逻辑:
make #生成test make #输出 is up to date touch test.c #更新c文件时间 make #重新编译1.4 伪目标.PHONY
clean目标,用来删除编译生成的中间文件与可执行程序:
clean: rm -f test *.o *.i *.s问题:如果当前目录刚好存在名叫clean的普通文件,执行make clean不会执行 rm 命令!make 会认为 clean 文件已经是最新,不需要执行规则。
使用.PHONY把 clean 声明为伪目标: 伪目标:不在乎磁盘有没有同名文件,只要执行make clean,无条件执行对应命令,不做时间戳对比。
正确写法:
.PHONY:clean clean: rm -f test *.o *.i *.s运行清理:
make clean1.5 Makefile 进阶语法
自定义变量
CC=gcc RM=rm -f test:test.c $(CC) -o test test.c .PHONY:clean clean: $(RM) test$(变量名)取出变量内容。
三大自动变量(高频面试)
表格
| 变量 | 含义 |
|---|---|
$@ | 当前规则的目标 |
$^ | 全部依赖文件集合,自动去重 |
$< | 依赖列表第一个文件 |
改写:
CC=gcc RM=rm -f test:test.c $(CC) -o $@ $^ .PHONY:clean clean: $(RM) testwildcard 通配符 + 后缀替换
wildcard *.c:获取当前目录所有.c文件名$(SRC:.c=.o):把列表中.c后缀替换为.o
SRC=$(wildcard *.c) OBJ=$(SRC:.c=.o)模式规则%.o:%.c
%代表通配任意文件名。含义:任意 xxx.o 文件依赖 xxx.c 源文件。
%.o:%.c $(CC) -c $< -o $@命令前面加@,代表不在终端回显这条 shell 命令,静默执行。
%.o:%.c @$(CC) -c $< -o $@1.6 多文件项目通用 Makefile 模板
可以直接复制用于多 c 文件小项目:
CC=gcc RM=rm -f TARGET=app SRC=$(wildcard *.c) OBJ=$(SRC:.c=.o) $(TARGET):$(OBJ) @$(CC) $^ -o $@ %.o:%.c @$(CC) -c $< -o $@ .PHONY:clean clean: @$(RM) $(OBJ) $(TARGET)使用:
make make clean注意:头文件
.h不会被 wildcard 捕获,大型项目需要手动把头文件加入依赖。
2 实战:Linux 终端进度条程序
2.1\r回车 与\n换行
老式打字机模型理解:
\n换行:光标移动到下一行行首\r回车:光标回到当前这一行的最开头,不向下换行
示例区分现象:
// 1 会sleep之后才打印hello printf("hello"); sleep(3); // 2 \n会刷新行缓冲 printf("hello\n"); sleep(3);Linux stdout 是行缓冲,遇到
\n才刷新缓冲区输出内容;没有换行,内容留在缓冲区,不会立刻打印。fflush(stdout);强制刷新标准输出缓冲区,立刻输出缓冲区内容。
倒计时 demo:
#include <stdio.h> #include <unistd.h> int main() { int i = 10; while(i >=0) { printf("%2d\r",i); fflush(stdout); sleep(1); i--; } printf("\n"); return 0; }2.2 完整进度条代码
process.h
#pragma once #include <stdio.h> void process_v1(); void FlushProcess(double total, double current);process.c
#include "process.h" #include <string.h> #include <unistd.h> #define NUM 101 #define STYLE '=' void process_v1() { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); const char *lable="|/-\\"; int len = strlen(lable); int cnt = 0; while(cnt <= 100) { printf("[%-100s][%d%%][%c]\r", buffer, cnt, lable[cnt%len]); fflush(stdout); buffer[cnt]= STYLE; cnt++; usleep(50000); } printf("\n"); } void FlushProcess(double total, double current) { char buffer[NUM]; memset(buffer, 0, sizeof(buffer)); const char *lable="|/-\\"; int len = strlen(lable); static int cnt = 0; int num = (int)(current*100/total); int i = 0; for(; i < num; i++) { buffer[i] = STYLE; } double rate = current/total; cnt %= len; printf("[%-100s][%.1f%%][%c]\r", buffer, rate*100, lable[cnt]); cnt++; fflush(stdout); }main.c
#include "process.h" #include <unistd.h> double total = 1024.0; double speed = 1.0; void DownLoad() { double current = 0; while(current <= total) { FlushProcess(total, current); usleep(3000); current += speed; } printf("\ndownload Done\n"); } int main() { DownLoad(); return 0; }配套简易 Makefile:
CC=gcc RM=rm -f TARGET=processbar SRC=$(wildcard *.c) OBJ=$(SRC:.c=.o) $(TARGET):$(OBJ) @$(CC) $^ -o $@ %.o:%.c @$(CC) -c $< -o $@ .PHONY:clean clean: @$(RM) $(OBJ) $(TARGET)运行:
make ./processbar3 Git 版本控制基础
3.1 版本控制解决什么痛点
写代码经常复制一大堆副本:v1、v2、最终版、最终版改改,版本混乱,分不清每版改了什么,回滚麻烦。 Git 是分布式版本控制系统:记录每一次文件修改历史,支持版本回退、多人协同开发。
3.2 Git 简单历史
Linux 内核社区早期使用商业 BitKeeper,2005 年合作终止。Linus Torvalds 开发 Git,目标:速度快、分布式、支持海量项目、强大分支管理。
3.3 安装与基础配置
CentOS 安装 git:
sudo yum install git -y git --version首次使用配置用户名邮箱(提交记录会携带):
git config --global user.name "你的名字" git config --global user.email "你的邮箱"3.4 Github 基础三板斧
- Github 网页注册账号,新建仓库
New repository,复制仓库 https/ssh 地址。
- 克隆仓库到本地:
git clone 仓库地址- 进入仓库目录,修改文件。
- 三板斧:
git add 文件名 # 将改动加入暂存区 git commit -m "写本次修改说明" # 提交到本地仓库 git push # 推送到远端Github辅助常用命令:
git status #查看当前状态 git log #查看提交日志 git pull #拉取远端最新代码到本地
.gitignore文件:配置哪些文件不需要 git 管理,比如编译生成的可执行程序、中间.o文件。
4 gdb 调试器基础
4.1 debug 版本与 release 版本
gcc 默认生成 release 版本,没有调试信息,不能 gdb 调试。 想要使用 gdb,编译的时候必须加上‑g选项生成 debug 调试信息。
示例代码sum.c
#include <stdio.h> int Sum(int s, int e) { int result = 0; for(int i = s; i <= e; i++) { result += i; } return result; } int main() { int start = 1; int end = 100; printf("I will begin\n"); int n = Sum(start, end); printf("running done, result is: %d\n", n); return 0; }编译带调试信息:
gcc -g sum.c -o sum gdb sumgdb 常用核心命令
表格
| 命令 | 功能 |
|---|---|
list/l | 查看源码,l 行号 |
run/r | 开始运行程序 |
break/b 行号 | 设置断点 |
info breakpoints | 查看所有断点 |
delete d 断点编号 | 删除断点 |
next/n | 逐过程,不进入函数 |
step/s | 逐语句,遇到函数进入函数内部 |
print/p 变量 | 打印变量值 |
display 变量 | 持续跟踪打印变量 |
continue/c | 运行到下一个断点 |
quit/q | 退出 gdb |
5 本篇小结
- make/Makefile 实现自动化编译;目标、依赖、Tab 命令;
.PHONY伪目标;$@ $^ $<自动变量。 \r回到行首、\n换行;stdout 行缓冲,fflush强制刷新,实现终端进度条。- git 分布式版本控制;
add commit push三板斧,管理代码版本。 - gdb 调试:编译必须加
‑g;掌握断点、next、step、print 基础调试命令。
6 面试 & 实操高频问答
Q1 make 和 Makefile 关系?
make 是可执行工具;Makefile 是保存编译规则的文本文件,make 读取该文件执行构建。
Q2 .PHONY 伪目标作用?
忽略文件时间戳,不管磁盘是否存在同名文件,执行 make 伪目标就无条件执行规则,典型用于 clean。
Q3 $@ $^ $< 分别是什么?
$@当前规则目标;$^全部依赖;$<第一个依赖。
Q4 \r 和 \n 区别,fflush 作用?
\n光标跳到下一行开头;\r光标回到本行开头;stdout 行缓冲,fflush 强制刷新缓冲区立刻输出内容。
Q5 gdb 调试为什么编译要加‑g?
-g生成调试符号信息,release 版本没有调试信息,无法 gdb 断点调试。
📚系列回顾: 04 上:yum + vim + gcc/g++ 04 下:make/Makefile + 进度条 + git + gdb 调试 后续预告:Linux 进程基础