拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CentOS 7 下 gcc 4.8.5 升级到 9.3.1 的完整实操指南

CentOS 7 下 gcc 4.8.5 升级到 9.3.1 的完整实操指南

去年年底给一台 CentOS 7 服务器编译某个开源项目时,configure 阶段直接甩了我一行红字:Error: gcc version must be at least 7.0, but 4.8.5 found。那一刻我才认真审视了这台机器的编译器——CentOS 7 自带的 gcc 4.8.5 是 2013 年的产物,它连 C++17 都不认识,更别说现代软件开发里那些依赖新编译特性的项目了。

如果你也在 CentOS 7 上遇到过类似的版本报错,或者一直知道 gcc 太老但不知道从何下手升级,那这篇实操笔记应该能帮上忙。我会从零开始,完整走一遍 gcc 4.8.5 升级到 gcc 9.3.1 的过程——包括环境准备、源码编译、版本切换、动态库配置,以及我实际踩过的几个坑。整个过程不需要重装系统,也不需要动系统自带的旧编译器,属于“和平共处、按需切换”的路线。

1. 为什么这版编译器非动不可

1.1 gcc 4.8.5老到什么程度

先给不熟悉的读者补个背景。CentOS 7 发布于 2014 年,它自带的 gcc 是 4.8.5,这个版本对 C++11 的支持属于“半成品”状态——很多特性要么缺失要么有 bug。到了 gcc 5 才开始默认启用 C++11,gcc 6 才完整支持 C++14,而 C++17 要到 gcc 8 才基本落定,gcc 9 才比较完整。

所以如果你在 CentOS 7 上跑这些操作,大概率会撞上版本墙:

  • 编译新版 Python(尤其 3.7 以后的版本),部分扩展模块会检测 C++ 编译器版本;
  • 编译新版 Node.js、Rust 工具链、OpenCV、Boost 等依赖 C++14/17 特性的项目;
  • 编译需要-std=c++17参数的代码,gcc 4.8.5 直接报unrecognized command line option;
  • 安装一些对工具链版本有硬性要求的科学计算库或深度学习框架。

最坑的不是 gcc 本身老,而是 CentOS 7 的系统组件(yum、内核模块、部分 system 工具)依赖这份旧工具链,你不能涸泽而渔——直接把系统 gcc 换掉,很可能把系统搞坏。所以正确的思路是:新 gcc 装到独立路径,和系统旧的 4.8.5 共存,需要时用环境变量切换。

1.2 为什么选9.3.1而不是更高版本

你可能要问:都升级了,为什么不直接上 gcc 12 甚至 13?

这里有个关键限制:CentOS 7 的 glibc 是 2.17,2012 年的老版本。glibc 太老会导致两个问题——一是新版本 gcc 在编译过程中可能依赖更新的 glibc 特性;二是即使 gcc 编译出来了,它生成的二进制程序在运行时也可能因为找不到 glibc 里的新符号而报错。

根据社区大量实践反馈,gcc 9.x 是 CentOS 7 上兼容性和功能性的最佳平衡点。它完整支持 C++17,部分支持 C++20 实验特性,同时不会对 glibc 2.17 提出苛刻要求。业内俗话叫“甜点版本”。标题里的 9.3.1 我解释一下:GNU 官方最后发布的是 9.3.0,9.3.1 这种带小版本后缀的通常是 Red Hat 系(devtoolset)打了补丁后的标记。实操中我们下载的就是 gcc-9.3.0 官方源码包,装完目录命名成 9.3.1 即可,不影响使用。

下表对比了各版本在 CentOS 7 上的表现,方便你决策:

gcc 版本C++ 标准支持glibc 2.17 兼容性编译耗时适用场景
4.8.5(系统自带)C++11 不完整原生兼容无需编译系统组件、旧项目维护
7.xC++14 完整良好中等多数开源项目
9.3.1C++17 完整良好较长主流选择,兼容性最好
11/12C++20 较完整一般,可能链接报错更长新特性强需求,风险较高

2. 动手前先摸家底:环境检查、网络与依赖

2.1 升级前的五项检查

源码编译 gcc 是个耗时耗资源的工程,不提前检查环境就开干,很容易中途翻车。我个人会依次跑这几条命令确认家底:

