简介面向 Ubuntu 18.04 环境下无法联网、需要在内网或离线机器上搭建 C/C 编译环境的开发者与运维人员这份压缩包提供了一套可直接用 dpkg 安装的 GCC 7 依赖集合用于解决缺少编译工具链、apt 源不可达以及依赖版本不匹配等常见问题。包内共 25 个文件以 24 个 deb 安装包和 1 个 sh 脚本为主deb 部分覆盖 gcc-7 及 g 相关组件、cpp 预处理器、binutils 二进制工具以及 libasan、libtsan、libgomp 等运行时与优化支持库脚本则用于批量安装并处理安装顺序压缩包整体约 20.05MB体积适中便于拷贝到 U 盘或内网分发。目前已有 3205 人学习下载。按依赖顺序执行安装即可完成离线部署省去逐条排查依赖关系的时间也便于在无外网的生产、教学或实验环境中复现统一的编译环境。1. 一个 zip 包引出的版本问题ubuntu18.04 上的 gcc7 到底该怎么落地上周有位同事拿了个ubuntu18.04gcc.zip过来说机器在内网、连不上软件源让我帮他把 gcc7 装上。他先在 Windows 上把 zip 解压了一遍传进 Ubuntu然后unzip展开在终端里执行第一个.deb屏幕上立刻出现一屏dependency problems prevent configuration他当时就以为包坏了。其实包没坏问题在于 ubuntu18.04 上装 gcc7 这件事有前置条件系统自带的 gcc-7 已经是 7.5.0平时很少需要额外安装真正容易出问题的是在没有网络的环境里用 dpkg 手动安装一套互相依赖的 deb 包而安装顺序、alternatives 优先级和-f install补依赖每一步都可能卡住。这篇记录就是我在这类离线机器上反复装过几轮后留下的操作顺序适合要固定 gcc7 版本做构建复现、或者内网机器无法访问软件源的人照着走。2. 装前把账算清gcc7 在 ubuntu18.04 上的版本来源与依赖清单动手前先把「这套东西从哪来、装到哪、依赖谁」三件事定下来比装到一半再回头查要省事得多。ubuntu18.04 这个发行版本身发布时就把 gcc-7 作为默认编译器版本停在 7.5.0/usr/bin/gcc通过 alternatives 指向gcc-7。所以拿到一个 gcc7 的压缩包时先别急着解压先想清楚你要的是「补上缺失的 gcc7」还是「把系统默认版本钉死在 7」这两件事的操作路径完全不同。2.1 三种来源的取舍源内安装、zip 里的 deb、源码编译同样叫「装 gcc7」三条路各有各的代价选错路会让后面所有步骤都变别扭。第一种是走 aptsudo apt-get install gcc-7 g-7。这是最省事的依赖由 apt 自己解装完 alternatives 也会自动注册好。限制是必须有可用的软件源内网机器、被裁剪过的镜像、或者源里只剩 main 而缺 universe 的情况都用不了。第二种就是手里这个 zip里面是一组已经编译好的 deb 包配合dpkg -i离线落地不依赖网络但依赖关系得自己扛。第三种是下源码./configure make我不太推荐在 18.04 上为了一个 7.5.0 去编译因为要先把 GMP、MPFR、MPC 三个数学库准备好编译一次十几到几十分钟机器内存小一点还会被cc1plus吃满。我一般的判断标准很朴素能联网就走 apt完全离线、或者构建流水线要求所有机器二进制一致就走 zip 里的 deb只有需要改--enable-languages、--with-arch这类编译选项时才考虑源码。zip 这条路的优势在于版本固定同一个包铺到十台机器上gcc -v的输出一模一样排查构建差异时省掉一大截猜测。2.2 解包后先看清结构用 dpkg-deb 读包再动手拿到压缩包第一步不是解压到当前目录而是先看清单。zip 里通常混着.deb、可能的install.sh、以及一份说明文件先unzip -l列一下确认没有多层目录嵌套也确认没有混进 arm64 或 i386 的包。# 不解压先看压缩包里的文件清单和体积 unzip -l ubuntu18.04gcc.zip # 正式解到一个独立目录避免和家目录里的东西混在一起 mkdir -p ~/offline/gcc7 cd ~/offline/gcc7 unzip ~/ubuntu18.04gcc.zip -d . ls -lhunzip -l的输出重点看两列文件名和长度。如果看到gcc-7_7.5.0-3ubuntu1~18.04_amd64.deb这种命名amd64就是架构标识必须和dpkg --print-architecture的输出一致否则dpkg -i会直接抛package architecture (arm64) does not match system (amd64)。长度那一列用来粗判包是否完整一个几百 KB 的gcc-7主包基本可以认定是下载中断留下的残包。解开之后对每个 deb 读一遍元信息比直接装要稳# 读单个包的版本、架构、依赖只看前 25 行足够 dpkg-deb -I gcc-7_*.deb | head -25 # 看这个包会把文件铺到哪些路径 dpkg-deb -c gcc-7_*.deb | head -20 # 一次性把目录里所有包的依赖打出来方便对照系统已装的东西 for f in *.deb; do echo $f; dpkg-deb -f $f Depends; donedpkg-deb -I输出里的Depends字段是后面排查的关键它列出的包名带版本约束比如libgcc-7-dev ( 7.5.0-3ubuntu1~18.04)这种带等号的强约束意味着版本差一个字符都会导致配置失败。dpkg-deb -c则是看文件落点重点确认/usr/bin/gcc-7、/usr/lib/gcc/x86_64-linux-gnu/7/这两处前者是编译器驱动后者是头文件和内部库缺一个都编不出东西。2.3 依赖与磁盘、编译内存的底线把包里所有Depends汇总去重再跟dpkg -l的已装列表对一遍缺什么心里就有数了。一套完整的 gcc7 相关包职责大致如下包名作用缺失时的典型报错gcc-7-base基础文件、版权与文档后续所有包依赖解析失败cpp-7C 预处理器配置阶段报依赖 cpp-7 未满足gcc-7C 编译器驱动与 cc1/usr/bin/gcc-7不存在libgcc-7-dev头文件、crt 目标文件、静态库cannot find crtbegin.og-7C 编译器驱动与 cc1plusg: command not foundlibstdc-7-devC 标准库头文件与链接用 .sobits/stdc.h: No such file磁盘方面这一套解开安装后大概占几百 MB/usr/lib/gcc和/usr/include/c是大头装之前用df -h /usr看一眼留出 1 GB 比较安心。内存方面gcc7 本身不挑但用它编大文件时cc1plus是内存消耗的主要部分2 GB 内存的机器编单个超过五千行的 C 文件容易触发 OOM这种情况要么加 swap要么拆文件和编译器版本无关。提示如果机器上已经存在 gcc-7 包dpkg -i会走升级或重装逻辑不会报错但也不会给你任何提示。装之前先dpkg -l | grep -E gcc-7|g\\-7看一眼现状避免以为「装上了」其实是「本来就有」。3. 用 dpkg 把它装上从解压到配置完成的四步前面把包看清楚了这一章是实际落地的部分。dpkg 和 apt 最大的区别在于apt 会在装之前把依赖顺序算好dpkg 则是你说装哪个就装哪个缺依赖就停在半配置状态。所以整个流程要按「先底层后上层、先 base 后具体」的顺序推中间任何一步报错都先解决再往下走不要连着往下灌命令。3.1 解压与落盘路径别放在 /tmp路径这件事看着无关紧要实际影响挺大。放在/tmp里有些系统配置了tmpfs或者定时清理装到一半重启源文件没了后面--configure -a想补都没法补。放在家目录下的固定目录最稳妥装完之后这个目录还能留作回滚用的备份。# 固定落盘位置权限给普通用户即可dpkg 安装时才需要 root mkdir -p ~/offline/gcc7-deb unzip -o ubuntu18.04gcc.zip -d ~/offline/gcc7-deb cd ~/offline/gcc7-deb # 确认架构一致输出应为 amd64 dpkg --print-architecture # 确认系统当前已装的 gcc 版本作为切换前后的对照 gcc --version | head -1unzip -o的-o是覆盖已有文件不询问重复解包时省掉一堆交互。解压目录我用gcc7-deb和系统目录区分开后面执行dpkg -i ~/offline/gcc7-deb/*.deb时通配符不会误伤别的文件。如果你习惯把包放在/opt记得chmod 755保证 root 能读到有些机器家目录是 700 的sudo 下反而读不到。3.2 dpkg -i 的安装顺序与批量装法真正的安装命令只有一条但顺序有讲究。dpkg 在批量安装时其实会尝试自己排序多数情况下dpkg -i *.deb一次就能过出问题的往往是 base 包和 dev 包之间的关系没理顺。我的习惯是分两批先装不会依赖别人的再装其余的。# 第一批base 包和预处理器这两个几乎不依赖同批其他包 sudo dpkg -i gcc-7-base_*.deb cpp-7_*.deb # 第二批编译器本体、C 驱动、运行时开发包一起上 sudo dpkg -i gcc-7_*.deb g-7_*.deb libgcc-7-dev_*.deb libstdc-7-dev_*.deb # 如果包的命名不统一直接用通配符一把梭dpkg 会自行排序 sudo dpkg -i ~/offline/gcc7-deb/*.deb前两条命令的意图是降低依赖问题的排查难度gcc-7-base是整个依赖链的根cpp-7只依赖 base 和 libc6这两个装上之后剩下的包报依赖错误的概率会小很多。第三条是兜底做法当你确认目录里所有 deb 都是同一批构建产物时一条通配符最省事dpkg 内部会按依赖关系重排装完再把没配置成功的挑出来单独处理。安装过程里最该盯的是输出里有没有Errors were encountered while processing:这一段。没有这一行说明所有包都进到了installed状态可以跳到 3.4 收尾有这一行后面的包名就是没能配置成功的先不要继续装别的转去 3.3 处理依赖。3.3 依赖没齐的两条补救路径apt -f install 与手动补包报dependency problems prevent configuration的时候先别慌用dpkg -l看状态列。状态是iU已解包未配置或者iF配置失败都说明文件已经铺到磁盘上了只差配置这一步不需要重装。# 看哪些包卡在未配置状态ii 是正常iU / iF 是有问题 dpkg -l | grep -E gcc-7|g\\-7|cpp-7 | grep -v ^ii # 路径一有可用源时让 apt 把缺的依赖补齐并重新配置 sudo apt-get -f install -y # 路径二完全离线时从包里找缺的那个 deb 单独补例如缺 libisl sudo dpkg -i libisl*.deb sudo dpkg --configure gcc-7apt-get -f install的-f是 fix broken 的意思它会读 dpkg 的状态库把所有未满足的依赖列出来能下的下、能配的配。这条路的前提是有源内网机器通常会卡在这里报一堆Failed to fetch。这时候走路径二把 2.2 步拿到的Depends清单对着dpkg -l逐条核缺哪个就单独dpkg -i那个包再dpkg --configure 包名触发配置脚本。如果报错信息里出现的是libc6-dev版本过低这类系统级依赖说明机器的系统补丁落后于这批 deb 的构建基线这种包我不建议硬装容易出现装了 gcc7 却把 libc6 弄坏、连ls都起不来的情况。稳妥做法是先把系统apt-get update apt-get upgrade到同一基线或者换一份和当前系统补丁级别匹配的包。3.4 收尾dpkg --configure -a 与 apt-mark hold装完之后做两件收尾的事把可能残留的未配置包全部配置掉然后把版本锁住防止某次apt upgrade把 gcc7 悄悄换掉。# 把所有处于未配置状态的包重新跑一遍配置脚本 sudo dpkg --configure -a # 确认安装结果三个包都应该在 ii 状态 dpkg -l | grep -E ^ii\s(gcc-7|g\\-7|libstdc\\-7-dev) # 锁住版本避免后续系统升级动它 sudo apt-mark hold gcc-7 g-7 cpp-7 libgcc-7-dev libstdc-7-devdpkg --configure -a的-a表示全部它会重新执行每个未配置包的 postinst 脚本。这一步经常被跳过结果是dpkg -l里看着有 gcc-7但/usr/bin/gcc-7的软链没建、man 文档没生成后面 alternatives 注册时会找不到目标文件。apt-mark hold是我在离线机器上的固定习惯。锁住之后再apt-mark showhold就能看到列表哪天要升级系统先apt-mark unhold解开。不锁的后果我遇到过一次例行升级把 libstdc-7-dev 换成更新版本头文件路径没变但内部 ABI 符号变了之前编好的产物跑起来报符号找不到查了半天才定位到是升级导致的。4. 版本切换与共存update-alternatives 的参数怎么给包装上不等于系统默认用的就是它。ubuntu 的编译器是通过 alternatives 机制做的软链/usr/bin/gcc本身是个指向/etc/alternatives/gcc的链接再指到具体版本。这一章说清楚优先级怎么给、什么时候该用手动软链、以及怎么在不改系统默认的前提下临时用 gcc7。4.1 用 alternatives 建组优先级与 slave 链接先说结论gcc、g、gcov应该作为一组来管理用--slave把它们绑在一起这样切一次全切不会出现gcc是 7 而g是 9 的尴尬局面。# 注册 gcc7 到 alternativespriority 按主版本号乘 10 给 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 \ --slave /usr/bin/g g /usr/bin/g-7 \ --slave /usr/bin/gcov gcov /usr/bin/gcov-7 \ --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-7 \ --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-7 # 交互式选择当前使用的版本 sudo update-alternatives --config gcc # 非交互场景下直接指定适合写进部署脚本 sudo update-alternatives --set gcc /usr/bin/gcc-7--install的四个位置参数分别是链接路径、组名、实际文件路径、优先级。优先级数字大的在自动模式下胜出所以我把 gcc-7 给 70、gcc-8 给 80、gcc-9 给 90这样新装的版本会自动接管需要钉死 7 的时候用--set手动指定逻辑清晰不用记。--slave后面跟两组参数前者是链接路径后者是实际文件slave 的特点是跟着 master 走master 一切它全切这是保证gcc和g版本一致的关键。--config gcc会列出所有候选版本和当前选择输入编号回车即可。这一步在脚本里不能用因为它需要交互输入自动化部署一律用--set指定完整路径写错路径会直接报错而不是静默失败比编号可靠。4.2 手动软链的做法和它的代价有些教程会教你直接ln -sf /usr/bin/gcc-7 /usr/bin/gcc看起来很直接但我一般不用。原因有三个一是它会覆盖 alternatives 建的链接之后update-alternatives --config gcc显示的当前选择就和实际不符排查时容易被误导二是系统升级重装 gcc 包时postinst 会把链接重新指回去你的修改静默失效而你不知道三是没有 slave 机制g还得单独再链一次漏了就是 C 编得过 C 编不过。只有在一种情况下我会考虑手动软链容器镜像里 alternatives 机制被裁剪掉了update-alternatives命令不存在或者/etc/alternatives目录是空的。这种场景下用软链是没办法的办法但要在 Dockerfile 里写清楚并且同时链gcc、g、cc、c四个入口cc和c这两个名字很多 Makefile 会直接调。# 仅在 alternatives 不可用时使用四个入口一起链别只链 gcc ln -sf /usr/bin/gcc-7 /usr/bin/gcc ln -sf /usr/bin/g-7 /usr/bin/g ln -sf /usr/bin/gcc-7 /usr/bin/cc ln -sf /usr/bin/g-7 /usr/bin/c4.3 编译期指定 CC/CXX不改系统默认也能用 gcc7多数时候我们并不想动系统默认版本尤其是这台机器上还跑着别的构建任务时。这种情况下根本不用碰 alternatives直接在构建命令前加环境变量就行Makefile 和 CMake 都会优先读这两个变量。# 单次构建临时指定编译器 CC/usr/bin/gcc-7 CXX/usr/bin/g-7 make -j4 # CMake 项目在 configure 阶段就把编译器钉死避免缓存污染 cmake -S . -B build -DCMAKE_C_COMPILER/usr/bin/gcc-7 \ -DCMAKE_CXX_COMPILER/usr/bin/g-7 # 确认 CMake 真的用上了输出里应能看到 gcc-7 的路径 grep -E CMAKE_(C|CXX)_COMPILER: build/CMakeCache.txtCC和CXX必须写绝对路径。写gcc-7这种相对形式也能工作但如果 PATH 里有多个 gcc-7优先级不确定写/usr/bin/gcc-7就完全没有歧义。CMake 那一行要注意的是编译器只在第一次 configure 时生效后面改CMakeCache.txt里的值不会重新检测必须删掉build目录重来这一点我在一个跨平台项目上反复遇到过。-j4里的并行度按 CPU 核数给nproc查一下即可。用 gcc7 编大项目时并行度不要开太满每个 cc1plus 进程都要吃几百 MB 内存8 核机器给-j6比-j8稳留点余量给系统。5. 避坑与排查五条装 gcc7 时的血泪记录下面这几条都是我在离线机器上真实撞过的现象、原因、解决按顺序写遇到对得上的直接照着处理。5.1 装完 gcc --version 还是老版本现象dpkg -l | grep gcc-7显示ii/usr/bin/gcc-7也存在但gcc --version输出的还是 9 或者别的版本。原因alternatives 没注册或者注册了但优先级低于现有版本自动模式下没选上。也有一种情况是/usr/bin/gcc被上一次手动软链改过指向了一个不存在的文件update-alternatives认为链接不归它管。解决先ls -l /usr/bin/gcc看它指向哪正常应指向/etc/alternatives/gcc。如果不是说明被手动改过用sudo update-alternatives --install重新注册后再--config gcc选一次。注册完update-alternatives --query gcc能看到所有候选和当前值这一步比猜快得多。5.2 dpkg -i 报依赖错误包停在 unpacked现象安装输出末尾出现Errors were encountered while processing: gcc-7dpkg -l里 gcc-7 状态是iU。原因同批 deb 里的某个依赖包没装或者装了但版本号不满足强约束。gcc 系列包的 Depends 里大量使用精确版本号包和包之间必须严格同批。解决dpkg-deb -f gcc-7_*.deb Depends把依赖列出来逐条对着dpkg -l核版本号。差版本的那条从包里找对应 deb 装上再sudo dpkg --configure gcc-7。有源的情况下sudo apt-get -f install -y一步到位离线机器只能手动核。5.3 编译 C 报找不到 stdc 或 bits/stdc.h现象C 代码编得过C 报fatal error: bits/stdc.h: No such file or directory或者链接阶段报cannot find -lstdc。原因只装了gcc-7没装g-7和libstdc-7-dev。这两个是独立包gcc-7的依赖里不含它们因为 C 编译器不需要 C 的东西。解决补装g-7和libstdc-7-dev前者提供cc1plus和驱动后者提供/usr/include/c/7/下的头文件和链接用的.so符号链接。装完ls /usr/include/c/7/bits/stdc.h确认文件在再编一次。5.4 dpkg 被中断之后所有安装都失败现象执行任何apt或dpkg命令都报dpkg was interrupted, you must manually run dpkg --configure -a或者报锁文件被占用。原因上一次安装中途被打断可能是 CtrlC、可能是 SSH 断连也可能是机器重启。dpkg 的状态库里有未完成的记录它会拦住所有后续操作防止状态错乱。解决按提示执行sudo dpkg --configure -a。如果报的是锁文件/var/lib/dpkg/lock-frontend被占用先ps aux | grep -E apt|dpkg确认没有活着的进程再删锁文件。锁文件被占也可能是有 unattended-upgrades 在后台跑等它跑完即可不要硬删。5.5 程序运行时报 GLIBCXX_3.4.22 not found现象用 gcc7 编出来的程序在另一台机器上跑启动时报version GLIBCXX_3.4.22 not found。原因程序链接的是编译机上的libstdc.so.6运行机器的系统库里没有对应版本的 C ABI 符号。这个问题和 gcc 装得对不对没关系是运行时库版本不一致。解决strings /usr/lib/x86_64-linux-gnu/libstdc.so.6 | grep GLIBCXX看运行机器支持到哪个版本号。最简单的做法是静态链接-static-libstdc -static-libgcc代价是体积变大。另一种是随程序分发一份匹配的.so并用LD_LIBRARY_PATH指定但要注意加载顺序别把系统库顶掉。6. 装完怎么验证三个层次的检查别只看 gcc --versiongcc --version只证明驱动能跑证明不了头文件、运行时库、链接器都齐全。我一般分三层验第一层是驱动和路径第二层是 C 和 C 的实际编译链接第三层是编译产物能不能在目标机器上加载。三层都过才算这套 gcc7 真的落地了。# 第一层驱动、版本号、内部搜索路径 gcc -dumpversion # 应输出 7 gcc -v 21 | tail -5 # 看 Configured with 那行确认构建参数 gcc -print-search-dirs | head -3 # 确认库搜索路径在 /usr/lib/gcc/.../7 下 # 第二层真实编译一个 C 和一个 C 程序 cat /tmp/t.c EOF #include stdio.h int main(void) { printf(c ok %d\n, __GNUC__); return 0; } EOF gcc -Wall -O2 /tmp/t.c -o /tmp/t /tmp/t cat /tmp/t.cpp EOF #include iostream #include vector #include algorithm int main() { std::vectorint v{3, 1, 2}; std::sort(v.begin(), v.end()); // 模板实例化验证头文件完整 for (int x : v) std::cout x ; std::cout \n; return 0; } EOF g -stdc17 -Wall -O2 /tmp/t.cpp -o /tmp/tpp /tmp/tpp第一层的-dumpversion输出的是主版本号gcc7 上就是7-v的末尾几行会打印Configured with:从里面能看出--with-arch、--enable-languages这些构建选项离线包和源码编译出来的差别通常就藏在这一行。第二层的 C 样例特意用了vector和sort是为了逼编译器去实例化模板、去读/usr/include/c/7/bits/下的头文件只写个hello world是验不出头文件包缺失的。第三层是很多人漏掉的尤其是在有部署环节的场景里检查项命令期望结果运行时库版本strings /usr/lib/x86_64-linux-gnu/libstdc.so.6 | grep GLIBCXX_3.4.22有输出说明 ABI 符号在二进制依赖ldd /tmp/tpp没有not found行对象文件架构file /tmp/tpp显示x86-64且dynamically linkedC17 文件系统g -stdc17 fs.cpp -lstdcfsgcc7 必须显式加-lstdcfs不加会报undefined reference表格最后一行是 gcc7 的一个时代特征std::filesystem在 GCC 7 里还放在stdcfs这个独立库里GCC 9 之后才并进主库。这个差异会导致同一份代码在 7 上编不过、在 9 上没事反过来也一样写构建脚本时用版本判断把-lstdcfs加上比事后查链接错误省事。全部验完我习惯把这次的版本信息记进机器上的一个备注文件gcc -v 21 | tail -3 ~/offline/env-notes.txt连同包的来源和解压目录一起写。半年后回来查「这台机器为什么编出来的东西和别的不一样」翻这个文件三十秒就能定位。从那以后我每次在一台离线机器上装完编译器都会强制把这三层验证走一遍尤其是第三层的ldd和GLIBCXX检查——这两步花不了两分钟却能挡掉上线之后才发现符号找不到的那类问题。希望帮到你。本文还有配套的精品资源点击获取