〇、从机器语言到 Hello World

青梧·2026-09-29 16:19

摘要 · 这篇文章能帮你解决什么?

如果你对编程毫无概念,或者只有一些零碎的了解;如果你照着老师给的步骤走完了,却完全不知道这些操作对自己的电脑做了什么;如果你只会用 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,相加)这样的助记符出现了。刚才那条机器指令就被写成了一行:

机器语言

汇编语言

含义

B8 01 00 00 00

mov eax, 1

把 1 放进 eax

83 C0 02

add eax, 2

让 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 只是站在最前面的总指挥。

这里我们要认识以下三个程序:

工具

全称

干什么

什么时候用

gcc

GNU C Compiler

把 C 语言代码翻译成可执行程序

写 .c 文件时

g++

GNU C++ Compiler

把 C++ 代码翻译成可执行程序

写 .cpp 文件时

gdb

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

C:ingw64 目录结构示意图,bin 文件夹高亮,右侧列出 gcc、g++、gdb 三个工具各自的用途

目录名不用记,认准一个就行: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 环境变量到底是什么

那份清单,学名就叫环境变量。

说明 · 环境变量

环境变量是一组 “名字 = 值” 的配置,由操作系统统一保管。程序在启动的那一刻,会把这批配置读进自己脑子里。 它不属于某一个程序,而是整个系统共享的一份公共设置。

举两个你天天在用、却从没注意过的例子:

环境变量

值大概是

谁在读它

USERPROFILE

C:\Users\你的用户名

想知道“当前用户的文件夹在哪”的软件

TEMP

C:\Users\你的用户名\AppData\Local\Temp

想找地方放临时文件的软件

它们的存在让程序不必把路径写死:换个用户名,读到的值就跟着变,代码一行都不用改。说白了,环境变量是系统启动程序时顺手递过去的一张纸条。

2.3 PATH:系统的程序通讯录

环境变量有很多个,但只有一个决定“你敲的命令能不能跑起来”。它叫 Path(习惯写作 PATH)。

说明 · PATH

PATH 的值是一串文件夹路径,路径之间用英文分号 ; 隔开。 它只干一件事:告诉系统,敲下命令之后该去哪些文件夹里找这个程序。

你敲下 g++ --version 的那一瞬间,系统做的是:

  1. 把 g++ 补全成 g++.exe —— Windows 上的程序都带这个后缀;

  2. 从上到下、按顺序去 PATH 里的每一个文件夹看一眼:“这里有 g++.exe 吗?”

  3. 一旦找到,立刻停手,执行它;

  4. 整份清单翻完还是没有,就打印那行“不是内部或外部命令”。

PATH 查找原理图:系统按顺序翻 PATH 清单,前几个文件夹都没有 g++.exe,在 C:ingw64in 里找到了

找到就停,说明 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)...。新窗口分成上下两半:上半部分是“用户变量”,我们操作这一半。

  1. 在上半部分的列表里选中 Path 那一行;

  2. 点它下面的 编辑;

  3. 在新窗口里点 新建,列表末尾会多出一个空行;

  4. 把 C:\mingw64\bin 粘进去(Ctrl + V);

  5. 逐层点“确定”,直到所有窗口都关掉。

注意 · 注意!

① 只点右上角的叉等于白干。 那个叉的含义是“放弃修改”,必须逐层按下 确定,改动才会真正写进去。 ② 改完必须把终端和 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 是两种可以互换的 Shell

上面这张图把这几层叠在一起看:最外层那个黑框是终端(壳),壳里真正执行命令的是 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 打开终端的四种方式

方式

怎么操作

适合什么场景

Win + R

按下后输入 cmd 或powershell 回车

最快,但起始目录是用户文件夹,还得自己 cd

资源管理器地址栏

打开文件夹,在地址栏里输入 cmd或powershell 回车

想在某个具体目录干活,最省事的一种

Shift + 右键

文件夹空白处按住 Shift 点右键,选 在此处打开 PowerShell 窗口 / 在终端中打开

同上,但更直观

地址栏输入 powershell

同第 2 种,换成 PowerShell

想用 PowerShell 时

记住一条就够用:在哪个文件夹里开终端,终端就默认待在哪个文件夹。 学会第 2 种,你几乎不用再手敲 cd 了。

