简介:这是一份面向 Ubuntu 18.04 用户的 GCC 离线安装资源,解决内网或无外网环境下无法通过 apt 在线安装编译器工具链的难题。资源包内含 25 个文件,由 24 个 deb 依赖包和 1 个一键安装脚本 do.sh 组成,覆盖 libubsan0、binutils、libasan4、gcc-7、libgcc-7-dev 等关键依赖,形成从底层运行库到编译器的完整链条。压缩包整体约 20.05MB,体积精简,便于拷贝至目标机器快速部署。目前已有 3203 人学习下载,适合需要在离线或受限网络环境中搭建 C/C++ 编译环境的开发者、运维人员及实验课用户。相比自行逐个收集依赖并手动安装,这份打包好的资源省去了逐个 dpkg 安装时版本匹配与依赖顺序排查的繁琐过程,直接执行 do.sh 即可按序安装,降低上手门槛。对刚接触 Ubuntu 离线软件包管理、不熟悉 dpkg 依赖关系的初学者尤为友好,也可作为离线环境批量部署 GCC 工具链的参考模板。
1. ubuntu18.04gcc.zip:它不是安装包,是编译器的“搬家方案”
接手内网构建机时,手头是 Ubuntu 18.04,系统带了 GCC 7.5,但我需要 C++17 的 filesystem 和更新的 OpenMP 特性。apt 源连不上,网上大多是“sudo apt install gcc-9”的教程,对离线环境完全没有帮助。我的做法是找一台能联网的同版本机器,把 GCC 9.3 整个安装目录打成一个 ubuntu18.04gcc.zip,解压到目标机后临时加进 PATH 就用。这个 zip 不是 deb 也不是源码包,而是一套可重定位的编译工具链,适合离线内网、合规受限、以及需要给多台同版本机器批量交付构建环境的场景。下面从包内结构、环境变量、常见翻车点到制作一个自己的离线包,逐个说清楚。
2. 读懂 ubuntu18.04gcc.zip:目录分工、版本选型与解压前的校验
2.1 解压后的目录里是谁在干活:bin、lib、libexec 的分工
很多人以为 gcc 是一个单独的可执行文件,实际上 bin 下的 gcc 只是驱动。真正的词法、语法、代码生成逻辑在 libexec 目录里,GCC 9.3 的目录结构大致是这样的:
gcc-9.3.0/ ├── bin/ │ ├── gcc │ ├── g++ │ └── gfortran ├── include/ # GCC 内部头文件,不是系统头文件 ├── lib/ │ ├── libstdc++.so.6 -> libstdc++.so.6.0.26 │ ├── libgcc_s.so.1 │ └── gcc/x86_64-linux-gnu/9.3.0/ # crtbegin.o、crtend.o、libgcc.a ├── libexec/ │ └── gcc/x86_64-linux-gnu/9.3.0/ │ ├── cc1 │ ├── cc1plus # 真正的 C/C++ 编译器本体 │ └── lto-wrapper └── share/ # man 文档、locale 等你在命令行敲 gcc,它只是按参数找到 cc1 或 cc1plus,然后把编译、汇编、链接这一串动作串起来。链接时用到的 crt1.o、crtbegin.o、libgcc.a、libstdc++.so 分散在 lib 目录里,这就是为什么单独拷贝 bin/gcc 到另一台机器一定会失败。
这份目录里最关键的是三个路径:bin 给 shell 提供 gcc 命令,lib 提供运行时和链接库,libexec 提供实际干活的编译本体。搬机器时,这三个目录必须保持相对位置,最好把整个 gcc-9.3.0 目录原样移动,不要只挑几个文件走。
2.2 为什么很多 18.04 离线环境选 GCC 9.3 而不是 7.5 或 12
Ubuntu 18.04 默认 GCC 是 7.5.0。对 C++11 来说够用,但对 C++17,GCC 7 对 filesystem 的支持是实验状态,std::filesystem 正式落地要到 GCC 8,完整可用要到 GCC 9。很多项目从 7.5 切到 9.3 之后,编译选项基本不用改,行为差异最小。
GCC 12 呢?它支持 C++20 更完整,但新版本带着更新版本的 libstdc++,对系统库依赖、ABI 兼容和一些老项目的代码写法都更挑剔。比如老代码里依赖 std::unary_function,在 GCC 12 里直接编译失败。而 GCC 9.3 在 18.04 上是退可守进可攻的均衡点。
版本选型上,我见过三类机器,各有各的固定搭配:
| 目标机器上的项目 | 推荐 GCC | 理由 |
|---|---|---|
| ROS Melodic、老设备驱动、PyTorch 1.x C 扩展 | 系统自带 7.5 | 第三方二进制按 GCC 7 的 ABI 编译,换编译器极易踩坑 |
| C++17 + CMake 新项目、内部基础库 | 9.3 | filesystem 完整,编译时间合理,老代码大多能编译 |
| 全新代码、C++20 特性依赖强 | 12 或 13 | 需要自行承担 libstdc++ 较新的兼容成本 |
这里提到一个常见场景:Ubuntu 18.04 上部署 LabelImg、ROS 相关工具时,pt 这类二进制包装包本身是用默认 GCC 7 编译的,不要因为嫌弃版本旧就全局换新,先区分你自己编译的代码和第三方二进制。
2.3 拿到 zip 后的第一步:sha256 校验与解压
在把所有文件拷进内网之前,先在能联网的机器上做一次完整校验。zip 包里我一般会放一个 SHA256SUMS 文件,内容大致是:
# 在 zip 所在目录执行 sha256sum -c SHA256SUMS # 正常输出类似: # gcc-9.3.0/bin/gcc: OK # gcc-9.3.0/lib/libstdc++.so.6.0.26: OK这一步不能省。GCC 离线包最常见的损坏不是文件内容少几个字节,而是通过网盘、内网盘中转后符号链接被破坏。zip 格式本身能记录符号链接,但如果你把 zip 拿到 Windows 上重新解压再打包,libstdc++.so 这几个链接文件会变成普通小文件,大小只有几十字节,后续编译时会出现“file format not recognized”这类报错,排查成本远高于校验成本。
解压命令:
unzip -q ubuntu18.04gcc.zip -d /opt ls -l /opt/gcc-9.3.0/lib/libstdc++.so*ls 输出里如果 libstdc++.so 和 libstdc++.so.6 显示为普通文件而不是libstdc++.so -> libstdc++.so.6.0.26,说明符号链接已经没了,把整个包删除,回到原始 zip 再走一遍内网传输,不要尝试手工修复大量链接。
3. 切换默认 gcc 到 9.3:三个环境变量和两种版本接管方式
3.1 三个环境变量和它们的设置顺序
解压到 /opt/gcc-9.3.0 后,直接运行 gcc 仍然是系统自带的 7.5。需要让 shell 优先找到新目录。我一般会新建一个环境脚本,而不是直接改 .bashrc:
sudo tee /etc/profile.d/gcc93.sh <<'EOF' export GCC_HOME=/opt/gcc-9.3.0 export PATH=$GCC_HOME/bin:$PATH export LD_LIBRARY_PATH=$GCC_HOME/lib:$GCC_HOME/lib64:$LD_LIBRARY_PATH export MANPATH=$GCC_HOME/share/man:$MANPATH EOF source /etc/profile.d/gcc93.sh这里三个变量各有作用:
- PATH 让 shell 找到 gcc、g++ 命令,必须把 $GCC_HOME/bin 放在最前面;
- LD_LIBRARY_PATH 让动态链接器找到 libstdc++.so.6、libgcc_s.so.1,否则运行时报找不到共享库;
- MANPATH 让 man gcc 显示的是 9.3 的文档。
另外还有一个容易漏的变量,供链接阶段寻找库文件和头文件:
export LIBRARY_PATH=$GCC_HOME/lib/gcc/x86_64-linux-gnu/9.3.0:$GCC_HOME/lib export CPATH=$GCC_HOME/includeLIBRARY_PATH 是给链接器找 -lstdc++、-lgcc 用的,CPATH 给预处理器找头文件。如果你的 GCC 解压在 /opt/gcc-9.3.0,使用自定义前缀时这两个变量几乎必须设置,否则会出现“找不到 crt1.o”或“找不到 vector 头文件”的报错。
3.2 用 update-alternatives 或符号链接接管默认版本:切换 gcc-12 的思路
环境变量只对当前登录 shell 有效。如果希望整个系统的构建工具默认走 9.3,最常见的是两种做法。
第一种,简单直接,用 /usr/local/bin 下的优先级链接覆盖 /usr/bin:
sudo ln -sf /opt/gcc-9.3.0/bin/gcc /usr/local/bin/gcc sudo ln -sf /opt/gcc-9.3.0/bin/g++ /usr/local/bin/g++/usr/local/bin 在默认 PATH 顺序上先于 /usr/bin,所以敲 gcc 会命中这里的软链。缺点是没有版本管理能力,之后想切回 7.5 要手动改链接。
第二种,用 update-alternatives 管理多个 GCC 版本,适合需要在 7.5、9.3、12 之间来回切换的机器:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 sudo update-alternatives --install /usr/bin/gcc gcc /opt/gcc-9.3.0/bin/gcc 90 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 110 sudo update-alternatives --config gcc数字 70、90、110 是优先级,数字大的优先。执行 --config 后会列出当前所有候选,输入编号即可切换。g++ 同理,需要单独建立一组 alternatives。注意 18.04 上如果 apt 装过 gcc-12,它会在 /usr/bin/gcc-12,不会自动顶掉系统默认 gcc,这就是很多人问“gcc 升级后为啥还是旧版本”的直接原因——系统默认 gcc 仍然指向 7.5,新版本只是多了一个命令名。
3.3 验证不是玄学:gcc -v 和带 std::filesystem 的 C++17 最小用例
切换完成后,先看版本,再看实际编译用的可执行文件路径:
gcc -v 2>&1 | grep COLLECT_GCC # COLLECT_GCC=/opt/gcc-9.3.0/bin/gccCOLLECT_GCC 这一行直接指向真正被调用的 gcc 路径。如果还是 /usr/bin/gcc,说明 PATH 或 alternatives 没生效。再跑一个带 filesystem 的最小用例:
#include <filesystem> #include <iostream> namespace fs = std::filesystem; int main() { fs::create_directories("/tmp/gcc-test"); std::cout << fs::absolute("/tmp/gcc-test") << std::endl; return 0; }编译命令注意 GCC 9 与 GCC 10 的差异:
g++ -std=c++17 -Wall -lstdc++fs test_fs.cpp -o test_fs ./test_fsGCC 9 的 std::filesystem 符号独立放在 libstdc++fs 里,必须显式加 -lstdc++fs;GCC 10 以后这个链接参数可以不加。如果看到“undefined reference to std::filesystem”,先检查是不是漏了这个库,不要急着怀疑 GCC 安装有问题。
4. GCC 离线包常见排查:五个翻车点的现象、原因、解决
4.1 运行 gcc 时报找不到 libstdc++.so.6
现象:解压后运行 gcc,甚至是运行 gcc -v,都报错:
error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory原因:GCC 9 的二进制自身也依赖新版 libstdc++,而系统默认库路径里只有旧版,LD_LIBRARY_PATH 没有指向 /opt/gcc-9.3.0/lib,动态链接器找不到它。
解决:确认解压目录后设置环境变量,或者写入系统级配置:
echo "/opt/gcc-9.3.0/lib" | sudo tee /etc/ld.so.conf.d/gcc-9.3.conf sudo ldconfig两种方案选一种即可。建议优先用 /etc/profile.d 下的环境变量方案,因为你只是给这台机器上的构建用户用;ldconfig 是全系统生效,可能影响其他依赖旧 libstdc++ 的软件。
4.2 gcc --version 显示升级了,但还是旧版本
现象:用 apt 或离线包安装了 GCC 9,gcc --version还是 GCC 7.5,版本号丝毫没变。
原因:最常见的两种情况。第一种是 PATH 里新版本目录没有排在前面,shell 优先找到了 /usr/bin/gcc;第二种是 update-alternatives 安装过但没有执行 --config,系统默认链接没改。
解决:先执行which gcc,如果是 /usr/bin/gcc,说明 PATH 问题;如果是 /etc/alternatives/gcc,说明 alternatives 问题。按 3.2 的方法调整,然后重新登录一遍 shell,不要只 source .bashrc,因为 .bashrc 里的顺序可能被系统文件覆盖。
4.3 编译报找不到 crt1.o
现象:自定义前缀安装 GCC 后,编译任何程序都报:
/usr/bin/ld: cannot find crt1.o: No such file or directory原因:crt1.o 是系统 glibc 的启动文件,通常位于 /usr/lib/x86_64-linux-gnu。GCC 在 -print-search-dirs 里没有把系统库目录和自身 lib 目录串起来,尤其是设置了 --sysroot 或改了 CPATH/LIBRARY_PATH 之后,老手也容易翻车。
解决:确认当前搜索路径:
gcc -print-search-dirs输出里 libraries 字段应同时包含 =/usr/lib/gcc/x86_64-linux-gnu/7.5.0/、=/usr/lib/x86_64-linux-gnu/ 和 /opt/gcc-9.3.0/lib 相关路径。如果缺失,用 LIBRARY_PATH 手工补充系统库路径:
export LIBRARY_PATH=$GCC_HOME/lib/gcc/x86_64-linux-gnu/9.3.0:/usr/lib/x86_64-linux-gnu:$LIBRARY_PATH4.4 std::filesystem 报 undefined reference
现象:代码写的是标准 C++17 filesystem,编译时报一堆 undefined reference,链接失败。
原因:GCC 9 之前版本把 filesystem 作为独立实验库;GCC 9 虽然正式支持,但符号仍然剥离在 libstdc++fs.a 中,需要手动链接。这不是安装问题,是参数问题。
解决:编译时加 -lstdc++fs:
g++ -std=c++17 -lstdc++fs your_program.cpp -o your_program如果项目用 CMake,需要在 target_link_libraries 里显式加 stdc++fs,不能用 CMAKE_CXX_STANDARD 代替。
4.5 用新 GCC 编译老项目时报 ABI 相关警告或编译失败
现象:把 ROS Melodic 或老 C++ 库从系统 GCC 7 换成 GCC 9 后,编译出现大量 _GLIBCXX_USE_CXX11_ABI 相关的重定义错误,或者链接期崩溃。
原因:GCC 5 之后引入了新的 std::string ABI,GCC 9 默认使用新 ABI,而用旧 GCC 编译的第三方二进制库里是旧 ABI。ROS Melodic 官方版本基于 GCC 7,与 GCC 9 混用时,同一个 std::string 在两个 ABI 间传递,轻则警告,重则运行崩溃。
解决:对需要保持旧 ABI 的项目,编译时加-D_GLIBCXX_USE_CXX11_ABI=0,强制使用旧 ABI。如果项目里有大量第三方预编译库,最稳妥的决策是保留系统 GCC 7.5 编译这些库,新 GCC 9 只用于你自己维护的、全量源码构建的模块。全局升级编译器前最好先做一次 ABI 审计,这是我从一次整机替换失败里得来的血泪经验。
5. 自己制作 ubuntu18.04gcc.zip:可重定位编译、打包与移交
5.1 用源码 configure 编一个 9.3.0:关键 configure 参数
如果没有现成离线包,最可靠的做法是找一台联网的 Ubuntu 18.04 机器,从源码编译一个可重定位 GCC。下载 GCC 9.3.0 源码后,先装编译依赖:
sudo apt-get install libgmp-dev libmpfr-dev libmpc-dev flex bison然后 configure 参数是关键,我常用这一组:
mkdir -p /build/gcc-9.3.0 && cd /build/gcc-9.3.0 ../gcc-9.3.0/configure \ --prefix=/opt/gcc-9.3.0 \ --disable-multilib \ --enable-languages=c,c++,fortran \ --disable-bootstrap \ --disable-werror make -j$(nproc) sudo make install参数说明:
- --prefix 决定了安装目录,也决定了解压后的相对路径基础,不要写成 /usr,否则后续无法整体迁移;
- --disable-multilib 只保留 64 位库,体积小很多,老项目需要 32 位支持时不要加这个参数;
- --enable-languages 按需选择,不需要 fortran 就去掉;
- --disable-bootstrap 跳过用新 GCC 编译自己的三遍循环,节省一半编译时间;追求极限性能的发布版 GCC 建议保留 bootstrap;
- --disable-werror 避免因警告被当成错误中断编译。
整个编译在 8 核机器上大约需要 40 到 60 分钟,磁盘占用 5GB 左右,make install 之后的目录约 1.5GB,压缩成 zip 后还能再小一些。
5.2 让目录脱离 /opt 也能动:rpath 和符号链接
自编译的 GCC 二进制默认带绝对路径依赖,直接打包到其他机器解压到不同目录,可能因为 RPATH 指向 /opt/gcc-9.3.0 而无法运行。我一般用 patchelf 把关键二进制的路径改成相对查找:
sudo apt-get install patchelf for f in /opt/gcc-9.3.0/bin/gcc /opt/gcc-9.3.0/bin/g++ \ /opt/gcc-9.3.0/bin/gfortran \ /opt/gcc-9.3.0/libexec/gcc/x86_64-linux-gnu/9.3.0/cc1 \ /opt/gcc-9.3.0/libexec/gcc/x86_64-linux-gnu/9.3.0/cc1plus; do sudo patchelf --set-rpath '$ORIGIN/../../lib:$ORIGIN/../lib' "$f" done$ORIGIN 是动态链接器支持的变量,表示当前可执行文件所在目录。bin/gcc 的 $ORIGIN/../lib 指向 /opt/gcc-9.3.0/lib;libexec 下的 cc1 离 lib 目录更远,需要 $ORIGIN/../../lib。处理完后用readelf -d /opt/gcc-9.3.0/bin/gcc | grep RPATH检查一下,确保每条路径都包含 $ORIGIN。
符号链接方面,需要确认 zip 里保留完整的链接链。推荐直接在 Linux 上用 zip 命令打包,不要经过 Windows 中转:
cd /opt && zip -r -y ubuntu18.04gcc.zip gcc-9.3.0/-y 参数是 zip 命令里保留符号链接的关键,漏掉的话,整包解压后 libstdc++.so 全部变成普通文件,GCC 直接不可用。
5.3 移交前的自检清单
制作完成后,先模拟目标环境:把 zip 解压到一个临时目录,比如 /tmp/gcc-reloc-test,不要用 /opt 原路径,然后执行以下检查:
sha256sum -c SHA256SUMS /tmp/gcc-reloc-test/gcc-9.3.0/bin/gcc -v /tmp/gcc-reloc-test/gcc-9.3.0/bin/g++ -std=c++17 -lstdc++fs \ /tmp/test_fs.cpp -o /tmp/test_fs /tmp/test_fs如果临时目录下能完成编译和运行,说明 RPATH 生效,包可以分发。再检查一遍动态库链接:
ldd /tmp/gcc-reloc-test/gcc-9.3.0/bin/gcc ldd /tmp/gcc-reloc-test/gcc-9.3.0/libexec/gcc/x86_64-linux-gnu/9.3.0/cc1plusldd 输出里不应出现指向 /opt/gcc-9.3.0 的绝对路径,否则目标机器解压后仍会依赖原目录存在。自检通过后,把 SHA256SUMS、解压说明和版本信息一起放进 zip,当作交付基线,之后每台机器核对一次校验值,省去大量远程排查时间。
6. 换完 GCC 后的一个习惯:整理编译日志而不是靠记忆
6.1 gcc 日志输出到文件:-v -### 和 tee 的组合
换编译器后第一次完整构建,别只盯屏幕。GCC 的报错信息可能几百行,终端滚动过后就看不清上下文。我的习惯是每次都把日志落到文件:
gcc -v -### your_program.c 2> gcc-verbose.log make clean && make 2>&1 | tee build.log-v -### 会打印 GCC 内部每一步的完整命令,包括预处理、编译、汇编、链接的完整参数,包括哪条路径被优先采用,日志输出到文件后,排查“为什么用了旧库”这种问题特别有效。make 的时候用 tee 同时输出到终端和文件,不会丢掉上下文,也不用 8 倍速翻终端记录。
6.2 记录编译器版本与路径指纹
同一台机器上隔三差五切换 GCC 版本时,我会在项目 build 目录里留一个一行命令生成的指纹文件:
gcc -v 2>&1 | grep -E "COLLECT_GCC|gcc version" > compiler.info下次项目出现诡异编译错误,先看 compiler.info,再对照当前 gcc -v 输出,立刻知道是不是版本被人动过。这个动作成本几乎为零,但能省掉很多和“玄学”搏斗的时间。
内网机器和在线环境的一大差别是,装错编译器版本后没有快速重装的后路。我现在每接手一台 18.04 构建机,第一件事就是把 GCC 版本、路径、环境变量脚本、sha256 校验记录写进团队共享的部署文档,以前吃过“gcc 升级后为啥还是旧版本”的亏,后来发现往往只是 PATH 顺序被环境初始化脚本覆盖。希望这篇笔记能帮你少走一遍这些弯路,让 ubuntu18.04gcc.zip 真正成为随手能用的后悔药。
本文还有配套的精品资源,点击获取