〇、从机器语言到 Hello World
摘要 · 这篇文章能帮你解决什么?
如果你对编程毫无概念,或者只有一些零碎的了解;如果你照着老师给的步骤走完了,却完全不知道这些操作对自己的电脑做了什么;如果你只会用 Dev-C++或 VScode 点按钮,不清楚代码到底是怎么变成程序的
这篇文章会从零开始带你走完:认识代码怎么变成程序 → 装编译器 → 配置环境变量 → 用最“土”的办法跑通第一个程序。 全程以 Windows 操作系统为主视角,每一个操作都能跟着做,不需要任何前置知识。
写在前面:为什么“写个代码”这么难?
你可能经历过这样的场景。
照着老师的步骤一步一步做,完全不理解这些操作到底对电脑做了什么。抄完代码,满怀期待地按下运行键 —— 结果屏幕上冒出一行红字:
终端里让人抓狂的报错
'g++' 不是内部或外部命令,也不是可运行的程序或批处理文件。
折腾半天,Hello World 终于跑出来了,成就感爆棚 —— 但你完全不知道刚才发生了什么。想自己写点东西,又无从下手。
问题通常不在你。问题在于没有人告诉你“中间发生了什么”。
很多人学编程的第一步就跳得太快了:直接打开一个编辑器开始敲代码。于是“编译器”、“环境变量”、“终端”这些词,全都变成了一堆只能硬背的操作步骤 —— 因为没人解释它们在干嘛。
所以这篇文章会反着来:先把原理讲清楚,再动手。 你会知道每个操作为什么要做、不做会出什么问题。
第0步 · 从开关到人话:认识三种语言
在装任何软件之前,先花十分钟搞明白一件事 —— 你写的那几行字,是怎么一步步变成 CPU 能执行的程序的。
0.1 电脑的世界里,只有“开”和“关”
把电脑拆开看,里面是一大堆电路。电路上的每一个位置只有两种状态:通电,或者断电。
我们用两个数字来记它们:1 表示通电,0 表示断电。这就是“二进制”这个词的由来。
一个开关,叫 1 位(bit)
八个开关排成一排,叫 1 字节(byte)
一排排开关按照某种顺序排列,就组成了一条“指令”
在键盘和显示器普及之前,程序员真的是在纸带上打孔来写程序的:有孔是 1,没孔是 0。

说明 · 机器语言
直接用
0和1写出来、CPU 能直接看懂的指令,就叫机器语言。 它是唯一一种 CPU 生来就认识的语言,其他所有语言最终都得翻译成它。
一条真实的机器指令(十六进制)长这样:
一条 x86 机器指令
B8 01 00 00 00
这 5 个字节其实只在说一件事:把数字 1 放进 eax 这个小盒子里。
注意 · 那为什么不能一直用机器语言?
因为它根本就不是给人用的。上面那 5 个字节只完成了“放一个数字”这么小的动作;写一个能算平均分的程序,你得手算出成百上千个字节,而且错一个 0 就全盘皆输,报错信息还不会给你任何线索。 难写、难读、难查错 —— 这三条足以把它从日常开发里淘汰掉。
0.2 汇编语言:给每条指令起个名字
既然 CPU 只认 0 和 1,那我们能不能换个思路:给每一种“开关组合”起个好记的名字,写代码的时候用名字代替?

于是 mov(move,搬运)、add(add,相加)这样的助记符出现了。刚才那条机器指令就被写成了一行:
机器语言 | 汇编语言 | 含义 |
|---|---|---|
|
| 把 1 放进 eax |
|
| 让 eax 再加 2 |
说明 · 汇编语言
用助记符(
mov、add、jmp这些短单词)代替 0 和 1 的语言。 它和机器语言几乎一一对应:一条汇编指令基本就对应一条机器指令。
这就是它的问题所在:汇编语言并没有比机器语言“高级”多少。 它只是换了一套更顺手的符号,思考方式仍然是“操作哪个寄存器、搬哪个字节” —— 你依然得用 CPU 的脑子去想问题。
不过别小看它。汇编到今天依然重要:操作系统内核、驱动程序、逆向工程都离不开它。
0.3 高级语言:干脆说人话
能不能再往前一步 —— 我不想知道寄存器,我只想描述“我要做什么”?

于是有了 C 语言这样的高级语言:
用 C 语言描述同一件事
int sum = 1 + 2;
printf("%d", sum);
这两行不需要你知道 eax 是什么、字节长什么样。你只描述了两件事:把 1 和 2 加起来,把结果打印出来。
说明 · 高级语言
用接近人类语言和数学表达式来描述“要做什么”的语言。C、C++、Python、Java、Go、JavaScript、TypeScript 等都属于高级语言。 它的抽象层次更高:一句话往往能顶几十上百条机器指令。
代价也很直接:CPU 完全看不懂。
0.4 三层语言放在一起看
把刚才那道加法题用三种语言各写一遍,差别一目了然:

越往上,越接近人话,写起来越省事;
越往下,越接近机器,执行起来越直接。
提示 · 记住这句话!!!
你写的是高级语言,而 CPU 只认机器语言。 所以中间必须有一个角色,把“人话”翻译成“0 和 1” —— 这个角色就叫编译器(compiler)。
0.5 所以第一步不是装编辑器,而是装编译器

注意 · 初学者刚开始最容易搞混的一件事就是分不清编辑器和编译器,以为DevC++、VSCode之类的就能把所有活全干了
编辑器(记事本、VSCode)是你写代码的地方 —— 它本质上只是个高级一点的打字软件;
编译器(我们接下来要装的 GCC)才是把代码翻译成程序的地方 —— 它才是让代码真正“跑起来”的那个“翻译官”。
所以顺序很清楚:
流程: ① 装编译器 → ② 配置环境(让电脑找得到它)→ ③ 纯终端跑通第一个程序 → ④ 搬进 VSCode 开始日常写代码
问答 · 为什么不是“直接装 VSCode”?
当然可以。但你会变成“只会按按钮的人” —— 程序跑不起来的时候,完全不知道发生了什么。 所以我们先用最“土”的办法:记事本 + 黑色终端窗口,把程序跑通一次。之后再搬进 VSCode,你会发现它到底替你做了什么,你一清二楚。
第1步 · 请一位翻译官:安装 MinGW {#install-mingw}
第0步的结论只有一句:你写的是高级语言,CPU 只看机器语言,中间必须有编译器来翻译。
那问题就很现实了 —— 该到哪里去下载编译器?
答案是去网上下一份 MinGW,解压到 C 盘(或者随便你喜欢的哪个位置) —— 这一步就干这一件事。
1.1 Windows 为什么不自带编译器
有些操作系统是自带编译器的:装好系统它就已经在里面躺着了,打开终端直接能用。Windows 没有这个待遇。

Windows
原因不难理解。Windows 的用户绝大多数是拿它办公、追剧、打游戏的,写代码只占很小一部分。微软自己也有一整套开发工具(Visual Studio),但那是几个 GB 的大件,默认不会塞进系统。
所以在 Windows 上写 C / C++,第一步永远是安装一个编译器。
下面 1.2 到 1.5 讲的就是windows系统如何安装编译器。
macOS
Mac 属于“自带”那一类:苹果把 Clang 这套编译器随系统一起装好了,只是默认锁着,要用一条命令解锁:
在终端里执行
xcode-select --install
敲完会弹出对话框,点 安装、同意许可协议,等它把命令行工具下完(几百 MB,需要点时间)就绪。之后 clang、clang++、lldb 都能直接用了。
说明 · Mac 上的名字对照
编译器叫
clang/clang++(不是gcc/g++),调试器叫lldb(不是gdb)。 本文后面凡是出现gcc、g++、gdb的地方,Mac 读者按这张表替换 —— 每个涉及命令的标签页里都会再提醒一次。
备注 · Mac 系统请看这里
本节 1.2 到 1.5(下载 MinGW、解压、配置 PATH)整段与你无关,可直接跳转至第2步。
1.2 MinGW-w64 到底是什么
MinGW 是 Minimalist GNU for Windows 的缩写,直译是“给 Windows 用的最小化 GNU”。
GNU 是个自由软件项目,最出名的作品是 GCC(GNU Compiler Collection,GNU 编译器套装)—— 一套能编译 C、C++、Fortran 的编译器。它最初并不是为 Windows 准备的,于是有人做了件事:把 GCC 搬到 Windows 上。这个搬运工作的产物,就叫 MinGW。
MinGW-w64 是它的一个分支:最早的 MinGW 只面向 32 位系统,后来有人补上了 64 位支持,一直维护到今天。现在网上下到的“MinGW”,基本都是 MinGW-w64。
说明 · 工具链
MinGW-w64 不是一个软件,而是一整箱工具。它们衔接起来像一条流水线,所以这种组合被叫作工具链(toolchain)。 一次编译要好几个小工具接力,
gcc只是站在最前面的总指挥。
这里我们要认识以下三个程序:
工具 | 全称 | 干什么 | 什么时候用 |
|---|---|---|---|
| GNU C Compiler | 把 C 语言代码翻译成可执行程序 | 写 |
| GNU C++ Compiler | 把 C++ 代码翻译成可执行程序 | 写 |
| GNU Debugger | 调试:让程序停下来、一步步走、看变量 | 程序跑不对时 |
一句话记法:gcc 管 C,g++ 管 C++,gdb 管找 bug。
备注 · MinGW 不是 IDE
它没有窗口、没有菜单、也没有“运行”按钮,就是几个命令行的
.exe。
1.3 去 winlibs.com 下载
MinGW-w64 是开源项目,可在 WinLibs 网站下载—— 它把 MinGW-w64 预编译打包好,下压缩包解压就能用。
WinLibs 官网 —— 下载预编译好的 MinGW-w64

打开页面往下滚,找到 Release versions 那一段。它分成 UCRT runtime 和 MSVCRT runtime 两组 —— 这是两套不同的系统运行库,UCRT 更新、更贴近现代 Windows,新手直接选 UCRT。
每个版本下面会列出两种架构、两种压缩格式,规则一张表说清:
页面上看到的选项 | 选哪个 | 为什么 |
|---|---|---|
UCRT runtime / MSVCRT runtime | UCRT runtime | 更新,更贴近现代 Windows 的系统库 |
Win32 - i686 / Win64 - x86_64 | Win64 - x86_64 | 现在的电脑基本都是 64 位 |
7-Zip archive / Zip archive | Zip archive | Windows 自带解压,不用额外装 7-Zip |
标着 LATEST 的就是最新版。下载下来的文件名很长,形如:
下载到的文件大致长这样
winlibs-x86_64-posix-seh-gcc-16.2.0-mingw-w64ucrt-14.0.0-r1.zip
数字会随版本更新而变,认准两个词就行:x86_64(64 位)和 ucrt(UCRT 运行库)。
注意 · 别被“一键安装包”骗了
网上还能搜到第三方打的“MinGW 一键安装版”,能用,但来源和版本都不好确认,甚至还TM需要付费?
比如某SDN。WinLibs 的压缩包解压即用,更干净也更省心。
1.4 解压到 C:\mingw64
备注 · 你也可以自己选一个目录,不过后续配置 PATH 时要用你自己选好的安装目录
在 C 盘根目录新建一个文件夹,取名 mingw64,完整路径就是 C:\mingw64。右键压缩包选 全部解压缩,解压到这里。解压完检查一下,里面应该长这样:
- name: C:/mingw64
type: dir
children:
- name: bin
type: dir
children:
- name: gcc.exe
type: file
- name: g++.exe
type: file
- name: gdb.exe
type: file
- name: include
type: dir
- name: lib
type: dir
- name: libexec
type: dir
- name: share
type: dir
- name: x86_64-w64-mingw32
type: dir

目录名不用记,认准一个就行:bin。点进去会看到一大堆 .exe,其中就有 gcc.exe、g++.exe 和 gdb.exe。
注意 · 两条铁律:路径不含中文,也不含空格
安装位置必须是
C:\mingw64这种干干净净的路径,别放进D:\我的软件\C++ 工具\这类地方。 编译器和构建工具处理这类路径时容易出错,更麻烦的是报错信息会因为编码问题变成乱码,你连错在哪都看不出来。
备注 · 解压出来多套了一层目录?
有些包会变成
C:\mingw64\mingw64\bin。把里层的东西全部剪切出来,让路径回到C:\mingw64\bin这一层就行。
1.5 验收:敲一下 g++,结果报错了
工具链已经安装完毕,该验收一下它到底能不能干活。
先按 Win + R,输入 cmd,回车,你会看到一个黑底白字的窗口。(终端和 PowerShell 是什么,下一节讲;现在只要会用这个窗口。)在里面敲这一行:
在终端里执行
g++ --version
正常的话,屏幕上会打印出一串版本信息。但现在你多半会看到这一行:
终端里的报错
'g++' 不是内部或外部命令,也不是可运行的程序或批处理文件。
这行字你也许已经见过 —— 现在轮到你亲手把它敲出来。
提示 · 这是预期之内的,不是你哪一步做错了
编译器确实已经安装到你的电脑上了:
C:\mingw64\bin\g++.exe就安装在你的硬盘上。 只是你还没把它的住址告诉系统。操作系统会在几个默认位置找一个叫g++的程序,没找着,只好回你“不是内部或外部命令”。 东西在,路没通 —— 就这么简单。
第2步 · 让电脑找到它:环境变量与 PATH
2.1 明明装好了,系统凭什么说“找不到”
想象一间工作室。你把扳手放进了新买的三层抽屉,第二天让助手去拿,他翻了半小时回来说:“我们这儿没有扳手。”
助手没错。他手里有一份工具清单,上面写的是“扳手在工作台左边第二个柜子”。你换了地方,清单没更新 —— 他不认识你的新抽屉,只认清单。
Windows 面对你敲下的 g++ 就是这个助手。它手里也有一份“去哪里找程序”的清单,上面没有 C:\mingw64\bin,自然找不到 g++.exe,只能回你一句“不是内部或外部命令”。

提示 · 这不是安装失败
报错里没有一个字提到“安装”,因为安装那一步完全成功了。 你要做的不是重装,而是更新那份清单。
2.2 环境变量到底是什么
那份清单,学名就叫环境变量。
说明 · 环境变量
环境变量是一组 “名字 = 值” 的配置,由操作系统统一保管。程序在启动的那一刻,会把这批配置读进自己脑子里。 它不属于某一个程序,而是整个系统共享的一份公共设置。
举两个你天天在用、却从没注意过的例子:
环境变量 | 值大概是 | 谁在读它 |
|---|---|---|
|
| 想知道“当前用户的文件夹在哪”的软件 |
|
| 想找地方放临时文件的软件 |
它们的存在让程序不必把路径写死:换个用户名,读到的值就跟着变,代码一行都不用改。说白了,环境变量是系统启动程序时顺手递过去的一张纸条。
2.3 PATH:系统的程序通讯录
环境变量有很多个,但只有一个决定“你敲的命令能不能跑起来”。它叫 Path(习惯写作 PATH)。
说明 · PATH
PATH 的值是一串文件夹路径,路径之间用英文分号
;隔开。 它只干一件事:告诉系统,敲下命令之后该去哪些文件夹里找这个程序。
你敲下 g++ --version 的那一瞬间,系统做的是:
把
g++补全成g++.exe—— Windows 上的程序都带这个后缀;从上到下、按顺序去 PATH 里的每一个文件夹看一眼:“这里有
g++.exe吗?”一旦找到,立刻停手,执行它;
整份清单翻完还是没有,就打印那行“不是内部或外部命令”。

找到就停,说明 PATH 的顺序会影响结果;全都没有才报错,说明那行报错的含义只有一个 —— 你的程序不在清单上的任何一个文件夹里。
备注 · 那终端里的
cmd、notepad、ping命令为什么直接就能用?因为
C:\Windows\system32本来就躺在 PATH 里。
2.4 动手:把 C:\mingw64\bin 交给 PATH
原理讲完了,动手只有一件事:在 PATH 清单末尾追加一行 C:\mingw64\bin。
备注 · Mac 读者:这一节只看不做
Mac 装上命令行工具之后,
clang已经在 PATH 里了,不需要手动配置。 2.4 和 2.5 的动手步骤只针对 Windows,你可以直接跳到 2.6。
注意 · 加的是 bin,不是 mingw64
要交给系统的是装着
.exe的那个文件夹,也就是C:\mingw64\bin。 写成C:\mingw64没有用 —— 那个文件夹里没有g++.exe。
按 Win 键,输入 环境变量 四个字,搜索结果里点 编辑系统环境变量。

在“系统属性”窗口右下角点 环境变量(N)...。新窗口分成上下两半:上半部分是“用户变量”,我们操作这一半。
在上半部分的列表里选中
Path那一行;点它下面的 编辑;
在新窗口里点 新建,列表末尾会多出一个空行;
把
C:\mingw64\bin粘进去(Ctrl + V);逐层点“确定”,直到所有窗口都关掉。
注意 · 注意!
① 只点右上角的叉等于白干。 那个叉的含义是“放弃修改”,必须逐层按下 确定,改动才会真正写进去。 ② 改完必须把终端和 VSCode 完全关掉再重开。 环境变量是程序启动的那一刻读进去的,已经打开着的程序拿的还是旧清单(旧PATH)。
{{< details summary="“用户变量”和“系统变量”,我到底该选哪个?" >}} 用户变量只对当前登录的账号生效;系统变量对这台电脑上的所有用户生效。
自己电脑自己用,选用户变量就够了:更安全,不会影响别人的账号,改错了也更好收拾。这也是本文推荐的做法。只有当这台电脑还有别人也要用 g++ 时,才考虑系统变量 —— 那通常需要管理员权限。
2.5 验收:这次真的成功了
先把那个黑窗口彻底关掉,再按 Win + R、输入 cmd 回车,开一个全新的窗口,敲这两行:
在终端里执行
g++ --version
gdb --version
配置对了的话,屏幕会打印两串版本信息:
终端输出
g++ (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders) 16.2.0
Copyright (C) 2026 Free Software Foundation, Inc.
gdb --version 的格式类似,只是换成它自己的版本号。看到版本号,这块石头就落地了。
没有消息就是最好的消息。 命令行只有在出错时才会说话。
这句话值得记很久:真正成功的命令往往一个字都不打印,终端安静地什么都不说,反倒说明一切正常。
验收清单:
“用户变量”的
Path里多了一行C:\mingw64\bin每一层窗口都是点 确定 关掉的,不是点叉
关掉了旧终端,重新开了一个
g++ --version打印出了版本号gdb --version打印出了版本号
成功 · 到这里,你的电脑已经是一台能写 C 的电脑了
编译器已经安装,操作系统也认得了它的名字。接下来只差一件事:写一个真正的程序。
2.6 顺便,把终端彻底搞明白
环境变量配好了,你和编译器之间还隔着一个东西 —— 那个黑色窗口,也就是终端。往后你的每一次编译、每一次调试都在这里发生。趁现在把它一次讲透。

2.6.1 终端、控制台、Shell、cmd、PowerShell 都是什么?
一张表拆开这几个词:
名词 | 是什么 | 一句话记法 |
|---|---|---|
终端(Terminal) | 你看到的那个窗口,显示文字、接收键盘输入 | 它是壳 |
控制台(Console) | 更早的说法,概念上和终端基本重叠 | 老名字,知道就行 |
Shell | 终端里真正读懂并执行你敲的命令的那个程序 | 它是里子 |
cmd | Windows 传统的 shell,从 DOS 时代继承下来 | 里子 A |
PowerShell | 更新的 shell,命令风格和 cmd 不一样 | 里子 B |
终端是壳,shell 是里子;cmd 和 PowerShell 是 Windows 上两种不同的里子。

上面这张图把这几层叠在一起看:最外层那个黑框是终端(壳),壳里真正执行命令的是 Shell(里子),而同一个窗口同一时刻只装一种里子 —— 要么 cmd,要么 PowerShell。想分辨自己站在哪一种里子里,看提示符最快:C:\Users\你> 是 cmd,PS C:\Users\你> 是 PowerShell。
提示 · 那本文的命令用哪个敲?
两个都能用。 后面的
g++、gcc、gdb在 cmd 和 PowerShell 里都跑得通。只有一处写法不同,就在 2.6.4 表格的最后一条。
建议以后在windows系统上只用powershell,因为cmd功能相对残缺,语法也与现代shell有差异,只是历史遗留下来的程序。
2.6.2 打开终端的四种方式
方式 | 怎么操作 | 适合什么场景 |
|---|---|---|
| 按下后输入 | 最快,但起始目录是用户文件夹,还得自己 |
资源管理器地址栏 | 打开文件夹,在地址栏里输入 | 想在某个具体目录干活,最省事的一种 |
| 文件夹空白处按住 Shift 点右键,选 在此处打开 PowerShell 窗口 / 在终端中打开 | 同上,但更直观 |
地址栏输入 | 同第 2 种,换成 PowerShell | 想用 PowerShell 时 |
记住一条就够用:在哪个文件夹里开终端,终端就默认待在哪个文件夹。 学会第 2 种,你几乎不用再手敲 cd 了。
2.6.3 第一批必须会的命令
调出你系统的那一栏,照着敲几遍就熟了:
Windows
命令 | 作用 | 什么时候会用到 |
|---|---|---|
| 切换到某个目录 | 想进 |
| 返回上一级目录 | 走错文件夹了 |
| 列出当前目录里的所有内容 | 确认 |
| 清屏 | 输出刷了一大堆,想从干净的一屏重新开始 |
| 新建文件夹 | 给新作业建一个目录 |
| 输出文字(配合 | 快速造一个测试用的小文件 |
| 显示文本文件的内容 | 不想打开记事本,只想瞄一眼代码 |
注意 ·
cd换盘必须加/d
cd D:\code\c在 cmd 里不会跳过去 —— 它只是记下了这个路径,当前盘还是 C 盘。跨盘切换必须写cd /d D:\code\c。 这个/d是 cmd 特有的坑,第一次遇到基本都会踩。
macOS
命令 | 作用 | 什么时候会用到 |
|---|---|---|
| 切换到某个目录 | 想进 |
| 返回上一级目录 | 走错文件夹了 |
| 列出当前目录里的所有内容 | 确认 |
| 显示当前目录的完整路径 | 迷路了,想知道自己身在何处 |
| 清屏 | 输出刷了一大堆,想从干净的一屏重新开始 |
| 新建文件夹 | 给新作业建一个目录 |
| 输出文字(配合 | 快速造一个测试用的小文件 |
| 显示文本文件的内容 | 不想打开编辑器,只想瞄一眼代码 |
提示 · Mac 上没有
/d那个坑路径从
/开始,没有 C 盘 D 盘的概念,cd想去哪就去哪,不用加任何开关。
2.6.4 让终端好用十倍的七个技巧
每一格都值得当场试一遍:
# | 技巧 | 为什么好用 |
|---|---|---|
1 | :(fa-solid fa-keyboard fi-c-blue): Tab 补全(强烈推荐) | 路径敲一半按一下 Tab 自动补齐;有多个候选时连按 Tab 会依次列出 |
2 | :(fa-solid fa-arrows-rotate fi-c-teal): 上下方向键翻历史 | 上一条命令按 ↑ 就回来了;cmd 里按 F7 还能弹出历史列表 |
3 | :(fa-solid fa-xmark fi-c-red): | 程序卡死、日志刷不停时掐掉它,比直接关窗口安全 |
4 | :(fa-solid fa-folder-open fi-c-amber): 路径含空格要加双引号 |
|
5 | :(fa-solid fa-arrow-right fi-c-violet): 把文件拖进终端 | 从资源管理器拖文件或文件夹到终端窗口,完整路径自动填好 |
6 | :(fa-solid fa-clipboard-list fi-c-green): 右键即粘贴 | 选中文字就是复制,点右键就是粘贴。cmd 里 |
7 | :(fa-solid fa-play fi-c-orange): 运行当前目录的程序 | cmd 里写 |
第 7 条先记住结论,下一节马上就会用到:你编译出 hello.exe 之后,会发现两种终端里运行它的写法不一样。.\ 的意思是“就在当前文件夹里找” —— PowerShell 出于安全考虑,默认不去当前目录找程序,所以必须显式指明。
到这里,你已经把两件最不“所见即所得”的事情啃下来了:安装编译器使电脑具备编译C/C++的能力,配置环境变量让操作系统能够找到编译器位置;终端那堆名词和命令,也从黑话变成了手上的工具。
现在只差最后一个动作 —— 让你的计算机真的跑出一行字来。
下一节,我们打开记事本(对,就是最普通的那个),写出人生第一个 .c 文件,用 gcc 把它变成 hello.exe,亲眼看着屏幕跳出 Hello, C!。
第3步 · 用最“土”的工具跑通第一个程序
编译器装好了,环境变量也配好了。现在,你写下的代码终于可以变成程序。
这一节我们故意不用任何编辑器插件、不按任何按钮 —— 一个记事本加一个黑色终端窗口,从头手敲一遍。看清了每一步在干什么,再搬进 VSCode 才不是照抄步骤。
3.1 用记事本写 hello.c
先给代码找个目录。打开文件资源管理器,进 D 盘,新建文件夹 code,再进去新建一个 c。你以后写的代码可以都放这里,路径就是 D:\code\c。
注意 · 路径里不要有中文和空格
编译器、调试器、构建脚本处理路径时,遇到空格容易把一整个路径当成两个参数拆开,遇到中文则可能因为编码对不上而找不到文件。
D:\code\c这种短、纯英文、无空格的路径最省心。桌面、我的文档、新建文件夹 (2)都属于“雷区”。

然后打开记事本,文件 → 新建,把下面这段代码附近进去:
hello.c 的完整内容
#include <stdio.h> // 引入标准输入输出
int main() {
printf("Hello, C!\n");
return 0;
}
注意 · 保存时的三个坑
新手第一次存 C 文件,基本都栽在这里。三个坑,一个都别踩:
坑 | 踩了会怎样 | 正确做法 |
|---|---|---|
保存类型 | 被存成 | 把保存类型改成所有文件 (*.*) |
文件名 | 只写了 | 文件名写全: |
编码 | 程序里的中文变成乱码 | 看右下角的编码,选 UTF-8 |
说明 · 记事本的编码在哪里看
新版记事本窗口的右下角状态栏会直接显示当前编码,点一下就能改;保存对话框里也有编码下拉框。
存好之后先别急着编译,回到终端,在 D:\code\c 里确认一下文件名:
在终端里敲 dir
D:\code\c 的目录
2026/09/15 20:14 312 hello.c
1 个文件 312 字节
看到干干净净的 hello.c,就可以往下走了。
警告 · 如果看到的是 hello.c.txt
说明保存类型没改。最快的补救办法是在终端里敲
ren hello.c.txt hello.c;更保险的是删掉它,改好保存类型后重新保存一次。
4.2 编译:一条命令把源码变成程序
现在,让第1步里那位“翻译官”正式上岗。在终端里敲:
在终端里执行
gcc -g hello.c -o hello
macOS
clang -g hello.c -o hello
然后——屏幕上什么都没有发生。
没有进度条,没有“编译成功”,终端只是安静地回到下一行等你敲新命令。这不是卡住了,这就是成功。
没有消息就是最好的消息。
再 dir 看一眼,D:\code\c 里已经多出一个 hello.exe。刚才那一行命令,把一份人类能读的文本,变成了一份 CPU 能执行的程序。
这一条命令背后,其实跑了四个程序
你敲的是 gcc,但 gcc 自己并不会翻译代码。它更像一个总调度:接到命令后,依次叫来四个小工具,一个接一个地干活。

阶段 | 干活的程序 | 它做了什么 |
|---|---|---|
预处理 |
| 把 |
编译 |
| 把 C 代码翻译成汇编代码 —— “翻译”就发生在这里 |
汇编 |
| 把汇编代码变成机器码,产出目标文件 |
链接 |
| 把你的代码和标准库、启动代码拼在一起,产出 |
备注 · 为什么非要拆成这么多步
根本原因就是提高复用性和可维护性。因为每一步都能单独复用:一百个源文件可以先各自汇编成
.o,改动了哪个就重新汇编哪个,最后统一链接 —— 大型项目靠这个才撑得住。一句话:gcc是总调度,四道工序各有专人。
注意 · 编译报错长什么样
报错开头通常是这样一串:
hello.c:8:15: error: ...,读法是文件 : 行号 : 列号。 一个实用习惯:从上往下改,先改第一个错误 —— 第一个错误经常会让编译器在后面看什么都像错的。
4.3 读懂这条命令的每一个字符
别急着往下敲,把刚才那行命令拆成四段看:
这一段 | 它在说什么 |
|---|---|
| 执行gcc编译器程序 |
| 在生成的可执行文件里额外塞进调试信息(行号、函数名、变量名) |
| 要翻译的源文件 |
| 指定输出文件名。 |
-o 后面写什么,产物就叫什么。 写 -o hello,Windows 自动补上 .exe 就是 hello.exe;写 -o myfirst,产物就是 myfirst.exe。
注意 · 少了 -g 这个参数,断点就是一句空话
其实
gcc hello.c -o hello也能编译、也能跑,但生成的程序里不含行号和变量名 —— gdb 拿到它只能看到一堆内存地址:break hello.c:12找不到第 12 行,print sum也不知道sum是什么。 所以记住这条判据:只要你还打算调试,-g就必须在。 代价只是可执行文件大一点点,运行速度不受影响。
{{< details summary="顺手记住:以后天天要用的几个参数" >}}
参数 | 作用 |
|---|---|
| 打开所有常用警告。它不阻止编译,但会提醒你“这里很可能写错了” |
| 开启二级优化,让程序跑得更快。调试时不要用 —— 优化会打乱代码和行号的对应关系 |
| 指定 C++ 标准的版本。用 C++ 时源文件是 |
所以等你熟练以后,那条命令大概率会长成这个样子:
熟练之后
gcc -g -Wall hello.c -o hello
说明 · 你可能已经发现了规律
命令行程序的不同行为都是通过
-加上某个英文字母或单词来实现的,比如对于gcc编译器来说,携带-o表明我要给编译好的文件设置文件名,-Wall就是在编译时提供警告信息,如果不加则不提供。这种通过携带参数(flag)来指定不同行为的用法,适用于所有的命令行工具。
不清楚具体的参数和行为时,大多可以用
--help或-h来查看具体的参数行为,比如在终端中输入gcc --help后回车。
4.4 运行它

运行一个程序,不同终端里的敲法是不一样的:
终端 | 你要敲的 |
|---|---|
cmd(Windows 传统命令提示符) |
|
PowerShell(Windows 新版终端) |
|
macOS 的 zsh |
|
在 cmd 里敲 hello,它会去当前目录找 hello.exe 执行。PowerShell 则为了安全,不会自动去当前目录找程序(防止你误执行一个同名的恶意程序),所以必须显式写出 .\,意思是“就在当前目录这里”。
提示 · 记不住就只记这一条
运行当前目录里的程序,前面统一加
.\(Mac 上是./)—— 这条在 cmd 里同样成立。 另外,exe后缀可以省略,Windows 会自动补上;Mac 编译出来的可执行文件本来就没有后缀。
敲回车之后:
程序跑起来的样子
Hello, C!
到这里,你的第一个 C 程序真的跑通了。
4.5 让程序停下来给你看:认识 gdb
4.5.1 为什么需要调试器
最原始的办法是在代码里到处插 printf,把中间结果打出来看。这招叫“printf 大法”,能用但很笨:改一次要重新编译一次,看完还得把 printf 一条条删掉。
调试器解决的就是这件事 —— 让程序在半路上停下来,然后慢慢看:
断点:告诉程序“跑到这一行就停”
单步:一次只往前走一行
查看变量:随时问一句“现在
sum是多少”
这三件套是用过就回不去的。而 gdb 就是第1步里跟着 MinGW 一起装进来的那个调试器 —— 之前 gdb --version 能打印版本号,说明它已经安装到你的硬盘上。

4.5.2 调试器的四条命令
调来调去,真正要记的就四条。选你系统那一栏 —— 两个系统的命令名不一样,别照抄错栏:
gdb
# | 命令 | 简写 | 干什么 |
|---|---|---|---|
1 |
| — | 启动调试器,把程序交给它 |
2 |
|
| 在 |
3 |
|
| 让程序跑起来,撞到断点就停 |
4 |
|
| 执行下一行(不钻进函数里面去) |
看完了想退出,敲 quit(简写 q)就行。
lldb
Mac 的调试器叫 lldb,思路和 gdb 完全一致,但下断点和查看变量的写法不一样:
# | 命令 | 简写 | 干什么 |
|---|---|---|---|
1 |
| — | 启动调试器,把程序交给它 |
2 |
|
| 在 |
3 |
|
| 让程序跑起来,撞到断点就停 |
4 |
|
| 执行下一行(不钻进函数里面去) |
run、r、n、q 在 lldb 里是为 gdb 老用户保留的兼容别名,可以放心用;只有 break 和 print 得按上表换写法。
下面那段会话示例以 gdb 为例,Mac 读者把命令换成这一栏就行,看到的现象是一样的。
先跑通最基础的三步 —— 启动、下断点、运行:
启动 → 下断点 → 运行
D:\code\c> gdb hello.exe
GNU gdb (GDB) 14.2
Copyright (C) 2023 Free Software Foundation, Inc.
(后面还有几行版本与许可信息,这里略过)
(gdb) break hello.c:4
Breakpoint 1 at 0x140001533: file hello.c, line 4.
(gdb) run
Starting program: D:\code\c\hello.exe
Hello, C!
有三件事值得注意:
(gdb)是调试器的提示符。 它后面的命令是敲给调试器听的,不是敲给你的程序的 —— 这一点非常容易搞混。地址那串十六进制数每台机器都不一样,不用管。看到
Breakpoint 1 at ...: file hello.c, line 4.就说明断点下好了。程序停在了第 4 行之前。 那一行还没执行。
备注 · 行号对不上了怎么办
break hello.c:4里的 4,就是你文件里printf("Hello, C!\n");那一行的行号,空行不一样它就不一样。 对着上面那份带行号的代码数一下,或者在 gdb 里敲list,它会把当前位置附近的代码连行号一起列出来。
4.6 到这里,你已经掌握了“原理层”
回头看看,这一板块里你真正拿到手的东西:
会用记事本写出能编译的源文件,并且知道保存类型 / 后缀 / 编码这三个坑
会用
gcc -g hello.c -o hello把源码变成可执行文件,并知道背后跑了预处理 → 编译 → 汇编 → 链接四道工序说得清
-g给了你什么:没有调试信息,断点就是空话会在 cmd 和 PowerShell 里分别正确地运行当前目录的程序
会用 gdb 下断点、单步、查看变量。
这条链路 —— 源码 → 可执行文件 → 运行 → 调试 —— 是后面所有工具的地基。VSCode、Visual Studio、CLion,做的都是同一件事,只是把这套动作包在了按钮后面。
问答 · 可是……每次都要手敲这么长的命令,不累吗?
累。而且命令只会越来越长:
gcc -g -Wall -fexec-charset=GBK hello.c -o hello,敲错一个字符就得重来。gdb 的纯文本界面也确实不直观 —— 想看一个变量怎么变,得反复敲next和
接下来就是通过使用VScode,搞定大一期间所有的关于C/C++的代码编写…………
参考文献
下面这些链接都是本文提到的工具的官方来源,需要查证细节时首选它们。
WinLibs — GCC + MinGW-w64 for Windows —— 下载本文使用的编译器,附版本与运行时说明
完整清单:
MinGW-w64 官网 —— 编译器背后那套 Windows 头文件和运行库的官方项目页。想知道“MinGW 到底是什么”时,从这一页开始。
Pre-built Toolchains · mingw-w64 —— 官方列出的各家预编译工具链,WinLibs 就在名单里;这一页也把 UCRT 与 MSVCRT 两种运行时的区别讲清楚了,选版本犯难时来查。
GCC online documentation —— 所有
gcc参数(-g、-Wall、-O2、-std=……)的权威出处。网上搜到的参数解释拿不准,就来手册里对一遍。GDB Documentation ——
break、run、next、print的完整手册。Environment Variables · Win32 文档 —— 环境变量的官方说明:为什么程序在启动那一刻才读到它们。第2步里讲的现象,这里能看到原理层面。
C 参考手册 · cppreference(中文) —— 遇到
printf、scanf、for这些语法细节想查准确写法时最省事的一个中文站点。注意:它是参考手册,不是教程,适合“我知道要用什么、只想确认怎么用”的时候翻。