Linux源码编译安装GCC:内网离线、依赖配置与多版本切换

发布时间:2026/9/29 1:32:46
Linux源码编译安装GCC:内网离线、依赖配置与多版本切换 1. 从 apt install gcc -y 到源码编译先搞清楚为什么如果你只是想让机器上有 gcc 能用那apt install gcc -y或者yum install gcc就够了没必要折腾源码编译。但现实里总有那么几种情况包管理器给你的东西就是不够用——发行版仓库里的 gcc 版本被冻结在某个老版本项目要求 C20 或者要-stdc23仓库里那个死活编不过机器在内网里没有外网源apt直接报Unable to locate package或者你要给一套离线交付的系统补齐工具链得把编译器打包成可迁移的目录。这几种场景叠在一起源码编译安装 gcc 就从没必要变成了唯一解。这篇文章讲的是一整套纯命令行下的操作流程从 8G 内存的普通虚拟机到 32 核的构建机都适用从 Ubuntu、Debian 到麒麟、统信这类内网环境也能套用。内容里既有能直接复制粘贴执行的命令也有我在实际编译过程中踩出来的坑——比如为什么编完了gcc -v还是旧版本为什么make跑到一半被系统杀掉为什么装完了程序仍然报GLIBCXX_3.4.32 not found。看完你至少能做到三件事知道该选哪个版本和哪些参数知道怎么在受限环境下把依赖凑齐知道装完之后怎么验证和管理多版本。我自己的经历是第一次编 gcc 花了整整一个通宵最后发现失败原因是内存不够加上并行度开了 16机器直接 OOM。第二次准备工作做足四核虚拟机跑了两小时四十分钟一次过。差别全在准备工作上所以下面的内容我会把准备这一块写得很细你照着做能省掉大部分返工。1.1 包管理器方案的边界在哪里包管理器装的 gcc 有几个天然限制。第一是版本锁定Ubuntu 20.04 默认给你 gcc 922.04 给 gcc 11你想用 gcc 13 就得加 PPA 或者换发行版而在内网环境下加源这件事本身就很麻烦。第二是编译选项固定发行版编译 gcc 时会开一堆自己的默认配置--enable-languages通常包含全部语言体积大但未必是你要的而某些需要定制的特性比如关掉 multilib 减小体积、指定--with-arch目标架构在二进制包里没法改。第三是依赖绑架你升级系统自带的 gcc可能会把libstdc6一起换掉进而影响系统上已有的软件这种连带风险在生产机上很致命。所以判断标准很简单如果仓库里有满足要求的版本、机器能联网、也不介意用系统默认编译参数那就别编用包管理器五分钟解决战斗。只有当版本、环境、定制这三项里至少有一项卡住的时候源码编译才是划算的。1.2 源码编译能解决的几类硬需求我把实际遇到过的需求归成四类。第一类是版本超前内核模块或者某些 C 项目要求更高的编译器版本仓库里没有又不想整个系统升级。第二类是离线交付目标机器在内网没有软件源你要把编译好的工具链整目录拷过去或者干脆在目标机上就地编译。第三类是架构交叉要给 ARM 平台准备工具链或者需要指定特定的--target这种必须在本地配上对应源码重新 configure。第四类是精确控制只想支持 c 和 c不要 fortran、ada、go把编译时间从三小时压到一小时或者明确要求关掉某些运行时库以减小最终体积。这四类需求用包管理器基本都实现不了源码编译的优势就在于--prefix让你装到独立目录不污染系统--enable-languages让你只编要的语言--with-gmp这类参数让你把依赖也一起管起来。整个工具链变成一个自包含的目录想删就rm -rf想搬就tar打包干净利落。1.3 先算清成本再动手动手之前先看一眼账。源码编译 gcc 的成本主要三块时间、磁盘、内存。时间上四核机器只编 c 和 c 并关闭 bootstrap大约 40 分钟到 1 小时开启完整 bootstrap 三段式大约 2 到 3 小时单核老机器上开 bootstrap做好 6 小时以上的心理准备。磁盘上源码包解压后约 1.2G构建目录会膨胀到 4G 到 6G安装目录约 1.5G 到 2.5G加上依赖库整个流程至少预留 12G 空余空间20G 更稳。内存上单个cc1plus进程峰值可能吃到 800M 到 1.2G所以 8G 内存的机器建议-j4以内16G 可以给到-j8具体公式后面会讲。注意这三项里最容易翻车的是内存。磁盘不够会明确报错内存不够是内核直接 OOM Killer 把进程干掉日志里只留一句Killed很容易误判成源码问题。2. 编译前的环境盘点与依赖清单准备工作决定了后面两小时是顺畅还是反复折腾。我的习惯是先跑一遍体检命令把磁盘、内存、CPU、系统架构、已有的 gcc 版本全部看清楚再决定版本和参数。# 系统与架构 uname -m cat /etc/os-release | head -n 5 # 资源盘点 nproc free -h df -h /opt /usr/local /tmp # 现有工具链 gcc --version 2/dev/null | head -n 1 ld --version 2/dev/null | head -n 1 make --version 2/dev/null | head -n 1这几条命令的输出直接决定后面三个决策装到哪里看/opt的空间、并行度开多少看内存和核数、能不能用系统自带 gcc 做引导看现有 gcc 版本。如果现有 gcc 是 4.8 以上基本都能胜任引导工作如果系统里压根没有 gcc那就得先想办法通过离线包或者本地仓库装一个因为 gcc 是自举编译的需要一个宿主编译器起步。2.1 磁盘、内存、CPU三个决定成败的硬指标磁盘方面我建议把源码、构建目录、安装目录分开放或者至少都放在空间最大的那一个分区下。/tmp如果没有单独分区且空间紧张编译时可能被临时文件撑爆虽然 gcc 的构建默认在 build 目录里产生中间文件但链接阶段还是会用到一些临时空间。内存方面给出一个粗糙但好用的估算公式并行度 min(CPU核数, 可用内存GB / 1.5)。比如 16G 内存、8 核取min(8, 10) 8稳妥点降到 6 更保险。如果内存实在不够又不想降并行度可以临时加一块 swap 文件顶上# 临时加 8G swap编译完可以删掉 sudo fallocate -l 8G /swapfile-gcc sudo chmod 600 /swapfile-gcc sudo mkswap /swapfile-gcc sudo swapon /swapfile-gcc free -h用 swap 的代价是编译速度会明显下降因为 SSD 换页也有开销但总比 OOM 强。编译完成后记得swapoff /swapfile-gcc rm /swapfile-gcc恢复。CPU 方面没什么可调的核数越多越快但要注意虚拟机的 CPU 超分情况。如果宿主机本身负载很高你在虚拟机里开满-j反而会因为资源争抢变慢这时候降到nproc的一半往往更稳。2.2 引导编译器与基础构建工具gcc 的构建过程需要几个基础工具在场make建议 3.8 以上、ld/as二进制工具链、tar、gzip/bzip2、sed、awk、grep、diff、patch、find。这些在常规发行版里都有但精简版系统或者容器镜像里经常缺。# Debian / Ubuntu 系 sudo apt install -y build-essential tar bzip2 xz-utils flex \ libc6-dev zlib1g-dev # RHEL / CentOS / 麒麟 / 统信系 sudo yum install -y gcc gcc-c make tar bzip2 xz flex \ glibc-devel zlib-devel这里有两个容易忽略的点。第一是flexgcc 构建过程中需要用词法分析器缺了会在 configure 阶段报错。第二是zlib开发包虽然 gcc 自带一份 zlib但用系统的能省编译时间前提是头文件得在。提示如果你的系统里已经有 gcc但版本比较老比如 4.8也能做引导只是编译速度会慢一些而且某些新语法可能不支持。一般情况下推荐宿主 gcc 版本不低于 4.8最好在 7 以上。2.3 GMP、MPFR、MPC、isl绕不开的四个库这四个库是 gcc 编译的硬依赖缺一个都过不了 configure。它们各自的分工是GMP 负责大整数运算MPFR 负责多精度浮点MPC 负责复数运算isl 负责多面体优化影响-floop-nest-optimize这类优化以及 Graphite 框架。gcc 自身用这些库来做常量折叠、循环优化和编译期计算。版本要求随 gcc 版本变化以 gcc 13 为例要求大致是 GMP 4.2 以上、MPFR 2.4.0 以上、MPC 0.8.0 以上、isl 0.15 到 0.26 之间。官方推荐的组合是 GMP 6.2.1、MPFR 4.2.0 或 4.2.1、MPC 1.3.1、isl 0.26。版本不匹配会出现两种典型症状太低直接报版本不满足太高可能因为 API 变更编译中途报错。所以别用最新版用官方脚本拉的那几个版本最稳。2.4 联网与离网两种依赖获取路径如果机器能上外网或者说能访问到软件镜像源最省事的办法是用 gcc 源码树里自带的脚本cd gcc-13.2.0 ./contrib/download_prerequisites这个脚本会下载 GMP、MPFR、MPC、isl 四个包并解压到源码树根目录configure 时会自动识别并使用它们不用额外指定路径也不用先装到系统里。脚本里的版本号是官方验证过的组合省去了自己搭配的烦恼。工作中我遇到最多的情况是内网机器脚本因为网络原因下载失败。这时候有两个选择一是在一台能上网的机器上先跑脚本把它下载的四个.tar.bz2文件拷到内网机器的源码树根目录再跑一次脚本脚本检测到文件已存在会跳过下载直接解压二是自己下载四个包分别解压后用--with-gmp这类参数指定路径。第二种方式更灵活也方便把依赖装到独立的目录里复用mkdir -p /opt/gcc-deps cd /opt/gcc-deps # 依次解压 gmp-6.2.1.tar.bz2、mpfr-4.2.1.tar.bz2、mpc-1.3.1.tar.gz、isl-0.26.tar.bz2 cd gmp-6.2.1 mkdir build cd build ../configure --prefix/opt/gcc-deps make -j$(nproc) make install cd ../../mpfr-4.2.1 mkdir build cd build ../configure --prefix/opt/gcc-deps --with-gmp/opt/gcc-deps make -j$(nproc) make install cd ../../mpc-1.3.1 mkdir build cd build ../configure --prefix/opt/gcc-deps --with-gmp/opt/gcc-deps --with-mpfr/opt/gcc-deps make -j$(nproc) make install cd ../../isl-0.26 mkdir build cd build ../configure --prefix/opt/gcc-deps --with-gmp-prefix/opt/gcc-deps make -j$(nproc) make install注意顺序不能乱MPFR 依赖 GMPMPC 同时依赖 GMP 和 MPFRisl 依赖 GMP。编完记得把/opt/gcc-deps/lib加进LD_LIBRARY_PATH否则后面编译 gcc 时链接器找不到这些库。3. 源码获取、校验与目录规划版本选对了能省一半事目录规划对了能省另一半。这两件事都不难但值得花十分钟想清楚。3.1 版本挑选的实际依据2024 年 2 月这个时间点上稳定分支里 GCC 13.2.02023 年 7 月发布是主力选择GCC 12.3.0 是更保守的备选GCC 11.4.0 适合需要和老系统兼容的场景。挑选时主要看三件事你的项目需要什么语言标准C20 需要 10 以上C23 部分特性需要 13、你的 glibc 版本是否够新太老的 gcc 配新 glibc 会报strdup之类的隐性声明错误、你的目标平台是否有已知问题。我的建议是没有特殊要求就选当年的最新稳定版也就是 13.2.0生态成熟、资料多、踩坑记录也全。如果要在很老的系统上编译比如 glibc 2.17反而要选相对老一点的 gcc因为新 gcc 的某些头文件处理方式在旧 glibc 上会出问题。3.2 下载与完整性校验源码从官方镜像站拿国内访问一般也还行。下载完一定要校验因为 gcc 源码包几百兆网络传输中断导致文件不完整是很常见的而这类问题的报错往往出现在编译的中途排查起来特别费劲。mkdir -p /opt/src cd /opt/src wget https://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-13.2.0/gcc-13.2.0.tar.gz wget https://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-13.2.0/md5.sum # 校验 md5sum -c md5.sum --ignore-missing 2/dev/null | grep gcc-13.2.0 # 或者直接比对 md5sum gcc-13.2.0.tar.gz校验通过后再解压tar -xzf gcc-13.2.0.tar.gz这里顺便说一个热词里经常出现的问题——解压乱码。如果你的压缩包里带有中文文件名的附加内容比如某些第三方打包的源码在LANGC或者LANGPOSIX的环境下解压会出现乱码文件名。解决办法是解压前设置LANGen_US.UTF-8或LANGzh_CN.UTF-8或者用unzip -O CP936指定编码。纯 gcc 官方源码包全是 ASCII 文件名不会遇到这个问题但如果你的源码包来源不明值得留意。3.3 源码树与构建树分离的目录布局gcc 官方强烈建议用 out-of-tree 构建也就是源码解压在一个目录构建在另一个目录configure时用相对路径指向源码。这样做的好处是源码树保持干净想换一套配置重编直接删掉 build 目录就行不用重新解压也能同时维护多套不同配置的构建。# 目录规划 # /opt/src/gcc-13.2.0 源码树 # /opt/build/gcc-13.2.0 构建树 # /opt/gcc-13.2.0 安装目录 mkdir -p /opt/build/gcc-13.2.0 cd /opt/build/gcc-13.2.0安装目录命名带上版本号是个好习惯后面做多版本共存和卸载都方便。/opt还是/usr/local看你的喜好/opt/应用名-版本是更清晰的方案一眼就知道是什么东西、什么版本。4. configure 参数逐条拆解configure 是整个流程里最需要动脑的一步参数给对了后面一路顺风给错了要么编不过要么编出一堆你用不上的东西。4.1 一条能跑通的最小骨架先给一条最小可用命令适合大多数场景cd /opt/build/gcc-13.2.0 /opt/src/gcc-13.2.0/configure \ --prefix/opt/gcc-13.2.0 \ --enable-languagesc,c \ --disable-multilib \ --disable-bootstrap \ --with-system-zlib这条命令的含义是装到/opt/gcc-13.2.0只编 C 和 C不要 32 位库支持跳过三段式自举以节省时间zlib 用系统的。实测在四核 16G 的虚拟机上从 configure 到 make 完成大约 40 到 60 分钟。4.2 --prefix多版本共存的第一步--prefix决定安装路径也决定了后续所有的库路径和可执行文件路径。如果你打算机器上保留系统自带 gcc同时并存一个新版那 prefix 必须和系统路径分开比如/opt/gcc-13.2.0。装完之后通过PATH、update-alternatives或者显式绝对路径调用三种方式各有适用场景后面验证部分会展开。不建议直接--prefix/usr那样会覆盖系统自带的 gcc 和 libstdc一旦出问题很难回退而且系统包管理器后续升级时会出现文件冲突。除非你明确知道自己在做什么比如在离线交付的成品镜像里做统一替换。4.3 语言集与 multilib 的取舍--enable-languages决定编哪些前端。常见的取值有c、c、fortran、go、ada、objc、d、m2。每多一个语言编译时间和磁盘占用都会明显增加fortran 和 ada 尤其吃资源。如果你的项目只写 C/C那就只写c,c能省掉可观的构建时间。--disable-multilib是另一个省时间的开关。默认情况下 gcc 会同时构建 32 位和 64 位两套库在纯 64 位环境下你根本用不上 32 位那一套。加上--disable-multilib能少编一半的运行时库构建时间大概能压掉两到三成。只有当你确实需要-m32编译 32 位程序时才需要打开它。反过来如果你要交叉编译给 ARM 平台那就要用--targetarm-linux-gnueabihf这类参数同时准备好对应的 binutils 和 C 库整个流程复杂度会高一个量级本文不展开。4.4 依赖库路径的三种指定方式依赖库的指定有三种情况分别对应前面说的三种准备方式。第一种用download_prerequisites把依赖放在源码树根目录这种情况下什么都不用加configure 会自动发现。第二种依赖装在独立 prefix比如/opt/gcc-deps用--with-gmp、--with-mpfr、--with-mpc、--with-isl分别指定--with-gmp/opt/gcc-deps \ --with-mpfr/opt/gcc-deps \ --with-mpc/opt/gcc-deps \ --with-isl/opt/gcc-deps第三种依赖装在系统路径但版本不满足要求这种情况比较麻烦通常需要先处理版本冲突或者用CPPFLAGS和LDFLAGS显式指向CPPFLAGS-I/opt/gcc-deps/include \ LDFLAGS-L/opt/gcc-deps/lib -Wl,-rpath,/opt/gcc-deps/lib \ /opt/src/gcc-13.2.0/configure ...第三种方式里加上-Wl,-rpath很关键它能把库搜索路径写进可执行文件的 RPATH避免运行期还要手动设LD_LIBRARY_PATH。4.5 完整可抄的配置命令与参数说明表把上面的内容组合起来给一条我实际用过的完整命令cd /opt/build/gcc-13.2.0 CPPFLAGS-I/opt/gcc-deps/include \ LDFLAGS-L/opt/gcc-deps/lib -Wl,-rpath,/opt/gcc-deps/lib \ /opt/src/gcc-13.2.0/configure \ --prefix/opt/gcc-13.2.0 \ --enable-languagesc,c \ --disable-multilib \ --enable-bootstrap \ --with-system-zlib \ --with-gmp/opt/gcc-deps \ --with-mpfr/opt/gcc-deps \ --with-mpc/opt/gcc-deps \ --with-isl/opt/gcc-deps \ --buildx86_64-linux-gnu \ --hostx86_64-linux-gnu \ --targetx86_64-linux-gnu参数作用建议取值--prefix安装根目录/opt/gcc-版本与系统隔离--enable-languages编译哪些前端按需一般c,c--disable-multilib关闭 32/64 位双构建纯 64 位环境建议开启--enable-bootstrap三段式自举提升可靠性生产环境建议开调试可关--disable-bootstrap跳过自举速度优先本地实验用--with-system-zlib使用系统 zlib系统有 zlib 开发包时开启--with-gmp等指定依赖库路径指向依赖的安装 prefix--build/--host/--target构建/宿主/目标平台本机编译三者一致即可注意--build、--host、--target三个参数不写时 configure 会自动探测一般没问题。但在一些架构识别异常的国产系统上显式写出来能避免工具链路径错乱。configure 跑完会输出一大段摘要重点确认三件事语言列表是不是你要的、依赖库版本是不是识别到了、--prefix是不是对的。摘要里如果出现The following requested languages could not be built之类的警告说明有语言缺依赖要么补依赖要么从列表里去掉。5. 编译与安装的实操过程configure 通过之后就是漫长的编译。这一步的操作很单调但有几个细节不注意就会白等几小时。5.1 并行度怎么定别让机器把自己编死前面给的公式并行度 min(核数, 可用内存GB / 1.5)是经验值。实际操作时我会再保守一点比如 8 核 16G 的机器公式算出 8我通常开-j6。原因是 gcc 构建过程中有几个阶段尤其是链接cc1plus和 libstdc 的时候内存占用会突然飙升如果卡在峰值上正好并行度打满就容易触发 OOM。# 保守一点先看内存 free -h # 假设 16G 可用开 6 个并行 make -j6编译过程中可以用top或者htop盯一下内存和负载。如果看到内存用到 90% 以上且 swap 开始大量换页果断CtrlC停下降并行度重来别抱侥幸心理。5.2 bootstrap 三段式到底在干什么--enable-bootstrap打开后会执行三段编译第一阶段用宿主 gcc 编译出一套新的 gcc 组件stage1第二阶段用 stage1 编译出的编译器再编一遍自己stage2第三阶段用 stage2 再编一遍stage3。最后比较 stage2 和 stage3 的产物是否一致一致说明编译器能稳定地编译自己没有因为编译器自身的 bug 导致代码生成错误。这个过程的意义在于可靠性。对于要用于生产或者长期使用的工具链多花一倍时间换一个经过自检的编译器是值得的。如果你只是临时用一下、编个项目--disable-bootstrap能省掉一半以上的时间这也是我在虚拟机里做实验时的常规选择。5.3 断线、断电、下班长任务怎么保活两三个小时的编译最怕的就是 SSH 会话断开一个SIGHUP就把make干掉了前功尽弃。解决办法是把任务放到tmux或者screen里跑# 建一个会话 tmux new -s gccbuild # 在会话里执行编译 cd /opt/build/gcc-13.2.0 make -j6 21 | tee build.log用CtrlB然后按D脱离会话任务继续在后台跑。下次连上来用tmux attach -t gccbuild回到会话。用tee build.log把日志存一份出了问题可以回溯特别是 OOM 这种不留明显错误信息的情况。如果不方便用 tmux也可以用nohupnohup make -j6 build.log 21 tail -f build.log但 nohup 的缺点是中断后想交互式恢复比较麻烦tmux 的体验好得多。5.4 make install 与库路径注册编译完成后先别急着装跑一遍make -j6确认退出码是 0。gcc 的构建有时候会出现部分成功的情况某些子目录失败了但整体不退出非零所以我习惯在make输出末尾搜一下 errorgrep -i error build.log | head -n 20确认干净之后再安装sudo make install装完之后关键一步是注册库路径否则新装的libstdc.so.6不会被系统找到导致编译出来的程序运行时报找不到符号。# 把新 gcc 的库目录注册到系统 echo /opt/gcc-13.2.0/lib64 | sudo tee /etc/ld.so.conf.d/gcc-13.2.0.conf sudo ldconfig # 确认生效 ldconfig -p | grep libstdc | head -n 3注意路径是lib64还是lib取决于你的系统架构和 configure 结果装完ls /opt/gcc-13.2.0/看一眼就知道。x86_64 上一般是lib64有些配置下会是lib。提示注册ld.so.conf.d会影响系统全局的库搜索顺序。如果机器上有其他软件依赖特定版本的 libstdc先确认新版本不会引起兼容问题。稳妥的做法是只在需要的时候用LD_LIBRARY_PATH临时指定而不是全局注册。6. 装完之后验证、切换与并行管理装完不等于能用验证这一步必须做而且要做三层。6.1 三层验证法第一层验证版本和路径/opt/gcc-13.2.0/bin/gcc -v /opt/gcc-13.2.0/bin/g -v正常输出里应该有配置参数、gcc 版本号、线程模型比如Thread model: posix。特别留意版本号是不是你要的那个配置参数里--prefix是不是对的。第二层验证实际编译能力cat /tmp/test.cpp EOF #include iostream #include vector int main() { std::vectorint v{1, 2, 3}; for (auto x : v) std::cout x ; std::cout std::endl; return 0; } EOF /opt/gcc-13.2.0/bin/g -stdc17 /tmp/test.cpp -o /tmp/test /tmp/test这一步能同时验证编译器本身、C 标准库头文件、链接器三样东西编过并能跑出结果说明整套工具链是完整的。第三层验证运行期库ldd /tmp/test | grep -E libstdc|libgcc如果libstdc指向的是新装的路径说明 RPATH 或者ldconfig配置生效了。如果还是指向/usr/lib64下的老库那就需要检查前面的库路径注册步骤或者编译时加上-Wl,-rpath,/opt/gcc-13.2.0/lib64。6.2 update-alternatives 做版本切换如果你希望系统里gcc这个名字能指向新版本同时保留随时切回旧版本的能力用update-alternatives是标准做法sudo update-alternatives --install /usr/bin/gcc gcc /opt/gcc-13.2.0/bin/gcc 100 \ --slave /usr/bin/g g /opt/gcc-13.2.0/bin/g sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 50 \ --slave /usr/bin/g g /usr/bin/g-11 # 查看和切换 sudo update-alternatives --config gcc优先级数字越大越优先100比50大所以默认会选新版本。这种方式的优点是切换集中管理缺点是仍然是全局的会影响所有用户。另一种更轻量的方式是把/opt/gcc-13.2.0/bin加到PATH最前面只在当前用户的 shell 里生效echo export PATH/opt/gcc-13.2.0/bin:$PATH ~/.bashrc source ~/.bashrc这种方式适合开发机上并存多个版本不同项目用不同环境。切换的时候改一下PATH顺序或者在项目里用绝对路径调用。6.3 回退方案与彻底清理源码编译最大的好处就是可回退。用--prefix装到独立目录的方案回退就是三步从PATH里去掉这个目录、删掉ld.so.conf.d里的配置文件、rm -rf安装目录。# 从 PATH 移除编辑 ~/.bashrc 删掉那一行 # 删除库路径配置 sudo rm -f /etc/ld.so.conf.d/gcc-13.2.0.conf sudo ldconfig # 删除安装目录 sudo rm -rf /opt/gcc-13.2.0 # 如果用了 alternatives 也要清掉 sudo update-alternatives --remove-all gcc整个过程不会动到系统自带的 gcc 和 glibc安全系数比直接覆盖系统目录高得多。7. 高频坑位排查实录这一节是全文最值钱的部分都是我或者同事实际踩过的坑。7.1 升级完还是旧版本三个隐藏陷阱这是搜得最多的问题gcc --version显示的还是老版本号。原因通常是三个按排查顺序来。第一个是PATH顺序。你装了新版本但没加PATH或者加了但排在后面shell 找到的还是/usr/bin/gcc。用which -a gcc能列出所有匹配的可执行文件看清楚谁在前面。which -a gcc echo $PATH type gcc第二个是 shell 的命令哈希缓存。bash 会把执行过的命令路径缓存起来你改了PATH之后不一定会重新查找。用hash -r清一下缓存或者直接exec bash重开一个 shell。hash -r gcc --version第三个是alternatives的链接没更新。用update-alternatives --config gcc确认当前指向或者直接看/usr/bin/gcc是个软链接还是真实文件ls -l /usr/bin/gcc update-alternatives --display gcc还有一个隐蔽情况是编译时用了CC或者CXX环境变量指定了老编译器比如在 Makefile 或者 CI 脚本里写死了/usr/bin/gcc这种情况下改PATH没用得改那个变量。7.2 编译期报错速查configure: error: Building GCC requires GMP 4.2, MPFR 2.4.0 and MPC 0.8.0这个报错几乎百分百是依赖没找到。要么跑download_prerequisites要么用--with-gmp系列参数指定路径。注意如果依赖装在非标准路径LD_LIBRARY_PATH也要带上否则 configure 阶段的编译测试会链接失败。fatal error: Killed signal terminated program cc1plus这一条基本就是 OOM。降并行度、加 swap、或者换成只编 c 不要 c三个办法任选。我之前在一台 8G 内存 8 核的机器上开-j8必挂降到-j4就稳稳的。error: char* strdup(const char*) is not a function这类是头文件缺失导致的隐式声明问题常见于在新 glibc 上编译老版本 gcc。解决办法是给CFLAGS加-D_GNU_SOURCE或者选一个和新 glibc 匹配的 gcc 版本。cannot find crti.o: No such file or directory是缺 C 库开发文件apt install libc6-dev或yum install glibc-devel解决。error while loading shared libraries: libmpfr.so.6出现在构建过程中说明依赖库没在库搜索路径里。export LD_LIBRARY_PATH/opt/gcc-deps/lib:$LD_LIBRARY_PATH然后重新make。7.3 运行期报错速查GLIBCXX 与 libstdclibstdc.so.6: version GLIBCXX_3.4.32 not found是典型的运行期库版本不匹配。你编译出来的程序链接了新版 libstdc但运行时加载的是系统的老版本。三种解法给程序加 RPATH、把新库路径写进ld.so.conf.d、或者运行前设LD_LIBRARY_PATH。我一般推荐 RPATH因为它跟着程序走不依赖环境变量。/opt/gcc-13.2.0/bin/g -stdc17 \ -Wl,-rpath,/opt/gcc-13.2.0/lib64 \ main.cpp -o main另一个相关问题是GLIBCXX_3.4.32找不到但你明明装好了新 gcc。这时候用strings看一下新库支持到哪个版本strings /opt/gcc-13.2.0/lib64/libstdc.so.6 | grep GLIBCXX | tail -n 5如果最高版本号小于你需要的说明装错版本了或者程序是用另一个编译器编的。7.4 内网离线与老旧系统的处理思路内网机器的难点在于依赖全要靠手搬。我的做法是在一台能上网的机器上完整走一遍流程然后把四样东西打包gcc 源码包、四个依赖库的源码包、一份configure命令记录、一份build.log关键片段。拿到内网机器上按同样的步骤执行。如果目标机器性能太差不适合就地编译也可以在外网机器上直接编好把/opt/gcc-13.2.0整个目录打成 tar 包搬过去。前提是两台机器的 glibc 版本和架构一致否则动态链接会出问题。跨大版本 glibc 的情况下这条路走不通只能就地编译。老旧系统比如 glibc 2.17 的机器上编译新 gcc最常见的坑是某些系统头文件缺少新特性定义。这时候可以尝试给CFLAGS加上-D_GNU_SOURCE -D_XOPEN_SOURCE700或者退一步选一个和系统年代接近的 gcc 版本。7.5 问题速查表症状大概率原因处理方式configure 报缺 GMP/MPFR/MPC依赖未找到跑download_prerequisites或--with-*指定路径make 中途Killed内存不足触发 OOM降-j并行度临时加 swapgcc -v还是老版本PATH 顺序、hash 缓存、alternativeswhich -a gcc、hash -r、--config gcc运行期GLIBCXX_x.x.xx not found加载了旧 libstdc加-Wl,-rpath或注册ld.so.conf.dcannot find crti.o缺 C 库开发包apt install libc6-dev构建中libmpfr.so.6 not found依赖库不在搜索路径设LD_LIBRARY_PATH解压后文件名乱码终端编码与包内编码不一致设LANGzh_CN.UTF-8或指定解压编码编译极慢几乎不动并行度过低或 swap 抖动提并行度检查free -h的 si/so8. 几条压箱底的经验前面讲的都是标准流程这一节说几个能实际省时间的做法。8.1 把成果打包成可分发 tar 包如果你们团队经常要在一批配置相同的机器上部署工具链别每次都重编。在一台机器上编好之后把安装目录打包cd /opt sudo tar -czf gcc-13.2.0-bin.tar.gz gcc-13.2.0目标机器上解压到同样的路径然后注册库路径sudo tar -xzf gcc-13.2.0-bin.tar.gz -C /opt echo /opt/gcc-13.2.0/lib64 | sudo tee /etc/ld.so.conf.d/gcc-13.2.0.conf sudo ldconfig这样部署一台机器只需要两分钟前提是目标机器的架构和 glibc 版本兼容。打包前用ldd /opt/gcc-13.2.0/bin/gcc看一下依赖了哪些系统库如果里面有非标准的路径那些库也得一起带走。注意gcc 的安装目录里有些路径是编译时写死的绝对路径比如内部搜索头文件的路径。所以解压路径必须和原来的--prefix完全一致否则会找不到头文件。这也是为什么我不建议用/usr做 prefix 的原因之一——换个路径就废了。8.2 增量重编别从零开始如果第一次编译因为某个参数不对需要重来不要make clean从头开始。gcc 的构建系统支持增量改了 configure 参数之后重新跑 configure再make一遍已经编好的部分会被复用通常能省掉一半时间。只有当你改了--prefix或者依赖库路径这种影响全局的配置时才需要删掉 build 目录重新来。另外make -j6之外还可以试make -l限制负载让系统在高负载时自动降低并行度适合多人在用的共享构建机make -j8 -l88.3 什么时候干脆别编最后说一句实在话源码编译 gcc 是个耗时耗力的活能不用就不用。如果仓库里有合适的版本哪怕版本低一点但够用优先用包管理器。如果需要多个版本并存Ubuntu 上有gcc-11、gcc-12这样的独立包直接apt install gcc-12就行比自己编省事得多。只有在包管理器明确解决不了的时候——离线、需要交叉、需要定制参数——才值得投入这两三个小时。我个人在实际操作中的体会是编 gcc 这件事的技术难度并不高真正的门槛全在准备和善后依赖版本搭配、内存估算、库路径注册、多版本切换这四件事做对了中间那个make就是等待而已。所以与其把时间花在盯着进度条不如在动手前把这一整套检查清单过一遍把可能出问题的环节提前堵上。另外强烈建议第一次编的时候全程用tee存日志出问题能回看第二次编同类环境的时候这份日志本身就是最好的操作手册。