1. 为什么“g++、gcc、cmake安装”值得单独写一篇
g++、gcc、cmake安装这件事,看起来就是三条命令行的事,可真正动手之后,很多人会在第一步就被卡住:有的提示找不到命令,有的是装完了版本太老,还有的是 Windows 下装了半天,编译一个最简单的 hello world 都报链接错误。我当年也在这个环节浪费过不少时间,后来踩了几次坑,才算把各个平台上的安装逻辑理清楚。
先说结论:这三个东西不是同一类工具。gcc 是 C 语言编译器,g++ 是 C++ 语言编译器,cmake 则是跨平台的构建系统生成器。很多人把“安装编译环境”理解成“装一个软件”,其实是把三件事揉成了一件事。只有把它们的分工搞清楚,后续配置才不会乱。
这篇我不会只贴几条命令,而是把为什么这样装、装完怎么验证、出问题怎么排查都讲透。无论你用的是 Linux、Windows 还是 macOS,只要跟着把工具链理顺,就能从“能跑”阶段升级到“知道自己在跑什么”的阶段。
1.1 gcc 和 g++ 不是同一个东西
gcc 这个名称早年是 GNU C Compiler 的缩写,后来项目扩展成了 GNU Compiler Collection,里面包括了 C、C++、Fortran 等前端。g++ 是面向 C++ 的主编译驱动,它和 gcc 的关系有点像一个工厂里的两条专线:底层共享很多组件,但处理的语言规则、默认链接的库完全不一样。
拿最常见的 C++ 代码来说,如果你用 gcc 去编译一个.cpp文件,很多默认行为并不等于 g++。最典型的例子是链接stdc++库。g++ 在编译 C++ 源文件时会自动链接 C++ 标准库,而 gcc 不会。于是经常有人遇到这种诡异场景:用 gcc 编译一个简单 C++ 文件,编译阶段没报错,链接阶段却冒出一堆undefined reference to std::cout。原因就是没把 C++ 库链上。
所以日常使用请记住这个基本分工:.c文件用 gcc,.cpp文件用 g++。如果非要让 gcc 处理 C++,那也要手动补上-lstdc++,但我个人不建议这么折腾。开发时尽量让工具做自己擅长的事,能少踩一个坑就是一个。
1.2 cmake 的真正角色是什么
cmake 不是编译器,它不直接负责把源代码翻译成机器码。它更像一个“构建脚本翻译器”,它会读取CMakeLists.txt,根据当前系统的环境生成对应的本地构建文件。在 Linux 上生成的大多是 Makefile,在 Windows 上可能是 Visual Studio 工程,在 macOS 上可能是 Xcode 工程。
很多人第一次用 cmake 时都有个误会:以为cmake . && make里的两个命令是同一层级的工具。其实cmake和make是上下游关系。cmake 负责“规划”怎么构建,make 负责“执行”构建。你用cmake -S . -B build生成构建文件之后,再用cmake --build build来触发实际的编译,这一步内部可能是 make,也可能是 ninja,具体看配置。
理解这一点之后,你就会明白为什么安装 cmake 通常还要搭配 gcc/g++:cmake 在配置阶段会主动去寻找编译器。如果系统里根本没有 gcc、g++,cmake 就算装好了,也会在第一步报C compiler cannot create executables这类错误。所以装环境时,要先把 gcc/g++ 处理好,再装 cmake。
2. 安装前的准备工作:先摸清系统底细
我在帮别人排查环境问题时发现,大部分安装失败不是命令记错了,而是没搞清楚自己所在平台的包管理方式。同样是 Linux,Ubuntu 用 apt,CentOS 用 yum,Arch 用 pacman,命令完全不一样。装之前花两分钟确认系统,比装错后再卸载省心得多。
2.1 查看系统版本与 CPU 架构
Linux 下最稳妥的方式是看发行版信息:
cat /etc/os-release uname -m第一行会显示系统名称和版本号,第二行会告诉你 CPU 架构。常见结果中,x86_64是标准 64 位 Intel/AMD 架构,aarch64是 64 位 ARM 架构。这个信息很重要,因为有些包管理器会自动区分架构,而手动下载安装包时,选错架构几乎必然失败。
macOS 下用:
sw_vers uname -m在 Apple Silicon 机器上会显示arm64,旧款 Intel 一般是x86_64。两者的 Homebrew 安装路径不同,后面环境变量配置会受影响。
Windows 下没这么复杂,在命令行输入winver能看到系统版本,输入where gcc可以快速知道当前有没有把 MinGW 加进 PATH。我在实际排查中遇到很多 Windows 用户卡在 PATH 上,后面单独说。
2.2 先检查环境里是不是已经装过了
很多新手一上来就执行安装命令,结果系统提示“已安装最新版本”,然后下一步还是不管用。问题多半出在“装是装了,但不在 PATH 里”或者“装的版本太旧”。所以安装前先做一次体检:
gcc --version g++ --version cmake --version如果在 Linux 终端里没有任何输出,大概率是没装。如果有输出,仔细看一下版本号。比如gcc (Ubuntu 9.4.0),说明系统自带的是老版本,你要评估它能不能满足项目要求。
Windows 下如果是在 PowerShell 里执行,命令同样有效。如果命令提示“无法识别”,那就是 PATH 没配好。这时候先别急着找安装包,先想清楚:你需要的到底是真正的 Windows 原生工具链,还是可以在 Linux 子系统里直接用的链。不同选择会带来完全不同的安装路径,这个决策直接影响后续所有操作体验。
3. Linux 下安装:多数发行版就是一条命令的事
Linux 生态的包管理思路很直观:用官方仓库里的包,让系统统一管理版本和依赖。只要你不是在折腾特别新的语言特性,仓库里的 gcc/g++/cmake 通常够用。
3.1 Debian/Ubuntu 系:apt 一把梭
Debian/Ubuntu 的标准做法是:
sudo apt update sudo apt install build-essential cmake很多人不理解为什么是build-essential,而不是直接apt install gcc g++。原因是我前面说的:一个可用的编译环境不只是单个编译器,还包括 make、链接器、基础头文件、常用库,这些散件放在一起才能完成一次完整编译。build-essential就是把这套基础组合打包好了。正常情况下,它包含 gcc、g++、make、dpkg-dev 等核心组件。
安装完成后,我建议立刻验证:
gcc --version g++ --version cmake --version如果你看到三行正常输出版本号,环境就算通了。这里有个常见误区:apt install cmake装的版本可能比较保守。比如 Ubuntu 20.04 默认仓库里 cmake 可能是 3.16,而项目要求 3.20 以上。遇到这种需求,我会优先考虑加第三方源,而不是从源码编译 cmake,因为源码编译容易一团乱麻。
3.2 RHEL/CentOS/Fedora 系:yum/dnf 的组合用法
CentOS 或 RHEL 7 及更早版本,习惯用 yum:
sudo yum groupinstall "Development Tools" sudo yum install cmakeCentOS 8 之后和 Fedora,推荐用 dnf:
sudo dnf groupinstall "Development Tools" sudo dnf install cmakegroupinstall "Development Tools"这个动作对应 Ubuntu 里的build-essential,会把 gcc、g++、make 等一整套开发编译工具装齐。执行完后同样检查版本即可。
有一个细节值得留意:RHEL/CentOS 默认仓库里的 gcc 版本可能非常老,比如 CentOS 7 自带 gcc 4.8,这对现代 C++ 项目来说基本没法用。所以如果你在公司服务器上工作,建议先和同事确认项目要求的 GCC 版本底线,再决定是否要另外启用软件集合或高版本源,不要贸然覆盖系统自带工具链。
3.3 Arch/Manjaro 系:base-devel 是核心
Arch Linux 和 Manjaro 上安装:
sudo pacman -Syu sudo pacman -S base-devel cmake-Syu先同步并升级系统,避免因为本地数据库过旧导致包冲突。base-devel是 Arch 生态中的基础开发包组,里面包含 gcc、make、autoconf 等工具。对 Arch 用户来说,这几乎是装任何软件前的必备步骤。
Arch 的优势是默认仓库版本很新,甚至可以在得到最新 gcc 和 cmake。因此 Arch 用户很少遇到“版本太老”的问题,反而要注意的是不要盲目滚动升级,把某个 Python 或 Qt 依赖搞坏。我的习惯是升级前先看一眼新闻,等一会儿再执行。
3.4 多版本切换:用 update-alternatives 而不是手动改路径
Linux 下还有个常见需求是机器上同时装多个 GCC 版本。比如系统默认是 gcc 11,但项目 A 要 gcc 9,项目 B 要 gcc 12。直接改/usr/bin/gcc软链接是很危险的,因为系统里的很多工具链可能依赖特定版本。
Debian/Ubuntu 自带的update-alternatives就是为这种场景设计的。安装好多个 gcc 版本后,执行:
sudo update-alternatives --config gcc sudo update-alternatives --config g++终端会列出所有可用版本,你按数字选择即可。这套机制比手动删软链接安全得多,因为它能保持在系统层面统一管理,回滚也容易。如果你用 CentOS,则可以用scl软件集合来激活特定版本,思路也差不多:不要让所有依赖同一套路径的模块被迫切来切去。
4. Windows 下安装:不要再被 MinGW 绕晕
Windows 下安装 gcc/g++ 根本不是“双击安装包”这么简单。很多人下载了一个 MinGW 压缩包,解压后扔在某个目录,然后发现 gcc 命令根本用不了。原因在于 Windows 本身不像 Linux 那样自带编译工具链,你需要选一套“Windows 上的 GCC 移植版”,并且把可执行文件目录手动加入 PATH。
4.1 为什么不能直接用 Windows 自带的编译能力
微软不在 Windows 里预装 gcc/g++,这是它的商业策略决定的。Windows 上官方开发流程用的是 Visual Studio 里的 MSVC 编译器。MSVC 和 gcc 在 C/C++ 标准支持、头文件路径、展开宏的细节上都有差异,所以你在 Linux 上写得好好的代码,拿到 Windows 上用 MSVC 编译,经常会出现莫名其妙的兼容错误。
如果想保持和 Linux 尽可能一致的行为,最省力的方案是用 MinGW-w64 项目提供的 Windows 版 GCC。它提供了一整套 Windows 原生可执行文件,编译出来的程序可以直接在 Windows 上运行,不需要额外开虚拟机。
4.2 我的推荐方案:用 MSYS2 管理一切
MSYS2 是目前维护最积极、对新手最友好的 Windows 开发环境入口。它自带一个类似 Linux 的包管理工具pacman,你可以用它一键安装 MinGW-w64 工具链和 cmake,不必手动配置 PATH 里的每个软件。
安装步骤如下:
- 去 MSYS2 官网下载安装包,默认安装到
C:\msys64。 - 打开 MSYS2 的终端,先执行:
pacman -Syu这里可能会要求关闭终端再重开,继续升级一次。
- 安装工具链和 cmake:
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake- 把
C:\msys64\ucrt64\bin加到系统 PATH 环境变量。
加 PATH 这一步非常关键。如果你装的是 mingw-w64-ucrt 版本,可执行文件会放在ucrt64\bin下;如果选择的是 mingw-w64 传统目录,则可能是mingw64\bin。建议装完后先用where gcc看一下,确认当前命令行用的是不是 MSYS2 里的那个 gcc。
很多 IDE,比如 VS Code、CLion,都需要手动指定编译器路径。你在 IDE 里配置编译器时,直接指向C:\msys64\ucrt64\bin\gcc.exe和g++.exe就行了,不需要再去折腾额外的环境变量。
4.3 如果只是写作业或小项目,WSL 更省心
我有段时间在 Windows 上做 C++ 小项目,后来发现最稳定的方式根本不是配置原生 Windows 编译器,而是启用 WSL,在 WSL 里按照 Linux 的方式安装。只要在 Windows 命令行执行:
wsl --install装完之后进入 WSL 终端,再执行 Ubuntu 那套apt install build-essential cmake,整个环境就通了。这样做的最大好处是:你在 Windows 上写代码,用的是 Linux 下一模一样的 gcc/g++/cmake 行为,在线评测、部署到服务器时几乎不会出现“在我电脑上能跑”的问题。
当然 WSL 也有代价:文件读写跨系统时有性能损耗,需要调 WSL 的挂载路径;GUI 程序运行也没原生 Windows 方便。但如果你主要工作是做 C/C++ 算法练习、小工具、自动化脚本,WSL 的价值远大于折腾原生 MinGW。而且将来你在 Linux 服务器上写代码时,这种使用习惯也能直接迁移。
5. macOS 安装:Command Line Tools 是第一步
macOS 上做 C/C++ 开发,绕不开一个很反直觉的事实:系统默认装的编译驱动是 clang,不是 gcc。很多人兴致勃勃地在终端里敲gcc --version,发现输出里写着Apple clang version,就以为自己装好了 gcc,其实这只是系统把gcc命令指向了 clang。
5.1 先用 xcode-select 装命令行工具
在 macOS 上最标准的起点是执行:
xcode-select --install这会弹出一个安装窗口,下载并安装 Xcode Command Line Tools。装完后,你就能在终端里使用 clang、clang++,以及make等工具。它安装的是苹果自己的 LLVM 工具链,不是 GNU 的 gcc,但对大部分 C/C++ 项目来说,clang 的语法支持已经非常完整,日常写代码没问题。
问题出在 cmake。新的 Command Line Tools 自带 cmake 吗?不同版本不一样,而且很多时候不会自动把路径配好。所以下一步我会直接用 Homebrew 装自己需要的版本,避免依赖系统自带的隐秘路径。
5.2 用 Homebrew 安装 GNU gcc 和 cmake
如果你确实想用 GNU 版本的 gcc,可以先用 Homebrew 装起来。Homebrew 的默认核心仓库里有gcc这个包,但它安装的可执行文件会被命名为gcc-13、g++-13这样的版本后缀,不会顶替系统自带的gcc命令。这样做是为了避免破坏苹果系统依赖。
安装命令:
brew install gcc cmake装完后,你可以通过gcc-13 --version来确认版本。如果你希望gcc直接指向 GNU 版本,可以在.zshrc里加个别名:
alias gcc='gcc-13' alias g++='g++-13'但我不建议在系统层面直接替换,因为苹果的构建脚本,比如某些 Ruby gem 的 native 扩展,可能默认依赖 clang。你改了gcc指向,很可能会把那些依赖搞挂。更稳妥的做法是:系统gcc保留,新项目里显式用gcc-13或让 cmake 通过CC、CXX环境变量指定编译器。
我自己的习惯是,在 macOS 上如果只是写跨平台项目,直接让 cmake 用 clang,不做任何版本的强行替换;只有项目明确要求 GNU 扩展语法时,才在 CMakeLists 里指定编译器路径。
6. 装完怎么确认:写一个小项目把链路跑通
安装工具链最忌讳的就是只看版本号正常就认为万事大吉。版本号只代表可执行文件存在,不代表整个编译、链接、构建流程能顺畅走完。所以我每换一个环境,都会用一段最简单的代码和一个最小 CMake 工程做一次“通车测试”。
6.1 先用命令行验证 gcc/g++
先建一个文件:
#include <stdio.h> int main() { printf("hello gcc\n"); return 0; }在终端执行:
gcc hello.c -o hello ./hello能看到hello gcc,说明 C 编译链路没问题。
再用同样的方式测 C++:
#include <iostream> int main() { std::cout << "hello g++" << std::endl; return 0; }执行:
g++ hello.cpp -o hello ./hello能输出hello g++,说明 g++ 和 C++ 标准库都正常。这一步里如果出现undefined reference to std::cout,多半是对着.cpp文件用了gcc命令,把命令换成g++即可。
6.2 用 CMake 走一遍正规流程
命令行编译只能证明编译器工作,不能证明 cmake 的配置阶段能正确找到编译器。所以我会再建一个最小工程:
hello_cmake/ ├── CMakeLists.txt └── main.cppmain.cpp就是前面那个输出 hello 的 C++ 源文件。CMakeLists.txt写成这样:
cmake_minimum_required(VERSION 3.16) project(hello) add_executable(hello main.cpp)然后执行:
cmake -S . -B build cmake --build build ./build/hello如果能看到输出,说明 cmake 生成构建文件、调用编译器、完成链接、输出可执行文件的整条链路都通了。
这里多说一句-S和-B参数。-S指定源码目录,-B指定构建目录。把生成文件放到单独的build目录里,能保持源码目录干净,不会出现一堆.o文件和临时文件混杂在源代码里的情况。这也是现代 CMake 推荐的 out-of-source 构建方式,比早年的cmake .干净得多。
7. 实际使用中最容易踩的坑
环境安装只是开始,真正动手编译时,坑才会一个个冒出来。下面这几个问题我基本每个季度都能遇到一次,整理出来给你当排查手册用。
7.1 明明装了 gcc,编译 C++ 却报 “没有这个文件”
有次同学问我,为什么gcc main.cpp -o main会报cc1plus: fatal error: no input files或者stdio.h: No such file or directory。排查了一圈,发现他装的是纯 gcc,而不是完整的编译工具链。gcc 本身可以识别 C++ 源文件,但如果系统里没有对应的 C++ 头文件和标准库,前端能跑,编译就会断在头文件阶段。
所以如果你要开发 C++,请确认g++ --version也能正常输出。如果g++不存在,回到前面的安装步骤,把build-essential或base-devel装齐。不要只装 gcc 一个命令,那只是把编译器躯壳装上了,配套件还没到位。
还有一个陷阱:在某些精简版 Linux 环境里,build-essential可能没装全,gcc和g++版本还不一致。这种情况偶尔会出现,原因多半是你手动添加了第三方源或者卸载过系统包。解决办法也很简单:sudo apt install --reinstall build-essential,重新对齐一次。
7.2 CMake 报 “C compiler cannot compile a simple test program”
这个报错几乎是 cmake 新手的标配。它发生在cmake -S . -B build的配置阶段,cmake 会先编译一个小测试程序来验证编译器能不能用。如果这个阶段就失败,问题大概率集中在:
- 系统里没有 gcc/g++,cmake 找不到编译器。
- 有编译器,但 PATH 里被其他版本抢先了。
- 缺少基础的头文件,比如
stdio.h都不存在。
排查顺序我建议是:
- 先执行
gcc --version确认编译器可用。 - 再执行
cmake -S . -B build看具体是哪一步失败。 - 如果报错里有
-lstdc++相关链接错误,检查 g++ 是否可用。 - 如果 PATH 里有多个编译器,用
where gcc(Windows)或which -a gcc(Linux/macOS)看清楚实际用的是哪个。
还有一个隐藏原因:环境变量CC和CXX被设置成了不存在的路径。CMake 会优先读这两个环境变量来选编译器,一旦它们指向了错误位置,cmake 不会自动回退到gcc。处理方式是显式清空它们,或在 cmake 配置时强制指定:
cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -S . -B build不建议长期在CMakeLists.txt里写死编译器路径,那样项目换台机器就无法复用。命令行覆盖只用于排查和特殊场景。
7.3 想用新特性,但包管理器给的版本太旧
这类问题在老版系统上最常见。比如想在 CentOS 7 上用 C++17,系统自带 gcc 4.8 编译出来的代码可能直接秒报语法错误。你当然可以手动下载新版 gcc 源码编译,但我不推荐普通项目这么干,因为源码编译 gcc 非常耗时,而且一个配置失误就会影响系统其他包对旧编译器的依赖。
更稳妥的思路是:先评估项目能不能在特定发行版上构建,如果不能,再考虑以下优先级:
- 升级到新一点的发行版版本。
- 启用发行版官方维护的软件集合或测试源,在没有镜像顾虑的前提下使用。
- 在 Docker 容器里跑一个官方维护的现代开发镜像。
容器方案我现在用得最多。它可以把 gcc 版本、cmake 版本、依赖库全部锁在一个镜像里,本机只要装 Docker 就行,完全不污染开发机。需要哪个版本就换镜像 tag,项目结束后一键丢弃,非常干净。
7.4 Windows 命令行里能识别,但 IDE 还是报找不到编译器
这基本是 PATH 顺序问题。你在 PowerShell 里执行gcc --version能通过,是因为当前用户的 PATH 里有 MinGW。但 IDE 启动时可能继承了系统级的 PATH,或者 IDE 内部配置的编译器路径为空。
解决办法是,不要在 IDE 里依赖“自动探测”,而是手动指定 gcc 可执行文件的绝对路径。比如 VS Code 的tasks.json里的command字段直接写成:
"command": "C:\\msys64\\ucrt64\\bin\\gcc.exe"CLion 的用户在 Toolchain 设置里把 Compiler 路径也指向同一个文件。只要路径写对,基本都能正常构建。手动指定比 PATH 自动搜索要可靠得多,尤其当你机器上同时装了 WSL、MSYS2、Visual Studio 多套环境时。
8. 我个人的安装策略和最后几点建议
环境安装这件事,其实是一套长期维护的工程决策。装的时候偷懒两分钟,后续可能天天被配置折磨。我现在基本遵循几个固定原则,分享给你参考。
8.1 能用包管理器就用包管理器,不要图“绿色版”
“绿色版编译器”几乎是 Windows 用户入坑的根源。很多人从网上下载一个解压即用的 MinGW,解压后却不知道要把bin目录加进 PATH,或者被下到旧版本导致编译成果只能在本地跑。包管理器虽然看似多学了一个工具,但它能帮你处理版本依赖、更新、卸载,长期收益远大于初期学习成本。
Linux 上同理,用 apt/dnf/pacman 安装后,系统会统一记录你所装的文件列表。哪天你想卸载build-essential,一条命令就能清理干净,不会留下一堆散落的.so文件和临时目录。手动源码安装虽然能让你看到完整过程,但一旦忘记源码放在哪里,后期清理就是噩梦。
8.2 把“版本”当作项目配置的一部分
在实际项目里,我会在项目文档或环境配置里明确记录 gcc 和 cmake 的版本要求,而不只是在 README 里写“需要 gcc”。因为现代 C++ 项目的语法和构建脚本对版本非常敏感。同一个CMakeLists.txt,用 cmake 3.16 和 3.26 解析,可能先后行为就不一样;同一个std::format,要 GCC 13 才能顺畅使用。
所以每次环境装好之后,我会顺手把版本号保存在一个文件里,比如ENV.md,写入:
- gcc 13.2.0 - g++ 13.2.0 - cmake 3.27.0 - OS: Ubuntu 22.04 x86_64这样在新的机器上复现环境时,我不用凭着记忆摸索,直接照单装对应版本。这个习惯在多人协作时尤其宝贵,能减少大量“在我机器上好好的”问题。
8.3 最后说一个我常用的手法:用可变路径隔离多个工具链
如果你的工作流里需要同时维护多个不同编译器版本,不一定要把系统全局环境改来改去。我会用 CMake 的 toolchain 文件来指定一套独立的编译链。这个文件通常不写死在项目里,而是放一个独立目录,内容大致是:
set(CMAKE_C_COMPILER /opt/gcc-13/bin/gcc) set(CMAKE_CXX_COMPILER /opt/gcc-13/bin/g++)然后在配置时显式指定:
cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/gcc-13-toolchain.cmake -S . -B build这样做的好处是,同一台机器上,项目 A 可以用 gcc 12,项目 B 可以用 gcc 13,互不干扰。这比反复改 PATH 要干净得多,也不容易误伤其他工具。要是你平时只是写写小练习,用系统默认版本就好,不必过早引入这套复杂度。
回到最早的话题,g++、gcc、cmake安装对我来说已经不是单纯“敲命令”的事,而是一整套环境管理思路的缩影。先把编译器、构建工具、项目需求三者之间的关系理清,再选择合适的安装方式,最后跑通一个最小测试工程。走到这一步,你的开发环境就算真正稳了。后面再遇到任何奇怪的编译报错,心态也稳很多。