# 查看系统版本 cat /etc/redhat-release # 查看当前 gcc 版本 gcc --version g++ --version # 查看 CPU 核数与内存 nproc free -h # 查看磁盘空间(源码包 + 编译产物约需 5~6GB 空闲) df -h /usr/local/src

重点说下内存和磁盘。编译 gcc 不是闹着玩的,make -j并发拉满时,每个编译进程大约吃掉 800MB 到 1GB 内存。我见过有人在 2G 内存的机器上直接make -j4,结果编译到一半被 OOM Killer 干掉了。经验公式是:-j并发数 = min(CPU 核数, 内存 GB 数 × 1.5)。4 核 8G 内存的机器,-j4是安全的;2G 内存就别贪了,-j2慢慢磨。

磁盘空间也一样,gcc 源码包解压后大约 1.5GB,build 目录里中间文件和最终安装产物加一起 4GB 左右,所以预留 6GB 是稳妥的。

2.2 换源与安装编译依赖

接下来是网络和 yum 源。这里有个时代背景必须提:CentOS 7 已于 2024 年 6 月 30 日 EOL,官方默认的mirror.centos.org源已经停止服务了。如果你现在直接yum install大概率报错,这就对应了很多人在搜的“centos7 无法 ping 通百度”和“centos7 更换国内 yum 源”这两个问题——前者多半是网络不通,后者是源失效。

推荐直接换成阿里云 Vault 源,一次性解决依赖安装问题:

# 备份原源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak # 写入阿里云 vault 源 cat > /etc/yum.repos.d/CentOS-Base.repo <<'EOF' [base] name=CentOS-7 - Base baseurl=https://mirrors.aliyun.com/centos-vault/7.9.2009/os/x86_64/ gpgcheck=0 [updates] name=CentOS-7 - Updates baseurl=https://mirrors.aliyun.com/centos-vault/7.9.2009/updates/x86_64/ gpgcheck=0 [extras] name=CentOS-7 - Extras baseurl=https://mirrors.aliyun.com/centos-vault/7.9.2009/extras/x86_64/ gpgcheck=0 EOF # 清理缓存并更新 yum clean all yum makecache

然后安装编译 gcc 所需的依赖。gcc 9.3 的 configure 阶段会检查 GMP、MPFR、MPC 三个库,官网源码包自带了一个./contrib/download_prerequisites脚本可以自动下载,但国内网络环境下从 GNU 官方下载这些依赖包速度很慢。我更推荐直接用 yum 安装,省事且版本足够:

yum install -y gmp-devel mpfr-devel libmpc-devel

如果你的 yum 源里这几个包版本比较旧,configure 阶段报版本不足,再考虑用源码方式编译安装这三个依赖库,或者用 download_prerequisites 脚本。绝大多数情况下 yum 源里的版本满足要求。

3. 选择路线:SCL还是源码编译

3.1 十分钟上手的devtoolset方案

在讲源码编译之前,先介绍一条“捷径”——Red Hat 官方提供的 Software Collections(SCL)。CentOS 7 上可以通过 SCL 直接安装 devtoolset-9,里面的 gcc 版本就是 9.x。

# 安装 SCL 仓库 yum install -y centos-release-scl # 安装 devtoolset-9 的 gcc 和 g++ yum install -y devtoolset-9-gcc devtoolset-9-gcc-c++ # 启用该版本 scl enable devtoolset-9 bash

启用之后再敲gcc --version,你会发现已经变成 9.x 了。这套方案的优点非常明显:不需要源码编译,几分钟搞定;由 Red Hat 测试过,可靠;也不污染系统原有环境。但缺点也硬:版本是 Red Hat 定死的,没有 9.3.1 这种细粒度选择;scl enable只在当前 shell 生效,每次新开终端都要重新执行;部分 SCL 版本对系统库有额外依赖,有时会拉进一堆新包。

3.2 两条路线的取舍逻辑

大家最纠结的就是:SCL 这么省事,还有必要源码编译吗?

我的判断标准很简单。如果只是为了让某个项目编译通过,不需要固定某个小版本,SCL 是性价比之王。但如果你是以下几种情况,源码编译更合适:

  • 需要精确的 gcc 小版本(比如 9.3.1),因为项目对编译器版本有严格校验;
  • 需要定制 configure 参数(比如禁用某些库、调整安装路径);
  • 想彻底掌握 gcc 的编译安装流程,或者需要在同类的离线/内网环境反复部署;
  • SCL 仓库安装失败或版本不合适,需要兜底方案。

