ARTICLE DETAIL

资讯详情

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

CentOS7 升级 GCC 11 实战:用 devtoolset-11 解决编译难题

CentOS7 升级 GCC 11 实战:用 devtoolset-11 解决编译难题 讲个真实场景你从 GitHub 上拉了一个 2023 年之后还在活跃维护的开源项目兴冲冲执行./configure make结果编译到一半直接报错什么“does not name a type”“undefined reference to std::__cxx11”。我最初遇到这种问题还以为是代码有问题折腾半天才发现罪魁祸首是 CentOS7 上那个“万年不变”的 GCC 4.8.5。这个默认编译器实在太老了连 C14 都支持得勉强更别说现在项目普遍要求的 C17/C20。于是就有了今天的主题——在 CentOS7 上把编译器升级到 GCC 11。写这篇文章时我把整个过程的原理、踩坑、排查经验都整理了一遍适合两类人看一类是刚入行、在 CentOS7 上装软件总是遇到编译错误的新手另一类是生产环境里有大量存量 CentOS7 机器、需要在不动系统核心组件的条件下把工具链“悄悄”升级的运维或开发同学。我会从方案选型开始讲然后是完整的安装配置命令最后是升级之后必然遇到的各种问题和对应排查方法。1. 升级前先想清楚你要的是“替代”还是“共存”很多人一看标题“CentOS7升级GCC11”第一反应就是“把系统默认的 gcc 从 4.8.5 替换成 11”。这个想法我特别理解但实际操作中我非常不推荐直接替换/usr/bin/gcc。原因跟 CentOS7 这套系统的设计逻辑有关。1.1 CentOS7 为什么这么“顽固”地用 GCC 4.8.5CentOS7 从 2014 年发布到进入维护周期结束基础仓库里的 GCC 一直停在 4.8.5表面看是“落伍”其实这是一种刻意的稳定策略。系统里大量核心组件——glibc、内核模块、systemd、甚至yum和rpm自己都在当年基于 GCC 4.8.5 编译过。GCC 升级到新版本之后生成的代码在函数符号修饰、内联规则、异常处理、特别是 C 标准库 ABI 上都有可能发生变化。如果你直接把/usr/bin/gcc指向 GCC 11那下一次某个系统基础包因为安全更新而重编时新编译出的二进制可能会和现有系统库产生潜在不兼容这时候排查起来就非常烧脑了。所以我的核心观点是在 CentOS7 上所谓的“升级 GCC”正确的思路是安装一个新版本 GCC 作为并行工具链然后让它在特定 shell 环境或特定构建脚本里“伪装”成默认编译器。需要它的时候它就在不需要的时候系统原有的 4.8.5 继续完成它的底层工作。1.2 三条主流升级路线对比针对 CentOS7社区里常用的方案其实有三种我把它们的优缺点和适用场景整理成了表格方案安装复杂度切换灵活性对系统的影响适合场景源码编译安装 GCC 11高依赖多耗时长需要手动配置 PATH可控但 libstdc 库路径要小心完全离线环境、或者有特殊补丁需求SCLSoftware Collections中的 devtoolset-11低官方仓库直接装scl enable一条命令切换并行安装不影响系统默认工具链绝大多数在线服务器、需要快速切换第三方仓库如 RedHat Developer Toolset低同上同上有 RedHat 订阅或镜像源的场景容器方案如 gcc:11 镜像中需要进入容器完全隔离纯编译任务、不希望污染宿主机我个人的选择是第二条路用 SCL 的devtoolset-11。它在 RHEL/CentOS 生态里就是专门干这个事的官方维护安装简单默认路径独立的/opt/rh/目录不会覆盖系统的任何文件。而且它不只是提供一个裸的 gcc还把g、gfortran、make、gdb等整个工具链都打包在一起了对一个编译环境来说特别省心。1.3 为什么不推荐普通人去源码编译 GCC既然 SCL 这么方便为什么网上还是有很多人分享“源码编译 GCC 11”我分析主要有两个原因。第一是某些线下生产环境是内网隔离的没有外部 yum 源只能拿着源码包去编译第二是少数场景需要给 GCC 打官方补丁或者定制编译参数。但源码编译这把刀本身有它自己的问题依赖非常多GCC 11 编译前需要gmp、mpfr、mpc三个数学库不是说你./configure make就完事前置条件缺一个就报错。编译时间以小时计make -j$(nproc)全速编译四核八线程的机器大概也要 30 到 50 分钟遇到一些小内存机器还可能直接 OOM。后期管理麻烦源码安装到/usr/local/之后你想卸载几乎只能手动删如果装的版本多了/usr/local/bin下会非常混乱。所以只要条件允许我都是优先用现成的包管理器方案。接下来进入正题看看 devtoolset-11 到底怎么装。2. 手把手安装 devtoolset-11下面这一段可以当作“操作手册”来用。我默认你的 CentOS7 是能访问外网 yum 源的哪怕是通过内网镜像操作前先用cat /etc/redhat-release确认一下系统版本。我实测的版本是 CentOS Linux release 7.9.2009内核 3.10.0-1160。2.1 安装前准备启用 SCL 仓库SCL 仓库对应的包名是centos-release-scl有的机器上默认已经装了你可以先执行一条命令看看yum list installed | grep centos-release-scl如果没有输出那就手动装上yum install -y centos-release-scl这个包会往/etc/yum.repos.d/下写入CentOS-SCLo-scl.repo和CentOS-SCLo-scl-rh.repo两个仓库文件。装的时候留意一下最后几行输出如果提示找不到这个包先执行yum install -y epel-release因为部分镜像源里centos-release-scl依赖 epel 的元数据。然后再yum clean all yum makecache重新尝试。2.2 安装 devtoolset-11 的工具链仓库就绪后执行下面的命令yum install -y devtoolset-11-gcc devtoolset-11-gcc-c devtoolset-11-make解释一下这几个包各自的用途devtoolset-11-gccC 编译器本体提供gcc命令。devtoolset-11-gcc-cC 编译器本体提供g和c命令同时安装新版头文件和libstdc标准库。devtoolset-11-make新版 GNU Make某些项目对新版 make 有要求建议一并装上。如果你想把调试工具、性能分析工具也补齐可以直接装元包devtoolset-11-toolchain它会把工具链全家桶一起拉下来。我这里没有选择全家桶原因就是生产机器上能少装就少装后面真需要某个组件再补也不迟。2.3 如何确认安装成功安装结束后工具链被放在/opt/rh/devtoolset-11/root/usr/bin目录下不是一个被系统 PATH 自动识别的路径。你可以直接调用完整路径看一眼版本/opt/rh/devtoolset-11/root/usr/bin/gcc --version能看到类似gcc (GCC) 11.2.1 20220127 (Red Hat 11.2.1-9)的输出就说明装成功了。但这样用实在太别扭因为每次编译都要敲全路径。下一节讲它的标准使用方式。3. 让新 GCC 真正“上岗”切换与配置SVN 这类工具链的“切换”其实是一个环境变量的操作本质上是把新版工具链的bin目录插到 PATH 的最前面。SCL 官方提供了一条专用的 shell 命令scl enable能把这件事做得非常优雅。3.1 临时切换只要当前终端生效最简单的方式scl enable devtoolset-11 bash这条命令会启动一个新的 bash 子进程在这个子进程里 PATH 已经被修改gcc、g指向的都是 11 版本。你可以直接运行gcc --version which gcc在scl enable环境里which gcc输出会指向/opt/rh/devtoolset-11/root/usr/bin/gcc。这个方式的好处是“用完就走”退出这个终端或子进程后其他一切回到旧版本状态。我在测试环境验证软件能否用新编译器编译时就是这么干的编译测试全在一个scl enable的 shell 里进行不担心污染全局。3.2 永久切换写进 bashrc 还是只写进脚本很多教程会说“把scl enable devtoolset-11 bash写进~/.bashrc”这样每次登录都是新版本。我在生产环境里其实不太建议这么干。原因跟你前面提到的系统组件稳定策略一样如果某个系统管理员某天在交互式 shell 里手动编译一个内核模块或系统服务而这一切是在新版 GCC 环境下进行的编译出来的东西跟内核头文件的匹配度就容易出问题。我更喜欢的方式是在具体项目的构建脚本里做环境声明。比如你有一个build.sh开头写上source /opt/rh/devtoolset-11/enable这个enable脚本专门用来在一个 shell 里激活工具链效果跟scl enable devtoolset-11 bash类似但不需要额外启动子 shell。之后脚本里所有 gcc 调用都自动使用新版本。这样做到“环境随项目走”别人 clone 项目过来按照 README 跑./build.sh就能得到一致的构建环境比依赖每个人手工scl enable要可靠得多。3.3 如果你想一劳永逸地改变“默认编译器”确实有一些场景是需要默认 GCC 直接变成 11 的比如一键安装某些大型软件依赖这些软件的编译脚本里写死了gcc而且又不支持CC/path/to/gcc覆盖。这时候有两种方式第一种是创建软链接把新 gcc 链接到/usr/local/bin这个目录 PATH 搜索顺序一般排在/usr/bin之前ln -sf /opt/rh/devtoolset-11/root/usr/bin/gcc /usr/local/bin/gcc ln -sf /opt/rh/devtoolset-11/root/usr/bin/g /usr/local/bin/g ln -sf /opt/rh/devtoolset-11/root/usr/bin/c /usr/local/bin/c这种方式比直接改/usr/bin/gcc安全因为系统的 yum、kernel 等机制大多调用/usr/bin/下的编译器不会用到/usr/local/bin。但要注意/usr/local/bin下的链接版本跟系统 GCC 版本不一致时某些编译系统可能检测到“GCC 版本不匹配”的警告属正常情况。第二种是修改~/.bashrc内容。说句实话如果不是“个人开发机、你说了算”的环境不要这么做真的。3.4 联动 CMake、Makefile 等常见构建工具GCC 本身切换好了但构建工具不一定自动跟着切。拿 CMake 举例我实测中经常遇到这种情况在scl enable环境里执行cmake ..结果 CMake 自动探测编译器时仍然找到了/usr/bin/gcc因为 CMake 的缓存里可能已经记录过旧编译器路径。最干净的做法是在 CMake 命令行里显式指定cmake -DCMAKE_C_COMPILER$(which gcc) -DCMAKE_CXX_COMPILER$(which g) ..前提是当前 shell 已经在 devtoolset 环境里这样$(which gcc)自动指向新版本。对于普通 Makefile很多项目支持环境变量CC和CXX你可以这样用export CC/opt/rh/devtoolset-11/root/usr/bin/gcc export CXX/opt/rh/devtoolset-11/root/usr/bin/g make clean make能用环境变量的就不要去改 Makefile这是我在多个项目里踩坑之后的共识。4. 编译期和运行期最容易出问题的几个地方GCC 装好、切换完了不代表万事大吉。我把升级之后最容易踩的坑分成两大类编译期的头文件/库路径问题以及运行期的动态库版本问题。这两类问题占了我遇到问题的八成以上。4.1 头文件路径新 GCC 找不到 C 头文件如果你只安装了devtoolset-11-gcc而没装devtoolset-11-gcc-c那么以新 GCC 编译任何 C 代码都会报fatal error: iostream: No such file or directory这个问题的本质是C 编译器包装包没带 C 标准库头文件。解决方法很简单回到第 2.2 节装devtoolset-11-gcc-c包。但我还想强调另一种情况你的项目用了额外的第三方头文件目录比如/usr/include/xxx新版 GCC 和旧版 GCC 的默认头文件搜索顺序差异可能会导致“找到旧版本头文件、再找新版本头文件”的局面。排查时可以用这个命令看搜索顺序echo | gcc -E -v -x c -输出的search starts here:后面就是头文件搜索路径你可以确认一下新版本的/opt/rh/devtoolset-11/root/usr/include/c/11是否出现在/usr/include之前。如果顺序反了编译时用-idirafter或-isystem手动调整。4.2 运行期动态库GLIBCXX 版本不匹配这是升级工具链后最经典的问题。新 GCC 11 编译出的 C 程序默认链接的是新版libstdc.so.6而系统/usr/lib64/libstdc.so.6是 GCC 4.8.5 时期生成的版本号只到GLIBCXX_3.4.19。当程序拷到另一台未装 devtoolset-11 的机器上运行时就会报/lib64/libstdc.so.6: version GLIBCXX_3.4.29 not found解决办法有两个方向。第一在运行机器上也装上 devtoolset-11或者直接把/opt/rh/devtoolset-11/root/usr/lib64/libstdc.so.6复制到运行库路径。第二编译时加入 rpath 选项让程序优先寻找新库g -o app app.cpp -Wl,-rpath,/opt/rh/devtoolset-11/root/usr/lib64我建议生产环境里编译发行动态库或二进制时评估一下运行环境是否都能保证有新版 libstdc。如果不能-Wl,-rpath或LD_LIBRARY_PATH是最稳妥的兜底方案。4.3 glibc 版本的隐藏限制这里要特别提醒GCC 11 编译出的程序虽然本身不一定依赖特别新的 glibc但如果你用 GCC 11 去编译较新的第三方库例如一些较新版本的 OpenSSL那些库的编译环境可能已经预设了较高的GLIBC_XX版本要求。CentOS7 系统自带的是 glibc 2.17某些新软件源码里会用#ifdef处理旧 glibc但也有一些不会。判断一个现有二进制需要多高的 glibc 版本可以用objdump -T /path/to/your_binary | grep -o GLIBC_[0-9.]* | sort -u看到GLIBC_2.18或更高版本那说明这个程序没法在 CentOS7 默认环境里跑。这种情况属于“GCC 升级也解决不了”的范畴你只能去找旧版本依赖或者考虑换新系统。所以我的原则是升级 GCC 能解决编译源码的版本问题但不能保证新版本源码在老系统的运行兼容动手之前要对目标依赖链心里有数。5. 常见问题与排查实录我把自己和身边同事踩过的问题汇总成一个速查表几乎覆盖了“CentOS7 升级 GCC11”之后可能会遇到的九成场景。如果你的问题不在表里大概率是环境本身的特殊问题把报错日志贴给搜索引擎优先搜 Red Hat Bugzilla。现象原因解决思路gcc: error trying to exec cc1plus: execvp: No such file or directory只装了 gcc没装 gcc-cyum install -y devtoolset-11-gcc-cversion GLIBCXX_3.4.29 not found运行时加载了系统旧 libstdc设置LD_LIBRARY_PATH或编译加 rpathfatal error: bits/cconfig.h: No such file or directoryC 头文件路径搜索顺序错误检查gcc -E -v -x c -输出确认新 include 路径在前scl enable devtoolset-11 bash后仍显示旧版本当前 shell 被缓存 PATH或 scl 安装不正确确认which gcc必要时hash -r清除命令哈希yum install devtoolset-11-gcc提示无此包SCL 仓库未启用或镜像源未同步检查/etc/yum.repos.d/yum repolist确认仓库列表编译时提示unrecognized command-line option -stdc17当前调用的还是旧版 GCC确认gcc --version检查 PATH 中最前面的目录用新版 GCC 编出的程序在别的机器上崩溃新 C ABI 与旧库冲突尽量静态链接-static-libstdc或把新版库带上编译内核模块报错/usr/bin/gcc被替换或 PATH 混乱编译内核模块时显式用系统自带编译器不要用 devtoolset再补充几个我实操中总结的细节第一不要急着删/usr/bin/gcc或者给/usr/bin/gcc做软链接。系统升级或yum update时如果某些基础包重编需要调用 gcc而/usr/bin/gcc指向了错误版本后果可能很隐蔽。我见过一台机器因为手动替换/usr/bin/gcc导致yum运行脚本时编译临时工具失败最后只能从救援模式修复相当折腾。第二scl enable devtoolset-11 bash的进程机制要清楚。它实际是启动了一个子 bash 进程你在里面敲exit就会退出新环境。如果要在脚本里用它请改用source /opt/rh/devtoolset-11/enable否则脚本里面定义的变量和函数在子 shell 中生效脚本结束后不保留。第三如果你同时装了多个版本的 SCL 工具链比如 devtoolset-7 和 devtoolset-11切换的时候要小心环境变量叠加问题。scl enable devtoolset-11 bash不会自动清掉 devtoolset-7 的路径局部环境变量会以新 PATH 为准但有时候会遇到头文件混杂两个版本的情况。遇到这种问题干脆在新开的 shell 里先env -i重置环境再scl enable。第四关于CC环境变量要和编译缓存配合。如果你之前用旧编译器跑过ccache那ccache里可能有旧编译器的缓存你用新 GCC 编译时会发现有些源文件“没有重新编译”实际是命中了旧缓存。解决办法ccache --clear第五如果公司有自建的离线 yum 源但同步 SCL 仓库不完整安装devtoolset-11-gcc时可能会提示缺少某些依赖包。这时不要盲目跳过依赖建议先从仓库里找到完整的devtoolset-11系列包列表确认libstdc-devel这个关键依赖在源里。它决定了新 g 能不能正常工作。第六devtoolset-11 内部的make版本比较新GNU Make 4.2.1而系统自带的是 GNU Make 3.82。如果一个项目里混用了老版本 make 的语法切到新 make 编译时可能会报一些奇怪的missing separator错误这时候可以不用devtoolset-11-make明确调用系统的/usr/bin/make。虽然新编译器配旧 make 怪怪的但至少能定位问题范围。6. 聊点过来人经验升级工具链不如规划好环境最后用我个人的体感来收尾。前段时间帮一个团队排查 CI 机器上的编译问题他们抱怨“GCC升级到 11 之后反而有更多项目编译失败了”。我上去一看这台机器同时装了 SCL 的 devtoolset-11、源码编译的 GCC 12、还有 Conda 自带的 GCC加上各种PATH写在不同的配置文件里编译时到底调用哪个全看环境变量运气。后来我建议他们把机器上不同版本的 GCC 彻底隔离项目构建脚本里固定好工具链路径设置CC/CXX环境变量跑了一次全量回归问题立刻少了一大半。所以我想强调的经验是在CentOS7这种老系统上升级GCC最核心的不是安装某个版本而是建立“环境隔离”的思维。开发环境用 devtoolset 切换是合理的生产环境跑编译任务则最好在干净的容器里做宿主机保持无污染。CentOS7 已经是很老很老的操作系统了越早规划好工具链的替代方案后面被依赖问题绊倒的概率就越低。如果你只是临时要编译一个开源软件我建议直接选 devtoolset-11如果你是要长期维护一个编译环境不妨多花点时间把工具链版本和软件版本写进配置文件里做版本锁定。这样不管系统怎么更新你的编译现场永远是可控的。
返回列表