
简介这是 GNU 编译器集合 9.1.0 的完整源码压缩包面向 Linux/Unix 平台上的 C、C 开发人员也适用于嵌入式工程师、系统软件研究者以及编译原理课程的师生。源码包包含 C 与 C 语言前端、各支持语言的运行时库和标准库同时提供基于 autoconf 和 automake 的构建脚本可针对不同操作系统自动配置生成 Makefile便于跨平台编译。压缩包为 tar.gz 格式体积约 118.36MB上游未给出文件总数及具体类型明细因此这里不罗列目录通常此类源码包内部以 .c、.h、.cc、.def 等源代码文件为主配合 configure 脚本与 Makefile.in 模板支撑完成探测、配置、编译与安装全过程。当前已有 269 人浏览学习。借助这份源代码读者可以完整追踪 GCC 从预处理、词法语法分析、优化到代码生成和汇编的流水线理解编译器各模块如何协同工作还能通过修改源码、自定义优化开关或针对特定处理器进行交叉编译锻炼系统级调试与工程实践能力。该版本属于较新的主线版本修复了旧版若干缺陷并增添对新语言特性的支持对深入掌握编译器技术有较高的学习价值。1. 为什么非要自己编译 GCC 9.1.0一个 tar.gz 的典型出场方式系统默认编译器太老是所有想用新特性的人迟早要面对的问题。Ubuntu 18.04 的 GCC 7 在 C17 支持上还有明显残缺CentOS 7 和部分银河麒麟系统干脆停留在 GCC 4.8.5连if constexpr都不认识。包管理器里没有你要的版本于是 gcc-9.1.0.tar.gz 这个源码包成了唯一选择——它是 GCC 9 主线的第一个发行版解压、configure、make、install 四步就能得到一套完全独立的新编译器。这篇内容写给两种人被旧编译器卡住的 Linux 使用者和需要在不破坏现有编译环境的前提下引入新工具链的运维。全文不聊虚的只讲我自己会照着做的完整流程、参数取舍和踩过的坑从拿到 tar.gz 到能稳定交付一个新 GCC 环境。2. 从 tar.gz 到可用编译器GCC 9.1.0 的完整编译流程2.1 解压与三件套GMP、MPFR、MPC 提前就位先做最基础的一步解压。gcc-9.1.0.tar.gz是标准的 tar gzip 打包一条命令解出来就是gcc-9.1.0源码目录。我一般会把源码包放在/opt/src这类专门目录里方便和后面的构建目录分开管理避免源码目录和编译产物混在一起。mkdir -p /opt/src cd /opt/src tar -xf gcc-9.1.0.tar.gz cd gcc-9.1.0这里说的是 Linux 上从源码搭建 GCC 环境的标准路径我见过有人想在 macOS 下用 clang 或者 gcc 直接编出 Windows 的 .exe那是交叉编译工具链的事得另配 mingw-w64 之类的东西和现在从 tar.gz 编原生 GCC 的流程完全是两码事别混在一起想。解压之后有一个省事的关键动作GCC 源码里带了一个contrib/download_prerequisites脚本它的作用是下载 GMP、MPFR、MPC 三个数学库的固定版本并在 configure 时自动指向它们。这三个库是 GCC 编译自身的前置依赖GMP 提供大整数和高精度浮点运算MPFR 在 GMP 之上做高精度浮点MPC 又依赖 MPFR 处理复数。大多数 Linux 发行版都装了其中某些库但版本常常不满足 GCC 9.1.0 的要求configure 会直接拒绝继续。./contrib/download_prerequisites这个脚本跑完会创建gmp、mpfr、mpc三个符号链接指向下载好的目录源码目录本身不被污染。如果机器能联网这一步是最省事的做法。如果离线环境常见做法是手工准备这三个库的源码包解压后放进gcc-9.1.0目录里并做同名符号链接链接名必须是gmp、mpfr、mpcGCC 在 configure 阶段会按这三个名字去识别。这里有个值得注意的细节download_prerequisites下载的版本经过 GCC 官方验证相互之间匹配不会出现 GMP 太新、MPFR 太旧之类的组合错误。你自己手工装的时候建议按 GCC 9.1.0 的 configure 报错提示来调整版本它会在报错信息里直接告诉你最低版本号和当前系统版本号。三件套准备不全会让 configure 卡住具体报错我放在避坑章细说。2.2 用 out-of-tree 构建configure 参数与安装路径的关系GCC 官方文档明确不建议在源码目录内直接 configure 构建in-tree build那样容易把源码目录弄乱清理也麻烦。实践里我一直用 out-of-tree在外面建一个专门的 build 目录所有编译中间文件都放这里以后想重来直接删目录就行后悔药做得很干净。cd /opt/src mkdir -p build-gcc-9.1.0 cd build-gcc-9.1.0 ../gcc-9.1.0/configure \ --prefix/opt/gcc-9.1.0 \ --enable-languagesc,c \ --disable-multilib参数说明逐个讲。--prefix决定最终安装位置我习惯用独立目录而不是直接塞进/usr/local这样以后卸载就是把整个目录删掉完全不碰系统其它文件。--enable-languagesc,c只构建 C 和 C 前端GCC 默认还会带上 fortran、ada、go 等一堆前端编译时间和磁盘占用都会明显增加对绝大多数场景来说只留c,c足够。--disable-multilib是关掉 32/64 位双库支持默认开启时 GCC 会尝试在 x86_64 机器上也编 32 位版本的库一旦系统缺少 32 位 glibc 的 dev 包make 到一半才报错很烦人。吃不准自己的需求就直接关掉。还有一个我常用的参数--program-suffix-9.1让安装出来的可执行文件名变成gcc-9.1而不是gcc。这样它可以和系统自带 gcc 完全并行不用抢 PATH 里同一个名字但代价是日常使用时得改脚本里的调用名。走 update-alternatives 的人通常不加这个参数两种流派我先都摆出来第 4 章会给一套整合方案。configure 结束时强烈建议把输出里checking for...的检查项扫一遍重点是最后有没有带error字样的结论。如果 configure 报错退出直接回到第 2.1 步重新检查三件套别硬着头皮往后走后面 make 一样会挂。2.3 make 与 make install并行数的选取和环境变量持久化configure 成功后进入真正的编译阶段。GCC 默认走 bootstrap 三阶段构建也就是用刚编出来的第一版编译器再编两遍自身确保最终产物足够健壮但这同时也是时间开销的大头。想快点出结果可以用--disable-bootstrap关掉代价是产物可靠性略降。make -j$(nproc)这里有一个非常关键的 -j 参数坑-j8不是越快越好。GCC 的 C 前端单进程能吃掉 1~2GB 内存8 并发会直接顶穿 8GB 机器的物理内存在 swap 里挣扎情况严重时 OOM killer 会挑最肥的cc1plus进程下手屏幕上出现Killed。内存不富裕的机器建议用make -j2或make -j4先探底反正编译一次十几分钟跑不掉比中途挂掉重来强。这一步顺利完成的标准是最后出现类似The directory that contains a successful GCC build is:的提示然后才轮到安装。sudo make installmake install会把编译器复制到--prefix指定的目录下也就是/opt/gcc-9.1.0。这一步需要 root 权限。装完先别急着用检查两个东西ls /opt/gcc-9.1.0/bin应能看到gcc、g、cpp等可执行文件find /opt/gcc-9.1.0 -name libstdc.so*能看到新库文件。然后做环境变量持久化——如果只开了一个终端export一次就够了但重启终端又是旧 gcc所以该写进 profile 得写。cat /etc/profile.d/gcc-9.sh EOF export PATH/opt/gcc-9.1.0/bin:$PATH export LD_LIBRARY_PATH/opt/gcc-9.1.0/lib64:$LD_LIBRARY_PATH EOF source /etc/profile.d/gcc-9.sh写/etc/profile.d是 Linux 上最通用的做法登录 shell 会自动加载用户无需改自己的 bashrc。注意lib64还是lib取决于发行版和 gcc 的 multilib 配置x86_64 上默认是lib64不确定就按上面find的结果改路径。关于LD_LIBRARY_PATH全局导出的风险和更稳的替代方案我放在第 4 章展开这里先让新 GCC 能跑起来。注意make install成功后不要随手删掉 build 目录。你后面可能要重新编译某个子模块或者发现某个 configure 参数选错了需要重来留着 build 目录能省下重新 configure 的时间。3. 踩坑记录GCC 9.1.0 编译安装中我遇到的 5 个问题与排查3.1 gcc --version 还是旧版本PATH 与 hash 缓存现象make install全部成功终端里gcc --version还是旧版本尤其刚敲完source或者开新终端的时候最常出现。很多人在这一步直接开始怀疑安装出了问题。原因两件事叠在一起。一是 PATH 里/usr/bin排在/opt/gcc-9.1.0/bin前面shell 先找到了旧 gcc二是 shell 对命令路径有 hash 缓存which gcc的结果一旦被缓存就算 PATH 改了也还是旧的。解决export PATH/opt/gcc-9.1.0/bin:$PATH hash -r which gcc gcc --versionhash -r清掉 shell 缓存which gcc看实际路径gcc --version看版本。如果which显示/opt/gcc-9.1.0/bin/gcc但version还是旧号那基本是动态库被污染跳到 3.4 解决。3.2 configure 卡在三件套版本检测依赖没就位现象configure 报错具体是checking for correct version of gmp.h... no或者直接提示Building GCC requires GMP 4.3.2 MPFR 2.4.6 MPC 0.8.1。这是我见过最多的 Ubuntu 安装 gcc 失败现场。原因三件套没在 GCC 认为正确的路径。常见三种一是系统根本没装二是装了但版本低于要求三是头文件和库文件装了但没在默认搜索路径里。第三种多发生在多版本系统上比如/usr/local/include里有新 gmp.h但 configure 的默认搜索顺序没把/usr/local/include放前面。解决最稳的是补跑download_prerequisites它能保证版本组合。离线或不想下载时手工把三个符号链接放进 gcc 源码目录ln -s /opt/gmp-6.1.2 gcc-9.1.0/gmp ln -s /opt/mpfr-4.0.2 gcc-9.1.0/mpfr ln -s /opt/mpc-1.1.0 gcc-9.1.0/mpc然后重新 configure。注意符号链接路径要写对如果当前在/opt/src下执行链接名是相对于gcc-9.1.0目录的。版本号别乱凑configure 报什么最低版本就给什么给新版一般也可以但别给太激进的新版库接口偶尔会 break不值得赌。3.3 make 中途触发 Killed内存与并行数没匹配现象make 执行到一半进程消失终端留下一行Killed或者显示cc1plus: internal compiler error: Killed (program cc1plus)。这种情况在内存 8GB 的机器上尤其高发。原因物理内存被耗尽内核 OOM killer 把编译进程杀了。GCC 编译大型 C 文件是内存刺客单进程吃 1~2GB 很常见并行数设得太高直接爆。解决先看内存再定并行数。free -h make -j2如果还崩换-j1加临时 swap 也能熬过去fallocate -l 8G /swapfile chmod 600 /swapfile mkswap /swapfile swapon /swapfile加 swap 是临时的重启失效但足够让一次完整编译熬过去。这个坑的血泪经验是先free -h看可用内存再决定-j而不是无脑nproc。3.4 新编译的程序报 GLIBCXX_3.4.20 not found动态库被旧的抢走了现象新 GCC 编译成功程序一运行就报/lib/x86_64-linux-gnu/libstdc.so.6: version GLIBCXX_3.4.20 not found。这是 gcc 升级后最容易让人以为编译器没装对的报错。原因编译时调用的 g 是新的但链接时动态库搜索顺序把程序指向了系统旧版libstdc.so.6。动态链接器默认先找LD_LIBRARY_PATH再找 ELF 里的 RUNPATH/RPATH最后才回系统默认目录LD_LIBRARY_PATH没包含新库目录自然用了旧的。解决最直接的是让程序跑起来前先加上库路径export LD_LIBRARY_PATH/opt/gcc-9.1.0/lib64:$LD_LIBRARY_PATH ./your_program更省心的做法是把库路径写进程序 ELF编译时直接加-Wl,-rpathg app.cpp -o app -Wl,-rpath,/opt/gcc-9.1.0/lib64这样程序跑起来自动优先用新库机器上没设LD_LIBRARY_PATH也一样。两种方式我建议优先 rpath它对环境没有全局副作用。具体参数边界放第 4 章。3.5 VSCode 一键编译仍走旧 gcc#include 错误与 compilerPath现象VSCode 里配好 task 一键编译运行结果报#include errors detected或者 F5 调试时还是调用了旧 gcc在c_cpp_properties.json里设置了 compilerPath 指向新路径插件还是识别不了标准库头文件。原因VSCode 的 C/C 插件里 compilerPath 和 includePath 是两个独立配置只改 compilerPath 不改 includePath 时插件还是会按它默认的系统头文件路径去找 C 标准库。而新 GCC 的头文件在/opt/gcc-9.1.0/include/c/9.1.0下插件自然找不到。解决同时改两个文件。c_cpp_properties.json里 compilerPath 指向新 gccincludePath 手工加入 C 标准库头文件目录{ configurations: [ { name: Linux, compilerPath: /opt/gcc-9.1.0/bin/gcc, includePath: [ ${workspaceFolder}/**, /opt/gcc-9.1.0/include/c/9.1.0, /opt/gcc-9.1.0/include/c/9.1.0/*, /opt/gcc-9.1.0/include ] } ] }这里 includePath 里的9.1.0/*是为了把x86_64-pc-linux-gnu这类平台子目录一并包含。不同 configure 的 target 目录名不一样不确定就ls /opt/gcc-9.1.0/include/c/9.1.0/看一下实际目录名再填。tasks.json 里的 command 也要写成绝对路径/opt/gcc-9.1.0/bin/gcc避免任务还走 PATH 里的旧命令。4. 多版本共存与路径管理让新 GCC 接管又不弄乱旧环境4.1 update-alternatives把 GCC 9.1.0 绑定成可切换的默认版本当系统里同时存在两个 gcc最怕的是依赖旧 gcc 的第三方东西被新 gcc 偷偷替换掉。update-alternatives 提供了显式切换机制默认命令还是/usr/bin/gcc但它只是个符号链接指向/etc/alternatives/gcc再由 alternatives 指向你选定的真实版本。sudo update-alternatives --install /usr/bin/gcc gcc /opt/gcc-9.1.0/bin/gcc 100 sudo update-alternatives --install /usr/bin/g g /opt/gcc-9.1.0/bin/g 100 sudo update-alternatives --config gcc--install后面三个要素链接位置、链接组名、优先级数值。数值越大优先级越高update-alternatives 会自动选优先级高的手动--config可以临时切到另一个。这个机制在 Debian/Ubuntu 和基于 Debian 的银河麒麟上都适用CentOS/RHEL 对应命令叫alternatives语法接近但不同大版本对链接组的写法有差异具体以alternatives --help为准。要注意的是这个接管动作会把/usr/bin/gcc从真实文件变成符号链接dpkg -V校验会认为包文件被修改在意这个就去make install时加--program-suffix用独立文件名避开系统路径。用了 update-alternatives 后日常 PATH 不需要加/opt/gcc-9.1.0/bin因为/usr/bin/gcc始终是统一入口。这和你自己 export PATH 的差别在于任何命令行工具只要锁定/usr/bin/gcc就能拿到当前默认版本不会因为环境变量没设而悄悄变回旧版。我经常来回切版本这套机制管理成本低很多。还有一个不太起眼但很实用的习惯编译第三方源码包时优先用CC/opt/gcc-9.1.0/bin/gcc CXX/opt/gcc-9.1.0/bin/g ./configure来指定编译器而不是改全局默认。这样这个第三方包用的是新编译器系统里其它东西还是旧编译器互不打扰。4.2 -I、-L 与 LIBRARY_PATH控制头文件和库的搜索顺序即使 PATH 和 update-alternatives 都指向了新 gcc还有一个隐患头文件。GCC 能自动找到自己安装目录下的标准头文件因为它会以编译器二进制所在路径为锚点推导 include 根但你的项目如果用了第三方库那些旧头文件大多还在/usr/include新 GCC 默认搜索顺序也会带上系统 include 目录于是编译器新、依赖旧的局面就出现了。控制头文件搜索顺序最直接的是-I/opt/gcc-9.1.0/bin/gcc -I/opt/gcc-9.1.0/include -I/opt/boost/include app.cpp -o app-I目录会在系统 include 之前被搜索但它只影响预处理阶段。链接时库的搜索顺序用-L指定/opt/gcc-9.1.0/bin/gcc app.o -L/opt/gcc-9.1.0/lib64 -lstdc-L只把目录加到链接器搜索列表实际找哪个库还受库名和版本影响最终结果要用ldd看。环境变量层面还有LIBRARY_PATH它给 GCC 前端提供系统额外库搜索路径等价于给 gcc 的链接过程补充-L。批量处理时导出它最快export LIBRARY_PATH/opt/gcc-9.1.0/lib64:$LIBRARY_PATH但它和-L一样只管链接期不管运行期。运行期的搜索归LD_LIBRARY_PATH和 rpath 管所以别指望LIBRARY_PATH解决 3.4 那种运行报错。一个常见的误用把LD_LIBRARY_PATH全局导出结果所有老的二进制程序全部切到了新 libstdc有些二进制固化依赖旧版本符号直接跑崩。我的习惯是LD_LIBRARY_PATH只在项目启动脚本里 export或干脆用下一节的 rpath避免全局污染整个系统的环境搭建。4.3 rpath 与 LD_LIBRARY_PATH 的搭配部署时动态库怎么带第 3.4 步踩过的那类问题根源就在运行期搜索路径没固化。部署阶段最头疼的场景是把程序从开发机拷贝到一台没装 GCC 9.1.0 的服务器上动态库带不走启动就想半天。LD_LIBRARY_PATH是你手动告诉系统去哪里找库但它依赖外部环境变量启动脚本一没配就回到 3.4 的报错更麻烦的是LD_LIBRARY_PATH的优先级高于 RUNPATH可能覆盖程序内预设的路径反而造成混乱。写进 ELF 的 rpath 是更稳定的答案。编译时加上这么一段g -stdc17 app.cpp -o app \ -Wl,-rpath,/opt/gcc-9.1.0/lib64 \ -Wl,--enable-new-dtags-Wl,-rpath把/opt/gcc-9.1.0/lib64写进程序的动态段--enable-new-dtags让它生成 RUNPATH 而不是老式 RPATH。区别很关键RUNPATH 的优先级低于LD_LIBRARY_PATH也就是说即使运行环境里有人设了LD_LIBRARY_PATH指向别处程序仍然优先遵循自己的 RUNPATH但同时还是能被环境变量覆盖给后续调换库版本留了余地。验证是否生效readelf -d app | grep -E RPATH|RUNPATH ldd app | grep stdcreadelf显示 RUNPATH 存在ldd显示libstdc.so.6指向/opt/gcc-9.1.0/lib64说明运行期依赖已经固化。部署时只要把这个目录和程序一起拷贝服务器上有没有新 GCC 都能跑。如果连/opt/gcc-9.1.0/lib64都不想带也可以把libstdc.so.6静态编进程序-static-libstdc -static-libgcc。代价是产物体积变大、ABI 灵活性降低GCC 9.1.0 的 C 库大量用了弱符号和动态版本控制静态链接在某些场景会引发行为差异。一般二三十人的内部工具我才会这么干对外交付的产品还是 rpath 稳。5. 验证安装用三个最小程序确认 GCC 9.1.0 真正生效安装完成、路径也理顺之后不要急着一头扎进项目里先跑三个最小验证。第一个是确认命令本身which gcc gcc --versionwhich显示/opt/gcc-9.1.0/bin/gcc才说明 PATH 生效如果显示/usr/bin/gcc哪怕 version 是新版本也说明你依赖的其实是 alternatives 或 PATH 之外的另一条解析链回看第 3.1 步。第二个验证编译一个打印版本宏的 C 程序。__VERSION__是编译器内置宏直接反映真正干活那个编译器的版本号比命令行显示更可靠。cat ver.c EOF #include stdio.h int main(void) { printf(GCC %s\n, __VERSION__); return 0; } EOF gcc ver.c -o ver ./ver输出 GCC 9.1.0 即通过。如果输出旧版本问题必然出在调用链上先gcc --version再readlink -f $(which gcc)一层层查。第三个验证 C17 特性加动态库归属。用if constexpr写一个最小模板配-stdc17编译#include iostream #include type_traits template typename T constexpr const char* kind() { if constexpr (std::is_integral_vT) return int; else if constexpr (std::is_floating_point_vT) return float; else return other; } int main() { std::cout kindint() \n; std::cout kinddouble() \n; }g ver_cpp17.cpp -o ver_cpp17 -stdc17 ./ver_cpp17 ldd ver_cpp17 | grep stdcldd输出里 grep 到/opt/gcc-9.1.0/lib64/libstdc.so.6才算完整通过如果它指向/usr/lib下的旧库说明第 3.4 步的 rpath 或LD_LIBRARY_PATH还没生效。if constexpr能编过本身就证明 C17 支持完整这在 GCC 4.8 上是连语法都过不去的。我自己现在每台机器装完 GCC 都会把上面四段命令合成一个check_gcc.sh放进工程目录换机器、换环境先跑一遍三分钟定位是命令层、编译层还是运行层的问题省到就是赚到。希望这篇能帮你在 GCC 9.1.0 上少走一段弯路。本文还有配套的精品资源点击获取