说白了,SCL 是“要用”,源码编译是“要掌控”。这篇文章后续就专注源码编译路线,SCL 当作对比参照。

4. 源码编译完整流程:九步实操

4.1 下载源码与准备目录

源码编译的第一步是下载正确的源码包。GNU GCC 9.3.0 的下载地址有很多,国内推荐中科大或清华镜像,速度比 GNU 官网快得多:

cd /usr/local/src # 从国内镜像下载(以中科大为示例) wget https://mirrors.ustc.edu.cn/gnu/gcc/gcc-9.3.0/gcc-9.3.0.tar.gz # 解压 tar -xzf gcc-9.3.0.tar.gz # 建立独立的 build 目录 mkdir -p /usr/local/src/gcc-build cd /usr/local/src/gcc-build

这里有个很多人忽略的细节:gcc 官方推荐 out-of-tree 编译,也就是在源码目录之外单独建一个 build 目录,然后在 build 目录里执行 configure 和 make。原因是 gcc 的编译产物非常多,如果直接在源码目录里编译,以后想清理、重新配置、同时维护多个版本都会很痛苦。隔离出 build 目录,源码目录始终干净,有问题删掉 build 目录重来就行。

4.2 configure参数逐个拆解

configure 是整条链路里最容易出问题的一步。参数配置不对,后面 make 到死都是白忙。我用的配置如下:

../gcc-9.3.0/configure \ --prefix=/usr/local/gcc-9.3.1 \ --enable-languages=c,c++ \ --disable-multilib \ --disable-libsanitizer \ --disable-bootstrap

下面逐个说参数含义,这就是为什么我不建议直接抄别人命令草草了事的原因:

--prefix=/usr/local/gcc-9.3.1:指定安装目录。我刻意没有用/usr/local默认路径,而是加上了版本号,为的是以后可以同时保留多个 gcc 版本,互不覆盖。切换版本时改 PATH 即可。

--enable-languages=c,c++:只编译 C 和 C++ 编译器。gcc 能编译的语言很多(fortran、go、ada、objc 等),但绝大多数场景只需要 c 和 c++,全部编译会多花时间且没必要。

--disable-multilib:禁用多库架构。CentOS 7 的服务器几乎都是纯 64 位,不需要同时产出 32 位库。不关掉的话,编译时会尝试处理 i686 和 x86_64 两套库,耗时增加不说,还经常因为缺少 32 位依赖库而报错。

--disable-libsanitizer:跳过 sanitizer(内存检测)库。这个库在很多 CentOS 7 环境里会因为缺少系统头文件而编译失败,而绝大多数业务项目根本用不到它。直接禁掉,省心。

--disable-bootstrap:跳过编译器自举验证。默认情况下 gcc 会用新编译出的编译器再编译一遍自身来校验稳定性,耗时巨大。对于生产环境我个人建议保留 bootstrap(默认开启),但如果机器性能有限,或者你只是想把版本快速升上去,可以--disable-bootstrap,后续使用中发现异常再重编译即可。我在 4 核机器上实测,开了 bootstrap 的完整编译耗时约 100 分钟,关掉能省三分之一时间。

4.3 编译与安装的节奏控制

configure 顺利通过之后,就是最耗时的 make 阶段:

# 根据核数和内存调整 -j 参数 make -j4 # 安装到 /usr/local/gcc-9.3.1 make install

执行 make 时没有输出信息刷新?这是正常的。gcc 的编译输出非常啰嗦,但几分钟内没有报错就没有大问题。编译期间我们干等也是浪费时间,可以直接把输出同时写到日志文件里:

make -j4 2>&1 | tee /root/gcc-build.log

等 make 结束后,用这条命令快速扫一眼有没有红线错误:

grep -E "error:|Error " /root/gcc-build.log | head -20

如果 grep 没有任何输出,恭喜,编译基本成功。如果报错,别慌,后面第 6 章专门讲排错。make install 完成之后,新的 gcc 就已经躺在/usr/local/gcc-9.3.1/目录下了。

