一台刚装好银河麒麟V10 SP3的服务器,跑gcc --version显示 8.5.0,看起来没什么问题。但等到真正编译一个需要C++20特性的项目时,编译器甩出一堆no matching constructor for initialization、is not a member of std之类的错误,这时候才意识到,系统自带的gcc版本已经成了开发流程的瓶颈。
正好 gcc-toolset-10 这个包,给银河麒麟这种基于RHEL8生态的系统提供了一条比较稳妥的路——通过SCL机制安装一套独立的GCC 10工具链,和系统自带gcc共存互不影响。这篇文章记录我在V10 SP3上安装、配置、使用gcc-toolset-10的完整过程,重点讲清yum仓库的坑、scl机制的原理,以及编译时容易踩的雷。适合需要在麒麟系统上编译C/C++项目、又不想破坏系统自带工具链的开发者和运维同学。
1. 为什么非要装gcc-toolset-10:系统自带gcc的局限与SCL思路
1.1 银河麒麟V10 SP3自带gcc的版本真相
银河麒麟V10 SP3作为RPM体系的系统,软件栈和RHEL8/CentOS8比较接近。默认安装环境下,gcc版本是8.5.0。这个版本是2018年gcc 8系列的最后一个大版本,放在今天的生产环境里已经明显偏老。
偏老体现在哪?实际编译中我遇到几类典型问题:
- C++17标准库里的一些新特性在gcc 8.5里支持不完整,比如部分filesystem相关特性、
std::clamp、std::optional的某些用法等; - 想用C++20特性(concepts、ranges),gcc 8.x直接不具备;
- 较新的开源库版本在configure阶段会检测编译器版本,低于gcc 10就直接拒绝编译;
- gcc 8对部分架构的向量化优化和指令集支持也不够充分,同样代码在gcc 10下性能有明显提升。
这些限制不是靠改几个编译参数就能绕开的。项目里用#if __cplusplus >= 202002L或#if defined(__GNUC__) && __GNUC__ >= 10做条件编译的情况很常见,编译器版本不够,代码路径直接走老实现,行为差异也就随之而来。
如果你只在机器上跑现成软件,不编译代码,那gcc版本对你完全无感。但只要承担开发编译任务,gcc版本就是绕不开的硬指标。
1.2 直接替换gcc的风险与SCL带来的解决方案
遇到这个问题,很多人第一反应是卸载旧gcc,换装新版本。这个思路在普通开发机上问题不大,但在服务器上强烈不建议。原因有三个:
- 系统大量二进制和库,比如内核工具、图形库、数据库驱动,在构建时用的是系统自带的gcc 8。直接把默认gcc换掉,编译内核模块或第三方驱动时可能出现ABI不兼容;
- 系统glibc、libstdc++和gcc之间有错综复杂的依赖关系,强制升级可能导致yum无法正常运行,甚至系统启动出问题;
- 很多运维脚本、构建脚本硬编码了
gcc这个命令,替换后会把整个环境搞得不可控。
gcc-toolset-10的解决方案是SCL机制。思路很简单:把一整套独立的gcc 10工具链安装到独立目录下(典型路径是/opt/rh/gcc-toolset-10/root/usr/bin),核心库、头文件都自包含,跟系统gcc互不干扰。想用新编译器时,通过scl enable切换环境变量,临时把新gcc放到PATH前面;不启用时,系统表现得跟没装过一样。
这个隔离思路在生产环境里非常实用:既解决项目需要新编译器的问题,又不会影响系统自带工具链,更不会破坏依赖旧gcc的系统组件。后面所有操作都基于这一原则设计。
2. 安装前必须解决的yum仓库问题
2.1 默认仓库状态与报错特征
在V10 SP3上装gcc-toolset-10,最先遇到的往往不是软件本身,而是yum仓库不可用。我遇到过几种典型情况:
- 机器在内网,默认repo地址访问不了,执行
yum install直接报Could not resolve host或Failed to connect; - 官方源地址在部分网络环境下响应极慢,最后超时报
Operation too slow. Less than 1000 bytes/sec transferred; - 最小化安装后只启用了BaseOS,AppStream相关的仓库没启用,导致gcc-toolset-10这种位于AppStream仓库里的包根本搜不到。
所以安装前第一步是确认仓库状态。执行yum repolist。重点看有几条repo,状态是enabled还是disabled,Packages数量是否合理。如果这个命令都报错,那要先解决源本身的问题。
2.2 用ISO挂载做本地仓库(离线环境首选)
纯内网或离线环境,最稳妥的做法是把V10 SP3的ISO镜像挂载成本地仓库。路径以实际ISO和目录结构为准,基本步骤是:
mkdir -p /mnt/kylin mount -o loop /path/to/kylin.iso /mnt/kylin然后创建repo文件:
vi /etc/yum.repos.d/kylin-local.repo内容建议把BaseOS和AppStream分两个块写,很多同学只写一个块,结果gcc-toolset-10一直搜不到:
[kylin-local-baseos] name=Kylin V10 SP3 BaseOS baseurl=file:///mnt/kylin/BaseOS enabled=1 gpgcheck=0 [kylin-local-appstream] name=Kylin V10 SP3 AppStream baseurl=file:///mnt/kylin/AppStream enabled=1 gpgcheck=0保存后清理并重建缓存:
yum clean all yum makecache有一个很容易忽略的细节:ISO里通常同时有BaseOS和AppStream目录,repo里漏掉AppStream,gcc-toolset-10就搜不到。gpgcheck=0在离线内部环境通常没问题,但对安全要求高时,建议设置gpgcheck=1并导入ISO里的GPG key。我之前在加了签名验证的仓库里安装时遇到Public key for xxx is not installed的报错,执行rpm --import导入对应key才解决。
2.3 配置在线仓库的注意事项
能联网的话,也可以配置在线yum源。需要注意几个点:
- 架构要匹配,x86_64和aarch64对应的repo路径不同,写死路径时容易出错;
- 版本路径一定要对应SP3,不要混用SP1、SP2、SP3的源,强制混用轻则包版本冲突,重则把系统依赖搞坏;
- 配好后执行
yum clean all && yum makecache,再检查yum repolist的包数量是否正常。
配置示例(具体地址以你实际可用源为准):
[kylin-network] name=Kylin V10 SP3 Network Repo baseurl=http://your-internal-mirror.example.com/Kylin/SP3/ enabled=1 gpgcheck=0常见的yum报错和对策我整理了一下,方便对照:
| 报错信息 | 常见原因 | 对策 |
|---|---|---|
| Could not resolve host | 网络不通/域名解析失败 | 检查DNS,切换内网源 |
| Errors during downloading metadata for repository | 仓库地址错误或无法访问 | 换镜像源,确认路径 |
| Public key for xxx is not installed | 未导入GPG key | rpm --import 对应key |
| Nothing matches gcc-toolset-10 | 仓库缺少AppStream或包不完整 | 补配AppStream仓库 |
多个repo同时启用时,同名依赖包可能来自不同源,造成依赖解析混乱,甚至事务冲突。我的经验是只保留一个有效源,其他临时repo文件用enabled=0关掉,用的时候再开。
3. gcc-toolset-10安装的完整流程
3.1 先用yum查清楚相关包
仓库准备好之后别急着安装,先看有哪些包:
yum list available | grep gcc-toolset-10或者:
yum search gcc-toolset会看到类似这些包:
gcc-toolset-10:元包,包含gcc/g++/gdb等重要组件,一般推荐直接装这个;gcc-toolset-10-gcc:C编译器;gcc-toolset-10-gcc-c++:C++编译器;gcc-toolset-10-libstdc++-devel:C++标准库头文件,编译C++程序必备;gcc-toolset-10-gdb:调试器;gcc-toolset-10-gcov:覆盖率工具。
如果什么都搜不到,基本可以断定是仓库没配全。重点检查AppStream是否启用,或者源是不是精简版。这时候不要反复重试yum install,先把2.2或2.3的仓库问题解决,才能继续。
3.2 安装命令与依赖处理
我的建议是直接装元包:
yum install -y gcc-toolset-10这条命令会把编译器、标准库、调试器一起装好,避免后续编译时缺头文件、缺库再来补装。如果只想装C/C++工具链,也可以显式指定:
yum install -y gcc-toolset-10-gcc gcc-toolset-10-gcc-c++ gcc-toolset-10-libstdc++-devel安装时yum会自动解析依赖,gcc-toolset-10-runtime、libmpc、libmpfr这些都会被带上。这里有个判断依赖是否正常的经验:用元包安装时,依赖数量一般在30到50个包左右;如果yum给出的依赖列表特别少,比如只有三四个包,要警惕是不是仓库不完整,装完很容易缺东西。
3.3 安装完成的初步验证
装完先验证版本:
scl enable gcc-toolset-10 bash gcc --version输出gcc (GCC) 10.3.1这类版本信息就成功了。注意scl enable会进入子shell,exit退出后外部环境仍是系统自带gcc 8.5.0。
接着编译一个测试程序:
echo 'int main(){return 0;}' | gcc -x c++ - -o /tmp/test_gcc10 && /tmp/test_gcc10 && echo OK再检查关键头文件是否存在:
ls /opt/rh/gcc-toolset-10/root/usr/include/c++/10/看到bits、ext、experimental这些目录,说明libstdc++-devel已经装好。别跳过这步,我之前有一次只装了gcc和gcc-c++,没装devel包,写个最简单的#include <iostream>都报fatal error: iostream: No such file or directory,白折腾了二十分钟。
4. 让gcc-toolset-10真正为你所用:scl机制与编译环境
4.1 scl enable到底做了什么
很多人第一次接触SCL会问:为什么都安装好了,直接敲gcc --version还是旧版本?原因在于gcc-toolset-10的程序放在/opt/rh/gcc-toolset-10/root/usr/bin下,这个路径不在系统默认的PATH环境变量里。执行scl enable gcc-toolset-10 bash时,系统会先读取enable脚本,再进入一个新的bash进程。
这个enable脚本做的事情,从本质上看就是设置PATH、LD_LIBRARY_PATH、MANPATH,把新工具链路径排在前面。可以打开看一眼:
cat /opt/rh/gcc-toolset-10/enable能看到类似export PATH=/opt/rh/gcc-toolset-10/root/usr/bin${PATH:+:${PATH}}的内容。理解了这一点,"为什么脚本里编译还是旧gcc"这类问题就好排查了,说白了就是环境变量没生效。
4.2 永久启用与编译脚本里的坑
不想每次开终端都手动执行scl enable,可以在/etc/profile.d/下建一个脚本:
vi /etc/profile.d/gcc-toolset-10.sh内容就一行:
source /opt/rh/gcc-toolset-10/enable这样所有登录shell默认就能用新gcc。但全局生效需要谨慎,机器上多个项目依赖旧gcc时,全局启用会影响老项目的构建环境。我更推荐的做法是:
- 在项目构建脚本开头显式
source /opt/rh/gcc-toolset-10/enable; - 或者直接把编译器路径写死:
export CC=/opt/rh/gcc-toolset-10/root/usr/bin/gcc export CXX=/opt/rh/gcc-toolset-10/root/usr/bin/g++这里有个真实教训:有一次我用Jenkins跑CI,构建脚本里先执行了scl enable gcc-toolset-10 bash然后调make。当时子shell里环境是好的,但后续步骤因为子shell退出,环境变量又没了。排查了半天才发现,流水线每条命令都是独立shell,必须统一注入CC/CXX路径,不能依赖临时的scl环境。
4.3 多个工具集切换
gcc-toolset系列不只有10,V10 SP3的仓库里可能还有9,如果想用更新的版本,也可以装11、12。多个工具集并存时,切换同样是:
scl enable gcc-toolset-12 bash多个enable脚本叠加时,后执行的在PATH里排前面,也就是最后生效。这个特性在需要多版本时很有用,但也容易混乱,建议同一个会话里只启用一个工具集,避免PATH里同时出现多个gcc路径导致选错。
5. 编译实战中的报错与排查经验
5.1 头文件、库路径错误
升级后编译真实项目,最常遇到的就是头文件路径问题。典型报错:
fatal error: bits/c++config.h: No such file or directory根本原因通常是gcc-toolset-10-libstdc++-devel没装。编译器能找到,但标准库头文件不完整。解决方法:
yum install -y gcc-toolset-10-libstdc++-devel还有一种情况是构建脚本里用-I/usr/include强制定位了系统头文件,导致gcc10实际用的是系统旧头文件。排查方式很直接——看编译命令里的-I参数,确认没有覆盖/opt/rh/gcc-toolset-10/root/usr/include的搜索路径。
5.2 libstdc++.so.6版本问题
用gcc10编译出的程序,在相关库版本更旧的机器上运行时,可能遇到:
/lib64/libstdc++.so.6: version `GLIBCXX_3.4.xx' not found这是运行时动态库不匹配,不是编译错误。排查方法:
# 查看目标机器上libstdc++支持的GLIBCXX版本 strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX解决一般有三条路:
- 保证运行环境和编译环境一致,部署时同步升级libstdc++;
- 编译时使用
-static-libstdc++ -static-libgcc静态链接; - 编译时设置
-Wl,-rpath指向新工具链自带库路径,但这有侵入性,要谨慎使用。
我在项目中主要用方案1和方案3组合,既保证开发机、CI、生产机环境一致,又在部署脚本里通过LD_LIBRARY_PATH指定库路径,最可控。
5.3 旧代码在gcc10下的编译行为变化
工具链升级最大的隐性成本,是旧代码在新编译器加新标准库下的行为变化。gcc10相比gcc8有几个典型差异,我都踩到过:
- 默认开启
-fno-common:如果你在头文件里定义了一个全局变量,比如int g_config;,多个源文件包含该头文件,gcc8可能还能链接通过,gcc10会直接报multiple definition of错误。临时规避可以加-fcommon,但根治还是要规范代码,把定义放到某个cpp文件里; - C++20下隐式删除拷贝构造:某些依赖拷贝构造的旧代码在C++20模式会出现新的编译错误,因为标准变了。想减少问题,可以先不要全开
-std=c++20,用-std=c++17过渡; - 对未定义行为的检查更严格:
-Wall -Wextra下会多出新警告,有些库用旧编译器没警告,新编译器会有。建议打开警告逐个修复,防止问题累积。
这几个点如果不提前知道,排查起来会非常耗时,因为报错信息往往指向代码里完全没动过的地方,很容易误以为是自己改坏了。
我在几台V10 SP3机器上搭完这套环境后,最大的心得是:gcc-toolset-10只是给你提供了一个新编译器,真正隔开旧系统影响的还是要靠SCL的环境隔离意识和正确的构建配置。建议正式项目里把CC/CXX写死到toolset路径,把环境激活放在脚本最前面,不要依赖交互式shell环境。这样即使换人、换机器,依然能稳定复现同一套编译结果。