Linux专栏第二篇,聊点真东西:基础开发工具。
上一篇我们花了不少篇幅在终端、目录、权限、用户这些基本命令上,那算是Linux的“门禁系统”。但门禁过了之后,真正要做开发时,很多人反而会愣住:我在这个黑乎乎的终端里,到底靠什么写代码、编译代码、调试代码?这篇就用一条实际开发路径,把几样躲不掉的工具串一遍:vim、gcc/g++、gdb、make。等你把这套链路跑通,在命令行里写C/C++程序就不再是零碎敲命令,而是一条顺畅的流水线。
1. 先把工具链的全局地图画出来:编辑器、编译器、调试器、构建器的分工
1.1 一个完整程序从源码到可执行文件要经过哪几步
先别急着敲命令,我们脑子里先画一条流水线。一个C程序从源代码变成能运行的可执行文件,中间要经过四个阶段:预处理、编译、汇编、链接。
- 预处理:把头文件展开、宏替换、处理条件编译指令,得到的是纯文本的
.i文件。 - 编译:把C语言翻译成汇编指令,得到
.s文件。 - 汇编:把汇编指令翻译成机器码,得到
.o目标文件。 - 链接:把多个目标文件和库文件组合到一起,解决“你的代码调用了别人写的函数,但还不知道它的地址在哪”的问题,最终生成可执行文件。
对应到工具上:vim负责把源码写出来;gcc/g++负责中间两个阶段以及最后的链接;gdb负责在程序运行起来之后盯着变量、断点、调用栈,帮你定位问题;make则不直接处理代码,它负责把前面所有步骤按照依赖关系自动化。
这四样东西不是四门独立的课程,而是一条完整的链。很多人学的时候会觉得“vim是一节课,gcc是一节课,gdb是一节课”,结果学完还是不会开发。真正有效的思路是把它当成一条生产线:每个工具只干一件事,干完把产物交给下一个工具。你第一次写程序时可能感受不到这种分工的价值,等项目变成十几个文件、还有第三方库参与的时候,你就会明白为什么每道工序都需要一个专门角色了。
1.2 为什么不用IDE,偏要学命令行工具链
我知道大部分人在Windows或Mac上写C/C++都是用IDE,比如常见的Visual Studio、CLion、Xcode。既然IDE里一个按钮就能编译运行,为什么还要学vim这套“原始”东西?
因为IDE是把前面说的四个阶段打包成了一个“编译运行”按钮,你点击之后它内部帮你执行了全部工序。问题在于:当你需要在一台没有图形界面的服务器上修改代码、排查问题,或者要在自动化构建脚本里编译几十万行代码时,那层打包好的界面是使不上的。生产环境里最常见的场景不是“打开IDE写代码”,而是“ssh登上一台机器,用vim快速改个文件,再用gcc把它编出来,用gdb看看为什么崩了”。
另一个理由是出错时的排查能力。IDE把四阶段藏得太深,一旦出现“undefined reference”“找不到头文件”这类错误,IDE只会给一个高亮提示,很多人根本不知道这个错误发生在哪一步、该怎么解决。而命令行工具链把每一道工序摊在你面前,你能精确知道问题出在预处理还是链接阶段,排查路径清晰得多。这篇文章适合已经会基础Linux命令、正准备上手写C/C++代码的读者,也适合那些在IDE里写惯了、突然要在命令行环境里干活的同学。
2. vim:绕不开的编辑器,把模式切换练成肌肉记忆
2.1 为什么Linux环境里到处都有vim
第一个环节是编辑器。我经常被问:为什么不是nano?不是emacs?不是VS Code?原因很朴素:几乎所有Linux发行版和服务器都预装了vim,你登录任何一台新机器,输入vim就能开始干活,不用安装、不用配置、不需要图形界面。nano确实更友好,但处理大文件和复杂操作时,vim的效率优势非常明显。这不是说vim天下第一,而是它的普适性极其可怕:长期维护的老项目、运维脚本、dotfile配置,到处都有它的身影。
以我自己为例,刚用vim时也是很嫌弃的,一个光标移动就够我纠结半天。后来在服务器上调一个线上问题,手边只有vim和一个日志文件,我被迫用vim查找、跳转、替换,处理完才发现这工具没那么难。从那以后,它就成了我的默认编辑器。如果你走上开发这条路,vim大概率是你躲不掉的基本功,早点把它练成肌肉记忆,后面会省很多事。
2.2 模式切换:跳出“打开就能打字”的惯性
vim和普通编辑器最大的区别是:它认为键盘不只是打字工具,更是操作指令。默认进入的是普通模式(Normal),这个模式下h、j、k、l负责移动光标,d是删除,y是复制,p是粘贴。你想输入文字,必须先按i进入插入模式(Insert)。这个设计一开始完全反直觉,我刚接触时就出现过“怎么敲字母屏幕上没反应”的尴尬。
但正是这个模式设计,让vim的操作效率极高。记住一个核心心法:普通模式里你随时可以发命令,插入模式里你只负责打字,日常使用90%的时间其实都在普通模式。下面这张表是我认为最值得先记的一张:
| 操作 | 命令 | 说明 |
|---|---|---|
| 进入插入模式 | i | 在光标前插入,最常用 |
| 返回普通模式 | Esc | 所有操作的中枢 |
| 删除当前行 | dd | 配合数字可用3dd删三行 |
| 复制当前行 | yy | 配合数字可用3yy复制三行 |
| 粘贴 | p | 粘到当前光标下一行 |
| 跳转文件头/尾 | gg / G | 快速定位 |
| 搜索 | /keyword | 按n跳到下一处 |
| 全局替换 | :%s/old/new/g | 带确认用gc |
| 撤销 | u | 后悔药 |
| 保存退出 | :wq | 保存并退出 |
| 不保存退出 | :q! | 强制退出 |
我建议你不要一次背全,先记i、Esc、dd、yy、p、gg、G这几个,然后在实操里反复用。一个月后你回头再看,会发现手指已经比脑子先记住它们了。
2.3 一份够用且不折腾的vimrc
vim默认的配置很朴素,但加上一份简单的vimrc之后,体验会舒服很多。下面这份配置适合新手,我逐行说明它做了什么:
set number " 显示行号 set relativenumber " 相对行号,配合移动命令很好用 syntax on " 语法高亮 set tabstop=4 " Tab显示为4个空格宽度 set shiftwidth=4 " 缩进步长4 set expandtab " Tab键展开成空格 set autoindent " 自动缩进 set hlsearch " 搜索高亮 set incsearch " 边输入边搜索 set cursorline " 高亮当前行 set encoding=utf-8 " 统一UTF-8编码,避免中文乱码 set fileencodings=utf-8,gbk " 自动识别编码把这些写到~/.vimrc里,重启vim立即生效。行号和相对行号是调试时的利器,配合数字加移动命令(比如5dd删除5行、3j向下移动3行)效率极高。自动缩进能让代码整齐,但后面会提到它也有一个坑。
2.4 我在vim里踩过的三个坑
第一个坑是粘贴代码缩进全部乱掉。原因是autoindent在起作用,你粘贴进来的代码会被按原有缩进逻辑重新处理。解决办法是粘贴前输入:set paste,粘贴完再输入:set nopaste。这两个命令来回切换有点烦,所以我平时不常开paste模式,只在需要粘贴大段代码时才打开。
第二个坑是中文乱码。多发生在服务器和本地环境编码不一致时,代码里的中文注释变成一片乱码。解决方法就是上面vimrc里的set encoding=utf-8和set fileencodings=utf-8,gbk,看那些老项目时尤其有用。
第三个坑更隐蔽:在终端里按Ctrl+S之后vim突然“卡住”,怎么按都没反应。其实不是vim死了,是终端的流控把输出暂停了。此时按一次Ctrl+Q就能恢复。这个坑很少写进教程,但几乎每个终端用户都会碰到一次。
3. gcc/g++:理解四道工序,编不过时才不会一头雾水
3.1 四道工序分别干了什么
写完代码,下一步是编译。请先记住一句话:gcc不是一步把代码变成可执行文件,而是要过四道工序。知道这一点,很多编译报错你就能自己分辨出发生在哪个环节。
用命令来感受一下,假设有一个demo.c:
# 预处理:头文件展开、宏替换、删除条件编译 gcc -E demo.c -o demo.i # 编译:C代码转汇编 gcc -S demo.i -o demo.s # 汇编:汇编转目标文件(机器码) gcc -c demo.s -o demo.o # 链接:目标文件和库组合成可执行文件 gcc demo.o -o demo平时你写gcc demo.c -o demo其实就是把上面四步一口气做完了。为什么要拆分理解?因为不同报错发生的阶段完全不同:头文件找不到大概率在预处理阶段;语法错误发生在编译阶段;“undefined reference”几乎必然发生在链接阶段。你如果只知道“编译报错”这个概念,排查时就得全盲猜,但如果你清楚每条报错属于哪道工序,定位范围瞬间小了一半。
3.2 日常编译选项速查表
gcc的选项很多,但常用的就那几个,真正工作中高频出现的我整理成了这张表:
| 选项 | 作用 |
|---|---|
| -E | 只做预处理,输出.i文件 |
| -S | 只做到编译,输出汇编.s文件 |
| -c | 只做到汇编,输出目标.o文件 |
| -o | 指定输出文件名 |
| -Wall | 开启常见警告 |
| -Wextra | 开启更多警告 |
| -g | 生成调试信息,gdb调试的前提 |
| -O2 | 开启优化,发布版常用 |
| -std=c11 | 指定C语言标准 |
| -I/path | 指定头文件搜索路径 |
| -L/path | 指定库文件搜索路径 |
| -lname | 链接名为libname的库 |
这里有个值得展开的点:-Wall不是“所有警告”,但它性价比极高。它会帮你发现很多潜在问题,比如变量未使用、比较类型不匹配等。我见过太多初学者在有警告的情况下继续往下走,最后调试半天发现是警告里早就提示过的隐患。别忽略警告,它往往是你代码质量的免费体检报告。
-g和-O2放在一起也有讲究。调试阶段用-g,发布阶段用-O2,两者同时用会遇到“代码行号跳来跳去、变量被优化没了”的现象。这是因为优化会重排指令、合并变量,不是编译器坏了,是它在帮你提速而牺牲了调试体验。
3.3 库文件与链接:让函数“找到家”
链接阶段是新手最容易卡住的地方,尤其是遇到第三方库的时候。区分两类库:动态库(.so)和静态库(.a)。动态库在程序启动或运行时才加载,体积小、多个进程可共享;静态库在链接时把代码直接拷贝进可执行文件,部署简单但体积大。选型规则一句话:想要更新方便、节省内存就选动态库;想完全独立部署、不担心环境差异就选静态库。
链接时的典型写法是这样的:
# 编译main.c,链接当前目录下的libmycalc.so,生成app gcc main.c -L./lib -lmycalc -o app # 运行前告诉系统去./lib找动态库 export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH ./app这里有一个所有新手都会踩的坑:-l选项在源文件后面写。链接器是从左到右扫描目标文件和库的,如果-lmycalc写在 main.c 前面,链接器扫描到库时还不知道 main.c 里的函数引用,就以为自己不需要这个库,最后报一堆 undefined reference。我刚开始也经常被这个规则坑到,记住“库名写在源文件后面”这个习惯能省很多事。
3.4 编译报错的排查顺序
被编译错误轰炸时,我有一个固定的排查顺序,能应付绝大多数情况:
第一,先看第一条error,不要被后面一长串吓到。编译器的错误经常像多米诺骨牌,第一条报错引发了后续十几条连锁反应,你只需解决第一条,后面经常自己就消失了。第二,区分错误阶段。“没有那个文件或目录”多半是头文件路径问题,看看是不是忘了-I;“undefined reference”是链接问题,检查库有没有链、顺序对不对、函数签名是否一致。第三,C++项目还要注意:函数重载会产生名前修饰,所以C++编译出的符号和C不一样,混合编译时需要在头文件里加extern "C"来约定接口。
我遇到过最典型的例子:写了一个计算器程序,编译报告undefined reference to 'add'。第一反应是“add函数没写”,结果翻代码明明写了。最后发现是编译命令只写了gcc main.c -o app,压根没把calc.c一起编译链接。这类问题的根源不是代码,而是构建命令不完整。等我们后面讲到make,这个问题就会被自动规避。
4. gdb:调试不是等到崩溃才开始
4.1 -g选项是调试的地基
编译通过只是第一步,程序逻辑不对照样白搭。gdb是命令行下的调试器,它最值钱的地方在于:不管有没有图形界面,只要终端能打开,它就能调试。
用gdb之前有一个前提:编译时必须带上-g选项。如果没加,程序也能跑,但gdb看不到源码行号和变量名,只能看到一堆汇编和内存地址,调试体验从“看代码捉虫”变成“对着天书猜谜”。而加入-g之后,调试器就能把机器码和源码行对应起来,你让它断在第几行它就断在第几行。
另外一个相关细节:如果你用-O2优化之后调试,会发现断点跳行、变量值看起来不对。前面也提过,这不是gdb的问题,是优化改变了程序的执行顺序。所以调试阶段建议用-O0或干脆不加优化,等代码稳定了再开-O2发布。
4.2 上手即用的gdb命令清单
gdb的命令很全,但真正高频使用的就那么几个。我建议第一次接触的人先记住这张表:
| 命令 | 作用 |
|---|---|
| gdb ./app | 启动调试 |
| r | 运行程序 |
| b 10 | 在第10行下断点 |
| b func | 在函数入口下断点 |
| info b | 查看所有断点 |
| n | 单步执行,不进入函数 |
| s | 单步执行,进入函数 |
| p 变量名 | 打印变量的值 |
| bt | 查看调用栈 |
| c | 继续运行到下一个断点 |
| q | 退出gdb |
其中n、s、p、bt是我每天用得最多的四个命令。n和s的区别很关键:n把函数调用当一步跳过去,适合看流程;s会钻进函数内部,适合查详情。我第一次调试时因为分不清这两个,结果在printf内部来回蹦了十分钟,还以为程序出问题了。
4.3 三个高频debug场景
场景一:段错误。程序编译通过,一运行就提示 Segmentation fault。遇到这种情况先按下面的流程:先gdb ./app,再r运行,程序崩溃时gdb会自动停在崩溃的位置,此时输入bt查看调用栈,它能直接告诉你崩溃发生在哪个函数、哪一行。绝大多数段错误的排查到这里就已经结束了,后面的修复就是检查那个位置的指针和数组越界。
场景二:结果不对但没崩溃。某函数明明该返回正数,结果算出负数。解决办法是在函数入口下断点,b calc,然后r,再用p 参数看看入参是否符合预期,最后n一步步走,盯着某个变量在哪一步变了。这个过程相当于把程序放慢一百倍,逻辑错误通常无处可藏。
场景三:程序卡死疑似死循环。按Ctrl+C中断,gdb会停在当前执行的位置,此时bt看调用栈,再用p 循环变量判断是不是跳不出循环条件。我碰到过一次循环里忘记更新计数器,gdb一停就发现变量一直没变,问题三秒钟定位。
4.4 core dump:崩溃后的案发现场
段错误还有一种更优雅的处理方式:利用core dump文件。先允许系统生成core文件:
ulimit -c unlimited程序崩溃后,系统会把进程崩溃时的内存镜像保存成一个core文件,然后用gdb打开它:
gdb ./app core你会发现不需要重新运行程序,直接就能查看崩溃位置的调用栈和变量值。这在复现困难的线上问题里特别有用:部署时带上ulimit -c unlimited,加上一个带时间戳的core文件名配置,崩溃现场就留住了。事后把core文件拿回来,用gdb离线分析,比自己猜要靠谱太多。唯一需要注意的是core文件可能很大,生产机器上要做文件大小限制和定期清理。
5. make与Makefile:把重复劳动交给机器
5.1 make的工作方式:时间戳与依赖
现在来到第四个工具。假设你还在一个只有三四个文件的小项目里,每次修改一个文件就重新敲一遍gcc main.c calc.c utils.c -o app -lm,勉强还能接受。但一旦文件数量到两位数,或者需要分目录、链接多个库、执行安装和清理动作,手敲命令就不再是效率问题,而是错误率问题。make就是为了解决这个“重复性构建”问题而生的。
make的核心机制出人意料地简单:比较时间戳。它不断检查“目标文件”和“依赖文件”的修改时间,如果依赖文件比目标文件新,说明源码改过了但产物没跟着更新,于是重新执行规则里的命令;如果目标文件已经很新,说明不需要重编。这也是新手第一次接触Makefile时最难转过弯的地方:make不是在机械地执行你写的命令,而是在“判断要不要执行命令”。理解这一点,后面很多诡异现象就都有了答案。
5.2 第一个像样的Makefile
我们给之前提到的计算器项目写一个Makefile,这是最小可用的版本:
CC = gcc CFLAGS = -Wall -Wextra -g TARGET = app SRCS = main.c calc.c OBJS = $(SRCS:.c=.o) $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $(TARGET) $(OBJS) %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -f $(TARGET) $(OBJS) .PHONY: clean逐行解释一下。前三行定义变量:编译器、编译选项、目标文件名。SRCS是源码列表,OBJS是目标文件列表,$(SRCS:.c=.o)表示把SRCS里所有.c后缀替换成.o。第一条规则说:要生成app,先得有main.o和calc.o,然后执行链接命令。第二条规则是模式规则,%.o: %.c表示任何.o都依赖同名.c,编译命令里的$<代表第一个依赖(也就是那个.c文件)。clean是清理任务的规则,rm删除产物。
配套执行非常简单:
make # 构建 make clean # 清理这个Makefile虽然短,但已经具备Makefile的基本骨架。我建议你第一次写时连注释都照抄一遍,亲手跑过make之后,那些变量和自动变量会用得很顺。
5.3 变量、自动变量和通配规则
Makefile里的“语法糖”再多,也离不开下面这几个核心记号:
| 记号 | 含义 |
|---|---|
| $@ | 当前规则的目标文件名 |
| $< | 当前规则的第一个依赖文件名 |
| $^ | 当前规则的全部依赖文件名 |
| $(SRCS:.c=.o) | 后缀替换,返回新列表 |
| $(wildcard *.c) | 自动收集目录下所有.c文件 |
真正理解这几个标记之后,你就不再用笨办法逐个列出所有文件了。比如想自动收集当前目录所有源码,可以写SRCS = $(wildcard *.c),这样新增一个.c文件都无需修改Makefile。自动变量$@、$<、$^则让规则只写一次、适配任意文件,它们让Makefile从“死板脚本”变成“通用规则”。我第一次看懂$<时有种“这简直是循环语句”的感觉,实际上它就是make帮你隐式遍历依赖文件的机制。
5.4 Makefile事故现场:tab键和伪目标
Makefile报错里最有名的一个:missing separator。九个有八个是因为规则行的命令前用了空格,而不是Tab键。make对格式要求非常严格,规则下的命令必须用Tab缩进,这是它最不近人情也最经典的地方。我的习惯是写规则时先按一下Tab,再开始写命令。
第二个常见坑是伪目标冲突。写了一个clean规则,假设当前目录里恰好有一个文件名叫clean,make会认为“clean已经存在且比任何依赖都新”,于是执行make clean时告诉你“make: 'clean' is up to date”,什么都清不掉。解决办法就是.PHONY: clean,声明clean是一个伪目标,不需要检查文件时间戳,每次都执行。这个坑几乎每个人都会踩一次,踩过就记住了。
第三个坑和增量编译有关:你修改了头文件,但make没有重新编译所有相关文件。原因很简单,规则里只写了.o依赖.c,没有把.h写进依赖。常见做法是使用-MMD参数自动生成头文件依赖,这里先记住结论:任何被源代码 include 的头文件,都应该作为一种依赖参与判断,否则就会遇到“我改了头文件但重新make没反应”的诡异现象。
6. 组合实战:用vim、gcc、gdb和make走通一个计算器项目
6.1 项目结构与代码设计
工具讲了一堆,现在串起来走一遍完整流程。我准备了一个非常典型的小项目:一个支持加减乘除的计算器程序,文件拆分如下:
mycalc/ ├── main.c # 主程序,负责接收用户输入并展示结果 ├── calc.h # 接口声明 ├── calc.c # 运算逻辑实现 └── Makefile # 构建脚本为什么要拆成多个文件?因为实际项目里没人把全部代码塞进一个main.c,设计上讲究“接口与实现分离”。calc.h只放函数声明,调用者不需要知道内部怎么实现;calc.c放具体逻辑,将来想优化内部算法时,不会影响main.c。这个设计思想不复杂,但它是后续一切模块化的基础。
calc.h长这样:
#ifndef CALC_H #define CALC_H double add(double a, double b); double sub(double a, double b); double mul(double a, double b); double divide(double a, double b); #endifcalc.c实现里,四个函数本身很简单,唯一需要注意的就是除法除数为零的问题。main.c逻辑也很直白:读取两个数和一个运算符,调用calc里的函数,输出结果。代码本身不是重点,重点是接下来我们如何用工具链把整个流程走通。
6.2 编译期遇到的两个问题
用vim把三个文件写好后,我故意模拟一个典型场景:第一次编译,我用了一个不完整的命令。
gcc main.c -o app结果报了一堆undefined reference to 'add'、undefined reference to 'sub'这样的链接错误。原因前面讲过:编译器编译了main.c,也看到了calc.h里的函数声明,但链接时找不到add、sub这些函数的实现,因为calc.c根本没参与编译和链接。解决办法也很直接:
gcc main.c calc.c -o app -Wall -g这次编译通过,但-Wall给出一个警告:除法函数里没有处理除数为零的情况。这就是我强调过不要忽略警告的原因,它提前告诉你运行时会有一个风险点。我没有立即去改代码,而是带着这个警告进入下一步,正好用它来演示gdb怎么帮你抓到问题。
6.3 用gdb揪出逻辑错误
程序能跑,但我输入5 / 0时,程序直接崩溃了。这就是刚才警告里预示的问题。定位过程如下:
先在代码里确认崩溃位置。重新用-g编译后启动gdb:
gdb ./app在gdb里输入r运行,输入5 / 0,程序崩掉时gdb会自动停在崩溃点,输入bt:
(gdb) r (gdb) btbt输出的调用栈清楚显示:崩溃发生在divide函数的return a / b这一行。我都不用猜,直接就知道是除零。接着用core dump再走一遍:重启终端前运行ulimit -c unlimited,再次运行程序崩溃后,当前目录出现一个core文件,然后:
gdb ./app core这次不需要重跑程序,直接进入崩溃现场,bt一样能拿到调用栈。修复方法是在divide里加上除零判断:
double divide(double a, double b) { if (b == 0) { fprintf(stderr, "错误:除数不能为0\n"); return 0; } return a / b; }改完重新编译运行,这次再输入5 / 0不会崩,会输出提示。一个从警告到崩溃再到core dump定位、最后修复的完整闭环,就是这套工具链最典型的用法。
6.4 构建脚本一次成型
修复之后,我不用再每次手动敲gcc命令了,直接用之前那个Makefile。把它放到项目目录下,执行:
makemake会检查main.o、calc.o是否比对应源码新。第一次构建因为还没有任何.o文件,所以两个目标文件会被逐个编译,最后链接成app。再执行make clean,可以看到app和两个.o文件都被清理干净。这个循环对于小项目只是省一两条命令,但当你维护一个上百个源文件的项目时,make能准确识别出“我只改了一个文件,只需重编这一个文件”,这个效率差异是巨大的。
为了验证增量编译,我再次执行make生成所有产物后,故意修改calc.c里的一行注释,再执行make。注意观察输出:
gcc -Wall -Wextra -g -c calc.c -o calc.o gcc -Wall -Wextra -g -o app main.o calc.o它只重编了calc.o和链接,main.o没有动。这就是时间戳机制在起作用,也是我前面强调的那个核心洞察:make不是执行命令,而是判断要不要执行命令。增量编译的能力在大型项目里能大量节省时间,因为C/C++编译单个文件往往是以秒计的,上百个文件全量重编可能就是几分钟。
6.5 工具链串起来的最终体验
走完这一遍,你会发现四个工具各司其职,没有任何一个环节是多余的。vim负责从零写出代码,gcc负责把代码变成机器能懂的命令序列,gdb负责在程序运行中当你的“放大镜”,make则把所有重复劳动抽出来,变成一条可重复执行的构建规则。
对于计算器项目,这套流程看起来甚至有点“大材小用”。但请把视野放大一点:同样的流程,把gcc换成g++,把calc.c换成你实际业务的几十个源文件,把Makefile加上安装、测试、清理等目标,它就是你日常开发工作的核心骨架。我之后如果再写Makefile进阶,多半会往两个方向展开:一是自动化生成头文件依赖,二是多目录项目的构建组织。但今天这套基础链路,已经足以帮你应付绝大多数入门阶段的开发任务了。
7. 关于这套工具链,我最后想说的
如果你正在学这套工具链,我给一个建议:千万别一开始就去背vim的命令表或者Makefile的语法清单。先把前面那个计算器项目自己完整敲一遍,从vim新建文件开始,到gcc编译、gdb调试core dump,最后用make一键构建。遇到不会的临时查,需要什么学什么。跑通之后你会发现,那些命令和选项不是死的知识点,而是你解决问题时顺手抓来的工具。
我个人在实际使用中的另一个体会是:这四个工具里最容易被低估的是make。很多人觉得项目小用不上,真等你接手一个多目录、多依赖的项目时,Makefile的依赖关系管理和增量编译能省下大量体力活。把这套基础打牢,之后接触更现代的构建系统时,你会发现它们解决的是同样的问题,只是换了一层更友好的语法而已。工具在变,但“编辑、编译、调试、构建”这条开发主线,永远不会变。