注意:make 报错直接看终端最底部的几行往往不够,因为错误信息可能早被刷上去了。tee落盘之后用 grep 精确抓 error 是效率最高的方式,这也是我为什么在第 6 章专门强调日志管理。

5. 升完还是旧版本?PATH与动态库的配置攻防

5.1 "升了个寂寞"的两种典型原因

这一步是大家最容易栽跟头的地方,也是热搜里“gcc升级后为啥还是旧版本”“怎么切换gcc版本为gcc-12”这两个问题几乎每天都有新帖的原因。

make install之后,我敲gcc --version,屏幕上赫然还是gcc (GCC) 4.8.5 20150623——升了个寂寞。原因很简单:系统在/usr/bin/gcc找到的是旧的 4.8.5,而新装到/usr/local/gcc-9.3.1/bin/gcc的版本根本没进 PATH。

另一个隐藏更深的原因跟动态库有关。就算gcc --version显示 9.3.1 了,你用新 gcc 编译出的程序运行时会去链接系统默认的/usr/lib64/libstdc++.so.6,这个旧库可能不包含新 gcc 生成的代码需要的 GLIBCXX 符号,运行阶段直接报version 'GLIBCXX_3.4.28' not found。这个坑稍后细说。

5.2 PATH与环境的三种配置方案

解决“找不到新版本”的思路是让 shell 优先找到新 gcc。有三种玩法,从简单到完整排列:

第一种,临时启用,适合单次测试:

export PATH=/usr/local/gcc-9.3.1/bin:$PATH

缺点是一条命令只对一个终端窗口有效。

第二种,写入用户环境变量,适合个人日常开发:

cat >> ~/.bashrc <<'EOF' export PATH=/usr/local/gcc-9.3.1/bin:$PATH export LD_LIBRARY_PATH=/usr/local/gcc-9.3.1/lib64:$LD_LIBRARY_PATH export CC=/usr/local/gcc-9.3.1/bin/gcc export CXX=/usr/local/gcc-9.3.1/bin/g++ EOF source ~/.bashrc

第三种,写入全局 profile,适合服务器统一配置:

cat > /etc/profile.d/gcc9.sh <<'EOF' export PATH=/usr/local/gcc-9.3.1/bin:$PATH export LD_LIBRARY_PATH=/usr/local/gcc-9.3.1/lib64:$LD_LIBRARY_PATH export CC=/usr/local/gcc-9.3.1/bin/gcc export CXX=/usr/local/gcc-9.3.1/bin/g++ EOF source /etc/profile.d/gcc9.sh

注意我在环境变量里同时设置了CC和CXX。很多项目的 Makefile 或 configure 脚本内部的编译器变量是硬编码成cc或gcc的,如果你只改 PATH,某些项目还是可能调到旧的/usr/bin/gcc。显式指定CC和CXX是最保险的。

这里也回应一下“怎么切换 gcc 版本”这个问题:如果你只想临时切回系统旧版本,只需要unset CC CXX并把 PATH 里的/usr/local/gcc-9.3.1/bin去掉即可。新版旧版共存互不干扰,这是所有操作的前提。

5.3 libstdc++动态库的正确姿势

再说动态库。gcc 9.3 对应的 C++ 标准库 libstdc++ 版本比 4.8.5 的新好几个台阶。你可以先验证一下新旧库支持的符号范围:

# 查看系统旧库 strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX | sort -V | tail -1 # 查看新 gcc 自带的库 strings /usr/local/gcc-9.3.1/lib64/libstdc++.so.6 | grep GLIBCXX | sort -V | tail -1

如果旧库最后一个 GLIBCXX 是GLIBCXX_3.4.19,而新库是GLIBCXX_3.4.28,那就说明你编译出的程序如果链接了系统旧库,运行时必然报符号找不到。解决方法是让动态链接器优先搜索新库路径。在 PATH 方案里我们加的LD_LIBRARY_PATH就是这个作用,但更稳妥的做法是写入 ld.so 配置:

echo "/usr/local/gcc-9.3.1/lib64" > /etc/ld.so.conf.d/gcc9.conf ldconfig

ldconfig执行完之后,用ldd验证一下新编译出的程序是否正确链接了新库:

# 写个测试小程序 echo 'int main(){return 0;}' > test.cpp /usr/local/gcc-9.3.1/bin/g++ test.cpp -o test # 查看链接到的动态库 ldd test | grep stdc++