2.6.3 第一批必须会的命令

调出你系统的那一栏,照着敲几遍就熟了:

Windows

命令

作用

什么时候会用到

cd 路径

切换到某个目录

想进 D:\code\c 干活:cd /d D:\code\c

cd ..

返回上一级目录

走错文件夹了

dir

列出当前目录里的所有内容

确认 hello.exe 到底生成了没有

cls

清屏

输出刷了一大堆,想从干净的一屏重新开始

mkdir 名字

新建文件夹

给新作业建一个目录

echo 文字

输出文字(配合 > 还能写进文件)

快速造一个测试用的小文件

type 文件名

显示文本文件的内容

不想打开记事本,只想瞄一眼代码

注意 · cd 换盘必须加 /d

cd D:\code\c 在 cmd 里不会跳过去 —— 它只是记下了这个路径,当前盘还是 C 盘。跨盘切换必须写 cd /d D:\code\c。 这个 /d 是 cmd 特有的坑,第一次遇到基本都会踩。

macOS

命令

作用

什么时候会用到

cd 路径

切换到某个目录

想进 ~/code/c 干活:cd ~/code/c

cd ..

返回上一级目录

走错文件夹了

ls

列出当前目录里的所有内容

确认 hello 到底生成了没有

pwd

显示当前目录的完整路径

迷路了,想知道自己身在何处

clear

清屏

输出刷了一大堆,想从干净的一屏重新开始

mkdir 名字

新建文件夹

给新作业建一个目录

echo 文字

输出文字(配合 > 还能写进文件)

快速造一个测试用的小文件

cat 文件名

显示文本文件的内容

不想打开编辑器,只想瞄一眼代码

提示 · 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): Ctrl + C 强制中断

程序卡死、日志刷不停时掐掉它,比直接关窗口安全

4

:(fa-solid fa-folder-open fi-c-amber): 路径含空格要加双引号

cd "D:\My Code\c"。不加引号,空格会被当成两个参数的分界

5

:(fa-solid fa-arrow-right fi-c-violet): 把文件拖进终端

从资源管理器拖文件或文件夹到终端窗口,完整路径自动填好

6

:(fa-solid fa-clipboard-list fi-c-green): 右键即粘贴

选中文字就是复制,点右键就是粘贴。cmd 里 Ctrl + C 是中断,不是复制

7

:(fa-solid fa-play fi-c-orange): 运行当前目录的程序

cmd 里写 hello;PowerShell 里必须写 .\hello;Mac 上是 ./hello

第 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 文件,基本都栽在这里。三个坑,一个都别踩:

坑

踩了会怎样

正确做法

保存类型

被存成 hello.c.txt,后缀变成 .txt,编译器根本不认这个文件

把保存类型改成所有文件 (*.*)

文件名

只写了 hello,存成没有后缀的文件

文件名写全:hello.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 自己并不会翻译代码。它更像一个总调度:接到命令后,依次叫来四个小工具,一个接一个地干活。

编译流程:预处理、编译、汇编、链接四个阶段,每一步的输入与产物

阶段

干活的程序

它做了什么

预处理

cpp

把 #include 展开成真正的头文件内容,把宏替换掉

编译

cc1

把 C 代码翻译成汇编代码 —— “翻译”就发生在这里

汇编

as

把汇编代码变成机器码,产出目标文件 hello.o

链接

ld

把你的代码和标准库、启动代码拼在一起,产出 hello.exe

备注 · 为什么非要拆成这么多步

根本原因就是提高复用性和可维护性。因为每一步都能单独复用:一百个源文件可以先各自汇编成 .o,改动了哪个就重新汇编哪个,最后统一链接 —— 大型项目靠这个才撑得住。一句话:gcc 是总调度,四道工序各有专人。

注意 · 编译报错长什么样

报错开头通常是这样一串:hello.c:8:15: error: ...,读法是文件 : 行号 : 列号。 一个实用习惯:从上往下改,先改第一个错误 —— 第一个错误经常会让编译器在后面看什么都像错的。

4.3 读懂这条命令的每一个字符

别急着往下敲,把刚才那行命令拆成四段看:

