简介:面向需要在无网络或内网环境下部署GCC编译环境的Linux运维与开发者,这份离线RPM安装包提供了完整的GNU编译器集合及依赖组件,可规避在线源不可用或依赖解析失败的问题,适用于RHEL/CentOS 6 x86_64系统。压缩包共24个文件,核心为22个rpm格式安装包,覆盖编译器本体、C/C++标准库、开发头文件等关键依赖;另含1个shell自动安装脚本与1个Markdown说明文档,便于一键执行与查阅步骤。包体整体约24MB,结构紧凑,便于拷贝至隔离环境使用。资源已有3988人学习,适合需要快速为老旧或受限系统配置C/C++编译工具链的开发者参考。通过该资源可掌握离线RPM方式安装GCC的完整依赖关系与安装流程,并在断网条件下完成编译环境搭建。
1. 先搞懂 gcc_rpm.tar.gz 是什么
拿到gcc_rpm.tar.gz这个文件的时候,第一反应多半是:这到底是个 RPM 包还是个 tar 包?其实两个都是——它是一堆 RPM 包被打成 tar.gz 归档后的产物,常见于离线安装 GCC、批量部署编译环境,或者从内网镜像站导出依赖包的场景。我在 CentOS 服务器上就经常收到这种文件,里面通常包含 gcc 主程序、g++、libgcc、libstdc++、cpp、binutils 等一批关联 RPM,甚至还有依赖链上的其他库。这篇文章就从gcc_rpm.tar.gz这个文件名出发,聊聊怎么正确安装、升级和切换 GCC,以及那些“升级后还是旧版本”“没有 rpm 命令”“CUDA 安装报错”之类的经典问题。如果你正在为离线环境装 GCC,或者刚升级完发现系统没反应,这篇文章应该能帮你少走不少弯路。
1.1 一堆 rpm 为什么要打包成 tar.gz
很多人第一次看到这种双重扩展名会觉得奇怪:既然已经是rpm,为什么不直接给一个目录或单独文件?其实原因很简单。RPM 虽然是 Linux 上的标准安装包格式,但它一装就是多个包,依赖关系也很复杂。假如你需要在内网的一台机器上装 GCC,把几十个 rpm 文件一个个传输太麻烦,而且容易漏。打成一个 tar.gz,既能用一条命令解压,又保留了目录结构,传输也更快。你可以先执行下面这条命令看看文件里有什么:
tar -tzf gcc_rpm.tar.gz输出里一般会列出gcc-9.4.0-1.el7.x86_64.rpm、gcc-c++-9.4.0-1.el7.x86_64.rpm、libstdc++-9.4.0-1.el7.x86_64.rpm之类的文件。这也就说明,它本质上是一个“离线 yum 仓库”的迷你版,里面的 rpm 包互相之间有依赖关系,安装时最好按顺序或交给包管理器去解析。
需要注意,gcc_rpm.tar.gz不是源码包,解压后不会出现configure脚本,所以不要想当然地去执行./configure && make。它里面的内容直接是二进制 rpm 包,安装后即可使用。区分源码包和 rpm 归档的方法是看文件列表:源码包里有.tar.xz、Makefile或configure.ac,而 rpm 归档里全都是*.rpm。
1.2 动手前先做环境检查,别急着解压
我在实际项目中见过不少同事拿到gcc_rpm.tar.gz后直接解压安装,结果装到一半报依赖错误。其实在动手前花两分钟做个环境检查,能避开大部分问题。你需要确认四件事:系统发行版、当前 GCC 版本、是否安装了 rpm 命令、以及磁盘空间是否充足。
cat /etc/redhat-release # 查看发行版 gcc --version # 当前 GCC 版本 which rpm # rpm 命令是否存在 df -h /opt # 确认安装目录磁盘空间如果系统是 CentOS/RHEL/Fedora,通常自带 rpm;但如果是 Ubuntu/WSL,就没有 rpm 命令,这时需要另想办法,后面我会专门讲。另外,GCC 的 rpm 包对底层库有依赖,比如libmpc、mpfr、gmp,如果系统版本太老,缺少这些库,安装时也会失败。可以用下面命令查看一个 rpm 包依赖哪些东西:
rpm -qpR gcc-9.4.0-1.el7.x86_64.rpm输出的依赖列表里如果有rpmlib开头的,那是 rpm 自身的特性,不用管;如果有具体的库名,比如libmpc.so.3()(64bit),就说明系统里必须存在这个动态库。如果缺依赖,优先从系统安装盘或镜像站找到对应的 rpm 一起打包进来,再重新生成一个新的gcc_rpm.tar.gz。
2. RPM 包安装 GCC 实操:从解压到正常使用
检查完环境,确认系统是 RHEL/CentOS 系列,也准备好了 rpm 工具,就可以开始安装了。这里我强烈建议不要一上来就rpm -ivh *.rpm,因为 rpm 不处理依赖,一旦包的安装顺序不对或者缺库,就会直接报错。下面是我平时惯用的两种安装方式,以及一个非常普遍的“升级后还是旧版本”问题。
2.1 两种离线安装方式:rpm -ivh 和 yum localinstall
首先解压到指定目录:
mkdir -p /opt/gcc_rpm tar -xzf gcc_rpm.tar.gz -C /opt/gcc_rpm cd /opt/gcc_rpm接下来有两种选择。第一种是传统方式:
rpm -ivh *.rpm这种方式简单粗暴,但不解决依赖顺序。如果 rpm 包自带互相依赖,rpm -ivh *.rpm有时候也能成功,因为 rpm 会先安装能被满足的包,遇到依赖不足就中止。一旦中间有包因为某个依赖缺失而失败,前面的包可能已经装上去了,留下一个残缺状态。所以我更推荐第二种方式:
yum localinstall *.rpm -y如果是 CentOS 8 或更新的系统,可以换成:
dnf install *.rpm -yyum localinstall会自动解析本地目录里的 rpm 包之间的依赖,也会尝试从已配置的 yum 源中补足缺失的依赖。它在离线环境下尤其好用,因为只要你的gcc_rpm.tar.gz打包时依赖收集得足够全,它就能一路顺利装完。
我把两种方式的区别整理成了表格,方便你根据场景选择:
| 方式 | 依赖处理 | 适用场景 | 缺点 |
|---|---|---|---|
rpm -ivh *.rpm | 不处理,遇到缺库直接报错 | 依赖齐全、数量少 | 安装顺序敏感,容易半途失败 |
yum localinstall *.rpm | 自动解析本地包间依赖,也可从源补依赖 | 离线包数量多、依赖复杂 | 需要系统有 yum/dnf,且本地包要完整 |
rpm -Uvh *.rpm | 升级已有包时使用 | 想让 GCC 替换系统旧版本 | 同样不处理依赖,依然有风险 |
2.2 升级后还是旧版本?PATH 和 alternatives 的坑
这是被问得最多的一个问题:“我明明升级了 gcc,为什么gcc --version还是旧的?”如果你也是用 rpm 包把 GCC 装到了自定义路径,或者通过 Software Collections 安装了 devtoolset,八成是 PATH 的优先级出了问题。
RPM 安装 GCC 时,通常会把二进制放到/usr/bin或/usr/local/bin。如果你在 CentOS 7 上装了 devtoolset-8 提供的 GCC 8.3.0,二进制实际路径是/opt/rh/devtoolset-8/root/usr/bin/gcc,而系统默认的/usr/bin/gcc还是 4.8.5。shell 查找命令时按照 PATH 从左到右找,/usr/bin通常排在/opt/rh/devtoolset-8/root/usr/bin前面,所以不管你怎么升级,敲gcc永远是旧版本。
解决办法有三个。临时切换:
export PATH=/opt/rh/devtoolset-8/root/usr/bin:$PATH或者用update-alternatives管理默认版本:
update-alternatives --install /usr/bin/gcc gcc /opt/rh/devtoolset-8/root/usr/bin/gcc 100 update-alternatives --config gcc如果是源码编译后安装在/usr/local/bin,同理,把/usr/local/bin调整到 PATH 前面,写入/etc/profile或当前用户的.bashrc。这里还要提醒一句,不要只看gcc,g++的版本也要一起确认,因为很多项目同时依赖 gcc 和 g++,切换时只改了 gcc 而忘了 g++,编译 C++ 程序时还是会用旧版本,产生一堆奇怪的报错。
2.3 没有 rpm 命令的系统,怎么处理这种包
如果你的机器是 Ubuntu/Debian,或者 WSL 默认 Ubuntu,那么gcc_rpm.tar.gz解压出来的*.rpm是无法直接安装的。很多人第一反应是“那我装个 rpm 命令”,确实可以执行sudo apt install rpm,但我不建议你这么做,因为 rpm 包即使是装上了,也不会被 dpkg 跟踪,后续卸载、升级都会很麻烦,系统里一半 deb 一半 rpm,简直是给自己埋雷。
更合理的处理方式有两条。一条是用alien工具把 rpm 转成 deb:
sudo apt install alien alien --to-deb *.rpm sudo dpkg -i *.deb但请注意,alien转换时对一些复杂的依赖关系和系统脚本处理得并不完美,尤其是 GCC 这种底层工具链,转换后可能出现各种诡异问题。另一条更省事的思路是:别在这个系统里死磕 rpm,直接用 Ubuntu 自带的 apt 源装 GCC:
sudo apt update sudo apt install build-essential在 WSL 里搭建 GCC 开发环境,我几乎只用这一条路。WSL 的 Linux 内核和 Ubuntu 软件源是完整可用的,直接 apt 安装才符合系统习惯。只有在目标机器明确必须使用某个 rpm 包时,我才会考虑让它跑一个 CentOS 容器作为编译环境,而不是强行把 rpm 塞进 Ubuntu。
3. GCC 升级路上的经典翻车现场
升级编译器这件事,很少有人能一次成功。很多时候,错误信息并不会直接指向“GCC 版本不对”,而是通过一个看起来无关紧要的报错,把你引到编译器版本这条线上去。我在这一章里整理了三个最常见的翻车点,每一个都有血泪教训。
3.1 CUDA 与 NVIDIA 驱动安装时报 failed to verify gcc version
如果你装过 NVIDIA 驱动或 CUDA,大概率见过这句话:
failed to verify gcc version. see log at /var/log/cuda-installer.log for details这个报错的本质是:CUDA 安装器会去检查当前系统的 GCC 版本是否在它支持的范围内。CUDA 对 GCC 的支持策略很保守,比如 CUDA 11.4 支持的最高 GCC 是 10.x,如果你的系统默认 GCC 是 11 或 12,安装器就会拒绝继续;反过来,如果 GCC 版本太老,也可能因为缺少某些新特性而失败。安装器还会调用 GCC 去编译一些测试代码,所以它验证的不只是版本号,还要求 GCC 真正可用。
碰到这种情况,先去看日志:
tail -n 100 /var/log/cuda-installer.log里面通常明确写了“expected gcc version between X and Y, but found Z”。然后在系统里找到支持范围内的 GCC,并用环境变量或软链接让安装器使用正确版本。常见做法是:
export CC=/usr/bin/gcc-9 export CXX=/usr/bin/g++-9 sudo ./cuda_installer.run或者驱动安装时,有些版本支持这样指定:
sudo ./nvidia-linux-x86_64-550.100.run --no-opengl-files即便加了参数,如果 GCC 本身版本不在支持列表里,驱动安装器照样会校验失败。另外还有一个隐藏坑:如果你此时已经升级过 GCC,但内核模块编译用的是/usr/bin/gcc这个绝对路径,而它指向的又是新版本,那么即使安装器验证通过,后续编译内核模块也可能失败。所以我现在的习惯是,安装 CUDA 或 NVIDIA 驱动前,先临时把系统gcc、g++统一切换到安装器支持的版本,装完再改回去。
3.2 升级 GCC 后旧软件还能不能跑:ABI 与 so 二进制兼容
有人问“用 GCC 编出的 so 差异会大吗”。这个问题不能一概而论。如果是同一个 GCC 版本,只是小补丁版本不同,编出来的 so 基本没差别。但跨大版本,比如从 GCC 4.8 升到 GCC 8.3,差异就非常明显。
GCC 5 是一个重要分水岭,它改变了 C++ 标准库的 ABI,std::string、std::list等容器的底层实现变了。如果你用新 GCC 编译出来的 so 依赖了新版本 libstdc++,而可执行程序是用旧 GCC 编译的,运行时很可能报GLIBCXX_3.4.21 not found之类的错误。换句话说,新编译的 so 和旧程序不一定兼容。
如果你的项目真的需要跨 ABI 场景,可以用一个编译选项强制回到旧 ABI:
g++ -D_GLIBCXX_USE_CXX11_ABI=0 -o mylib.so -shared mylib.cpp但这只是权宜之计。长期维护还是建议统一工具链版本,所有模块用同一个 GCC 大版本重新编译。另外,升级 GCC 后不要忘记 libstdc++ 动态库路径,尤其是源码安装的 GCC,默认库路径可能在/usr/local/lib64,如果系统找不到,需要设置LD_LIBRARY_PATH或更新/etc/ld.so.conf。
3.3 编译时链接不到 SDL/第三方库,和 GCC 版本有关吗
网上经常有人问“gcc link sdl 失败”,很多人误以为是升级 GCC 导致的。其实多数情况是开发包没安装,或者 GCC 的搜索路径和库安装路径不一致。比如编译 SDL2 程序时:
gcc -o game game.c -lSDL2如果报错找不到头文件或库,最常见的原因是缺少libsdl2-dev。这是头文件和链接下载文件的问题,和 GCC 版本没有直接关系。但 GCC 升级后确实可能出现一个冷门问题:源码编译的 GCC 安装在自定义 prefix 下,默认的库搜索路径不包含/usr/local/lib或/usr/local/lib64,这时候-lSDL2就算装了 SDL2 开发库,也可能找不到。
可以用下面命令查看 GCC 的搜索路径:
gcc -print-search-dirs如果发现库路径不对,编译时手动指定即可:
gcc -o game game.c -I/usr/include/SDL2 -L/usr/lib/x86_64-linux-gnu -lSDL2 -lSDL2main这个问题的通用解决思路是:先用pkg-config --cflags --libs sdl2查看正确的编译参数,再把它放到命令行里,而不是把锅甩给 GCC 版本。
4. 特殊场景下的 GCC 安装思路
并不是所有环境下都能轻轻松松用 rpm 装 GCC,也并不是每个人都想让系统 GCC 被升级。这里分享几个我实际用过的特殊场景,包括 conda 离线环境、WSL 开发环境,以及嵌入式 IDE 里被 GCC 插件卡住的情况。
4.1 用 conda 离线环境替代 rpm 方案:tar.gz 也能创建 GCC 环境
很多公司内网机器没有 root 权限,或者你不希望动系统默认的/usr/bin/gcc,这时 conda 是一个非常合适的隔离方案。有人会问,conda 环境不都是基于 yaml 创建的吗?其实 conda 本身也支持从本地压缩包创建环境。你可以在一台有网的机器上下载gcc_linux-64和g++_linux-64的 conda 包(通常是.conda或.tar.bz2格式),然后把它们打包成 tar.gz,带到内网去:
conda create --offline -n gcc-env /path/to/gcc_linux-64-*.conda /path/to/g++_linux-64-*.conda创建成功后,激活环境:
conda activate gcc-env gcc --version这种方法的好处非常明显:环境完全隔离,不会影响系统 GCC;也不需要 root 权限;想升级就重新创建一个环境。如果连 conda 源都没有,更直接的方法是用 conda-pack 把已经配置好的环境打成 tar.gz,在目标机器解压后 source activate,里面就带了一套独立的 gcc 和 g++。要注意的是,conda 里的 gcc 名字可能带有前缀,比如x86_64-conda-linux-gnu-cc,编译代码时最好显式指定 CC 和 CXX。
4.2 WSL 里搭建 GCC 开发环境,别再硬找 rpm 命令
WSL 是很多开发者的日常。在 WSL 里,如果下载了一个gcc_rpm.tar.gz,我建议直接把它扔到一边,除非你确是在模拟 CentOS 环境。WSL 默认发行版如果是 Ubuntu,安装 GCC 就一句话:
sudo apt update sudo apt install build-essential gdb装完后gcc --version就成了系统 GCC。如果你想在 VSCode 里写 C/C++,并直接用 WSL 里的 GCC 编译,可以装一个 Remote-WSL 插件,然后打开 WSL 目录,底层调用的就是这个 GCC。
还有不少做嵌入式开发的朋友会问:如何把 STM32 工程在 VSCode 下使用 GCC 编译?这里说的不是系统 GCC,而是 ARM 交叉编译工具链。常用的arm-none-eabi-gcc在 Ubuntu 下同样可以用 apt 安装:
sudo apt install gcc-arm-none-eabi在 VSCode 里配置tasks.json,把 command 指向arm-none-eabi-gcc,再搭配 Cortex-Debug 插件烧录调试,整套流程就通了。这个场景里完全没有 rpm 什么事,如果你还在纠结gcc_rpm.tar.gz,八成是找错方向了。
4.3 嵌入式/IDE 场景:从 STM32 到 S32 DS,GCC 也能统一
一些专业 IDE,比如 S32 Design Studio 3.6,会内置或要求外部的 GCC 工具链。最近有朋友遇到“安装插件 GCC 10.2 失败”的问题,多半是它找不到指定版本的编译器,或者系统默认 GCC 和插件要求的版本不一致。这种问题不建议去改系统 GCC,因为系统级改动影响面太大。
我采用的是“目录级工具链”方案:把一个符合版本要求的工具链 tar.gz 解压到比如/opt/gcc-10.2,然后在 IDE 的设置里把编译器路径指过去,再把 PATH 按需添加。这样既不影响系统其他程序,IDE 插件也能正确识别版本。对 STM32 来说,arm-gnu-toolchain的 tar.gz 包解压即用,也符合同样的思路。所以,当你在 IDE 里遇到 GCC 版本校验失败时,优先想“我能不能在外部准备一个独立版本”,而不是推翻系统 GCC。
5. 踩过这么多坑之后,总结的几条实用经验
写到这里,我发现最终解决问题的往往不是某个具体命令,而是做决策的思路。这一章我整理了几条个人经验,希望能帮你少踩点坑。
5.1 什么时候该用 rpm 包,什么时候干脆源码编译
如果目标是 CentOS 7 上把 GCC 升级到 8.3.0,且机器能访问内网 yum 源,优先使用 SCL 或 RPM 包,因为快、可回滚、管理方便。但如果你需要的是一个自定义配置的 GCC,比如只编译 C 和 C++,或者需要关闭 multilib,源码编译更合适。
源码编译 GCC 是件非常消耗时间的事情,有点耐心的话,一个 8.3.0 干净编译至少需要三四十分钟,机器配置差可能要一小时以上。configure 参数也很有讲究,不同选项会影响最终安装路径和功能,比如:
./configure --prefix=/usr/local/gcc-8.3.0 --enable-languages=c,c++ --disable-multilib make -j$(nproc) sudo make install如果你是首次编译,建议先configure --help看看有哪些选项。我用源码编译后,系统里往往会出现/usr/local/bin/gcc和/usr/bin/gcc并存,这又回到了 PATH 和 alternatives 的问题。所以不到万不得已,我还是优先推荐用 rpm 或 conda,省心很多。
5.2 升级 GCC 后必查的三项验证
升级完 GCC,很多朋友敲一句gcc --version看到新版本就以为大功告成。实际上至少要做三项检查,缺一不可。
第一,确认gcc和g++都指向新版本:
which gcc g++ gcc --version g++ --version第二,检查 PATH 的顺序,确保新版本路径排前面:
echo $PATH第三,检查 C++ 运行库版本是否匹配。如果你的程序链接了 libstdc++,用这个命令查看:
strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX对照你程序运行所提示的 GLIBCXX 版本号,如果系统 libstdc++.so.6 里的版本比程序要求的低,升级就没彻底。该重新生成软链接就重新生成,该更新 ldconfig 就更新。另外,改完 PATH 或 alternatives 后一定要重新登录或者source ~/.bashrc,否则当前终端还是旧环境。
5.3 RPM 查询和依赖定位技巧
最后一个实用技巧是关于 RPM 查询的,尤其是离线环境里排查依赖的时候。常用几个命令是:
rpm -qa | grep gcc # 模糊查询已安装的 gcc 相关包 rpm -qf /usr/bin/gcc # 查看某个文件属于哪个包 rpm -qpR gcc-9.4.0-1.el7.x86_64.rpm # 查看未安装的 rpm 包依赖 rpm -qpi gcc-9.4.0-1.el7.x86_64.rpm # 查看未安装包的信息 yum provides "*/gcc" # 在 yum 源中搜索命令对应的包如果你拿到一个gcc_rpm.tar.gz,却发现安装时报缺依赖,其实不必惊慌。先rpm -qpR把缺的依赖名记下来,去系统安装光盘的Packages目录或内网镜像里找到对应 rpm,下载后补进同一个目录,再重新打包成 tar.gz,下次就可以直接交给别人了。很多gcc_rpm.tar.gz文件就是这么来的——上一个安装的人已经踩过一遍依赖坑,再分发时就帮你把配套的包全放一起了。
这几年我处理过好几次离线 GCC 安装,最大的感受是:文件本身并不复杂,复杂的是系统里已经存在的旧工具链和各种依赖约束。不要急着解压安装,先想清楚你要哪个版本、装到哪里、会不会影响现有程序,再动手。最后再提醒一句,任何升级前,最好先备份一下/usr/bin/gcc和/usr/lib64/libstdc++.so.6,万一出问题还能快速回滚。
本文还有配套的精品资源,点击获取