看到libstdc++.so.6 => /usr/local/gcc-9.3.1/lib64/libstdc++.so.6才算真正成功。这一步做不做,直接决定你编译出的程序能不能带走运行,尤其是部署到其他没升级过 gcc 的机器上时。

6. 编译日志与高频坑位排错

6.1 日志先行:编译输出必须落盘

这一章写点实际的排错经验。先说一个非常容易被忽视的习惯:编译 gcc 这种耗时长、输出多的任务,一定要把输出落盘保存。前面第 4 章我用的make 2>&1 | tee /root/gcc-build.log就是这个目的。

很多人编译失败后只会盯着终端滚屏的最后几十行,这非常低效。正确做法是:

# 保存完整日志 make -j4 2>&1 | tee /root/gcc-build.log # 编译完成后系统排查错误 grep -E "error:|Error [0-9]|fatal" /root/gcc-build.log | head -30 # 定位到具体文件时,带上行号上下文 grep -n -B2 -A2 "error:" /root/gcc-build.log | head -40

日志落盘还有一个好处:编译中的 warning 比较多,你可以在日志里 grepwarning:分析潜在问题。比如有些新特性的代码在 gcc 9 下会有 deprecated 警告,虽然不影响编译成功,但提前看到了心里有数。

6.2 四个最常见的编译错误与修复思路

我把源码编译 gcc 过程中最容易翻车的四个问题列出来,全部是实际场景,附带修复方案。

第一,configure 阶段报 GMP/MPFR/MPC 版本不足。报错长这样:configure: error: Building GCC requires GMP 4.2+, MPFR 3.1.0+, MPC 0.8.1+。这种情况要么 yum 里这几个包没装,要么版本确实太旧。先执行yum install -y gmp-devel mpfr-devel libmpc-devel,如果 yum 源里的版本仍不够,就需要去源码编译这三个库,并把它们的路径通过--with-gmp、--with-mpfr、--with-mpc参数指给 gcc 的 configure。这一步的坑在于:三个库的安装路径要独立,且 gcc 的 configure 找不到它们时不会自动去/usr/local/lib找。

第二,make 阶段进程被杀,报g++: fatal error: Killed signal terminated program cc1plus。这是内存不足的典型症状。解决思路:先free -h确认内存,然后降低-j并发数,比如从-j4降到-j2。如果内存真的很小,可以临时加 swap:

# 添加 4G swap 文件(按需调整大小) dd if=/dev/zero of=/swapfile bs=1M count=4096 chmod 600 /swapfile mkswap /swapfile swapon /swapfile

第三,链接阶段报/usr/bin/ld: cannot find -lstdc++。这种情况多发生在 make 的后半段,原因一般是 gcc 源码里的 libstdc++ 目录编译有问题,或者 configure 检测库文件路径出错。先看日志里具体哪个文件链接失败,然后确认/usr/local/gcc-9.3.1/lib64下是否生成了libstdc++.so。如果没生成,说明 libstdc++ 子目录编译失败,多半是前一个错误引发的连锁反应,回到日志往上找真正的 error。

第四,编译出的程序在别的机器上报version 'GLIBCXX_3.4.28' not found。这就是动态库没带对。解决办法是把你机器上/usr/local/gcc-9.3.1/lib64/libstdc++.so.6.0.29这个库文件一起拷贝到目标机器,放到/usr/local/lib64/,然后配置LD_LIBRARY_PATH或写入/etc/ld.so.conf.d/。非要“静态编译”的话,可以在编译时加-static-libstdc++ -static-libgcc,但这样产物体积会变大,不推荐作为默认方案。

写到这里,整个 CentOS 7 上升级 gcc 4.8.5 到 9.3.1 的流程算是完整走了一遍。从我自己的实操感受来说,最大的体会是千万别手贱去动系统自带的 gcc——新版装到独立目录、用环境变量切换、让它们和平共存,才是长期稳定的玩法。另外就是编译前把日志落盘、把内存和 swap 准备好,这两件事能帮你省下大量排查时间。这套流程跑通之后,以后再遇到“某个项目要求 gcc 版本不低于某值”之类的需求,我会先看下 SCL 有没有合适的版本,没有就源码编译,算是形成固定套路了。

返回列表