这一段

它在说什么

gcc

执行gcc编译器程序

-g

在生成的可执行文件里额外塞进调试信息(行号、函数名、变量名)

hello.c

要翻译的源文件

-o hello

指定输出文件名。-o 就是 output 的缩写

-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="顺手记住:以后天天要用的几个参数" >}}

参数

作用

-Wall

打开所有常用警告。它不阻止编译,但会提醒你“这里很可能写错了”

-O2

开启二级优化,让程序跑得更快。调试时不要用 —— 优化会打乱代码和行号的对应关系

-std=c++17

指定 C++ 标准的版本。用 C++ 时源文件是 hello.cpp,命令要把 gcc 换成 g++

所以等你熟练以后,那条命令大概率会长成这个样子:

熟练之后

gcc -g -Wall hello.c -o hello

说明 · 你可能已经发现了规律

命令行程序的不同行为都是通过-加上某个英文字母或单词来实现的,比如对于gcc编译器来说,携带-o表明我要给编译好的文件设置文件名,-Wall就是在编译时提供警告信息,如果不加则不提供。

这种通过携带参数(flag)来指定不同行为的用法,适用于所有的命令行工具。

不清楚具体的参数和行为时,大多可以用--help或-h来查看具体的参数行为,比如在终端中输入gcc --help后回车。

4.4 运行它

一台电脑屏幕上冒出由圆点和线条组成的小烟花,桌上放着一杯冒热气的水

运行一个程序,不同终端里的敲法是不一样的:

终端

你要敲的

cmd(Windows 传统命令提示符)

hello

PowerShell(Windows 新版终端)

.\hello

macOS 的 zsh

./hello

在 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

gdb hello.exe

—

启动调试器,把程序交给它

2

break hello.c:4

b hello.c:4

在 hello.c 的第 4 行下一个断点

3

run

r

让程序跑起来,撞到断点就停

4

next

n

执行下一行(不钻进函数里面去)

看完了想退出,敲 quit(简写 q)就行。

lldb

Mac 的调试器叫 lldb,思路和 gdb 完全一致,但下断点和查看变量的写法不一样:

#

命令

简写

干什么

1

lldb hello

—

启动调试器,把程序交给它

2

breakpoint set --file hello.c --line 4

b hello.c:4

在 hello.c 的第 4 行下一个断点

3

run

r

让程序跑起来,撞到断点就停

4

next

n

执行下一行(不钻进函数里面去)

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 和 print。 下一篇文章,我们把这一切搬进 VSCode:让编辑器记住编译命令、一键编译运行、在某一行点一下就设好断点,还能在侧边栏里直接看到所有变量的值随着单步一起变化。 而到那时候你会发现 —— VSCode 并没有替你多做任何一件事,它只是把上面这些命令,翻译成了按钮。

接下来就是通过使用VScode,搞定大一期间所有的关于C/C++的代码编写…………


参考文献

下面这些链接都是本文提到的工具的官方来源,需要查证细节时首选它们。

WinLibs — GCC + MinGW-w64 for Windows —— 下载本文使用的编译器,附版本与运行时说明

完整清单:

  1. MinGW-w64 官网 —— 编译器背后那套 Windows 头文件和运行库的官方项目页。想知道“MinGW 到底是什么”时,从这一页开始。

  2. Pre-built Toolchains · mingw-w64 —— 官方列出的各家预编译工具链,WinLibs 就在名单里;这一页也把 UCRT 与 MSVCRT 两种运行时的区别讲清楚了,选版本犯难时来查。

  3. GCC online documentation —— 所有 gcc 参数(-g、-Wall、-O2、-std=……)的权威出处。网上搜到的参数解释拿不准,就来手册里对一遍。

  4. GDB Documentation —— break、run、next、print 的完整手册。

  5. Environment Variables · Win32 文档 —— 环境变量的官方说明:为什么程序在启动那一刻才读到它们。第2步里讲的现象,这里能看到原理层面。

  6. C 参考手册 · cppreference(中文) —— 遇到 printf、scanf、for 这些语法细节想查准确写法时最省事的一个中文站点。注意:它是参考手册,不是教程,适合“我知道要用什么、只想确认怎么用”的时候翻。