ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ubuntu 18.04 下用 dpkg 安装 gcc 7 并配置多版本共存指南

Ubuntu 18.04 下用 dpkg 安装 gcc 7 并配置多版本共存指南

简介:面向 Ubuntu 18.04 系统用户与运维开发者的 GCC 7 离线安装包,针对无外网或受限网络环境下编译工具链缺失、在线 apt 安装失败等问题,提供可直接落地的本地部署方案。压缩包共 25 个文件,以 24 个 deb 安装包和 1 个 sh 脚本为主,整体约 20.05MB;deb 覆盖 gcc-7、cpp-7、binutils 及 libasan4、libgomp1、libmpc3 等编译运行依赖,sh 脚本用于批量安装与顺序处理,避免手动逐个 dpkg 的繁琐。已有 3207 人学习下载,说明该离线方案在同类场景中具备较高参考价值。读者可据此在目标机器上快速补齐 GCC 7 编译环境,减少依赖缺失与版本冲突带来的排错成本,适合需要稳定构建 C/C++ 项目的开发与维护人员使用。

1. 从一台编译报错的 Ubuntu 18.04 说起

上周帮同事救一台 Ubuntu 18.04 的构建机,现象很典型:make跑到一半报cc1plus: error: unrecognized command line option '-std=c++17',查了半天发现系统自带的 gcc 是 7.5.0,但项目里某个第三方库的 CMake 脚本硬性要求 gcc 7 以上且带完整 C++17 支持,而机器上之前被人手动装过一版来路不明的 gcc,update-alternatives指向混乱,gcc -v和/usr/bin/gcc根本不是同一个东西。这类问题在 Ubuntu 18.04 上太常见了——系统默认源里的 gcc 版本停在 7.x,很多新项目又刚好卡在这个版本线上,装对了能跑,装歪了就是一堆玄学链接错误。

这篇记录的就是我在这类机器上反复走过的一套流程:用gcc7 ubuntu18.04 dpkg这套组合把编译器环境装干净、配正确、验证到位。适合两类人:一是刚接手一台 Ubuntu 18.04 构建机、需要把 gcc 7 装利索的新手;二是被多版本 gcc 共存搞烦了、想搞清楚dpkg安装和update-alternatives切换逻辑的熟手。装不好会怎样?轻则编译报错,重则把系统自带的 gcc 覆盖掉,连apt都跑不起来,那就真得进恢复模式救了。

2. 装前把环境盘清楚:版本、依赖与 dpkg 底账

动手之前先别急着dpkg -i,Ubuntu 18.04 这套环境有几个前置条件必须先确认,否则装到一半发现依赖缺失,或者装完发现和系统自带 gcc 打架,返工成本比预检高得多。

2.1 确认系统版本与现有 gcc 状态

第一步永远是看清楚脚下踩的是什么。Ubuntu 18.04 的代号是 bionic,lsb_release和uname两个命令交叉验证,避免在容器或裁剪镜像里误判。

# 确认发行版和内核,bionic 对应 Ubuntu 18.04 lsb_release -a uname -r # 查当前 gcc / g++ 的真实版本和路径 gcc --version g++ --version which gcc g++ readlink -f $(which gcc) # 看系统里已经装了哪些 gcc 相关包 dpkg -l | grep -E '^ii\s+(gcc|g\+\+|build-essential)'

readlink -f这一步很关键,因为/usr/bin/gcc往往是个软链,指向/etc/alternatives/gcc,再指向真正的/usr/bin/gcc-7。如果只跑gcc --version看到 7.5.0 就以为万事大吉,很可能实际编译时用的是另一个路径下的旧版本。dpkg -l的输出里ii表示已安装且配置完成,如果看到iU(已解包未配置)或rc(已删除保留配置),说明之前有人装到一半中断过,这种残留必须先清掉再装新的。

我一般还会顺手看一眼apt源里 gcc-7 的可用版本,确认和要装的包版本对得上:

# 查源里 gcc-7 相关包的可用版本 apt-cache policy gcc-7 g++-7

如果apt-cache policy显示候选版本是 7.5.0,而手上的 zip 包里是 7.4.0 或别的构建,就要想清楚是走 apt 装还是走 dpkg 装本地包——这两条路后面会分别讲,混着来最容易出问题。

2.2 依赖清单:dpkg 装包前必须补齐的几样东西

dpkg和apt最大的区别是:dpkg只负责把包解开、跑安装脚本,不帮你解决依赖。所以用dpkg -i装 gcc 7 的 deb 包之前,得先把依赖手动补齐,否则会卡在dpkg: dependency problems prevent configuration。

常见做法是先装好构建基础链和 gcc 7 运行时依赖:

# 补齐 dpkg 安装 gcc-7 所需的基础依赖 sudo apt-get update sudo apt-get install -y build-essential libc6-dev libgcc-7-dev \ libstdc++-7-dev binutils make

build-essential是个元包,拉进来gcc、g++、make、libc6-dev一整套;libgcc-7-dev和libstdc++-7-dev是 gcc 7 编译 C++ 时链接阶段要用的开发库,缺了会在链接期报cannot find -lstdc++。binutils提供ld、as这些底层工具,make不用多说。这几样装完,dpkg -i装 gcc 7 的 deb 包时基本不会再因为依赖报错。

提示:如果机器上已经有别的 gcc 版本(比如 gcc-8 或 gcc-9),libgcc-7-dev和libstdc++-7-dev仍然要单独装,不同大版本的运行时库是分开的,不能互相替代。

2.3 版本选择:apt 源装还是 dpkg 装本地包

这是整篇里最需要想清楚的一个取舍。Ubuntu 18.04 官方源里gcc-7的版本是 7.5.0,如果你的 zip 包里提供的 deb 包正好也是 7.5.0 或兼容的 7.x,两条路都行;但如果 zip 包里是特定构建(比如带补丁的 7.4.0),就只能走dpkg。

方式适用场景优点风险
apt install gcc-7源里有对应版本,追求省事自动解决依赖,可apt统一管理版本被源锁定,无法装特定构建
dpkg -i *.deb手上有特定 deb 包,需精确控制版本版本完全可控,离线可装依赖要手动补,装错难回滚

我一般的判断标准是:只要源里版本能满足项目要求,优先apt,因为后续升级、卸载都干净;只有当项目明确要求某个特定构建、或者机器完全离线时,才走dpkg装本地包。这篇后面两条路都会写到,因为实际运维里两种场景都躲不掉。

3. 两条安装路径:apt 装 gcc-7 与 dpkg 装本地 deb

环境盘清楚之后,安装本身其实不复杂,复杂的是装完之后多版本怎么共存、默认指向怎么切。这一章把两条安装路径拆开写,每条都给到能直接抄的命令和参数说明。

3.1 走 apt 源安装 gcc-7 与 g++-7

源里能解决的情况,这是最省心的方式。Ubuntu 18.04 默认源就带 gcc-7,直接装即可:

# 从官方源安装 gcc-7 和 g++-7 sudo apt-get update sudo apt-get install -y gcc-7 g++-7 # 验证装上的版本 gcc-7 --version g++-7 --version

装完之后系统里会多出/usr/bin/gcc-7和/usr/bin/g++-7两个可执行文件,但注意:此时gcc这个命令仍然指向系统原来的版本,gcc-7只是并存。这是 Ubuntu 多版本管理的设计,不会自动改默认指向,需要手动用update-alternatives切(下一节讲)。

apt-get install的-y是自动确认,批量部署脚本里必加;手动操作时可以不加,看一眼要装哪些包再确认,避免误装一堆推荐包。如果只想装最小集合,可以加--no-install-recommends:

# 只装必需包,不拉推荐包,适合精简镜像 sudo apt-get install -y --no-install-recommends gcc-7 g++-7

3.2 用 dpkg 安装 zip 包里的本地 deb

离线机器或者需要特定构建时,走这条路。假设 zip 包解压后目录里有若干.deb文件,先看清楚再装:

# 解压 zip 包并进入目录 unzip ubuntu18.04gcc.zip -d gcc7-pkgs cd gcc7-pkgs # 列出包内 deb 文件,确认包名和架构 ls -lh *.deb dpkg-deb -I gcc-7_*.deb | head -20

dpkg-deb -I看的是包的元信息,重点看Architecture是不是amd64、Depends里列了哪些依赖。架构不对(比如拿到 i386 的包)装上去也跑不起来;Depends里的包如果没装,dpkg -i会报依赖错误。

确认无误后安装,注意一次把所有 deb 传给dpkg -i,让它自己按顺序处理:

# 一次性安装目录下所有 deb,dpkg 会按依赖顺序处理 sudo dpkg -i *.deb # 如果报依赖错误,用 apt 补依赖(apt 能读 dpkg 的未完成状态) sudo apt-get install -f -y

dpkg -i *.deb一次传多个包比一个个装靠谱,因为 dpkg 会在这一批里做依赖排序。装完如果提示dependency problems,别慌,跑apt-get install -f让它把缺的依赖补上,这个-f是--fix-broken的缩写,专门修 dpkg 留下的半配置状态。

注意:dpkg -i装本地包时,如果包版本比源里的低,dpkg 不会拦你,但后续apt upgrade可能把它又升回去。要锁版本得用apt-mark hold,这个后面避坑章节会讲。

3.3 多版本共存:update-alternatives 的配置逻辑

不管走哪条路装完,系统里都会出现多个 gcc 版本并存。Ubuntu 用update-alternatives管理这类「同一命令多个实现」的场景,理解它的优先级机制比死记命令重要。

先看当前 alternatives 状态:

# 查看 gcc 的 alternatives 配置和当前指向 update-alternatives --display gcc update-alternatives --display g++

输出里会列出所有候选路径、优先级(priority)和当前选中的那个。优先级数字大的在--auto模式下优先,但手动--config选过之后会锁定为手动模式。把 gcc-7 注册进 alternatives:

# 把 gcc-7 注册为 gcc 的一个候选,优先级设高一点 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 \ --slave /usr/bin/g++ g++ /usr/bin/g++-7 # 交互式选择默认版本 sudo update-alternatives --config gcc

--install的四个参数分别是:链接路径(/usr/bin/gcc)、alternatives 组名(gcc)、实际可执行文件路径(/usr/bin/gcc-7)、优先级(70)。--slave是把 g++ 绑到 gcc 这个组上,切 gcc 时 g++ 跟着切,避免出现 gcc 是 7 而 g++ 还是旧版本的错位——这个错位是后面编译报错的高发原因。

--config会弹出交互菜单,列出所有候选让你选编号。脚本化部署时用非交互方式:

# 非交互直接切到 gcc-7,适合自动化脚本 sudo update-alternatives --set gcc /usr/bin/gcc-7

--set直接指定路径,不走菜单,适合写进部署脚本。切完再跑一次gcc --version和g++ --version确认两者版本一致,这一步别省。

4. 避坑与排查:gcc 7 装完最常见的五个翻车现场

装 gcc 这件事,命令本身没几个,翻车基本都翻在装完之后。下面五条是我在不同机器上真实踩过的,按「现象 → 原因 → 解决」记下来,遇到对号入座就行。

4.1 现象:编译报cc1plus: error: unrecognized command line option '-std=c++17'

原因:gcc已经切到 7,但g++还指向旧版本,或者反过来。C++ 编译走的是g++,-std=c++17是 g++ 7 才完整支持的选项,旧版 g++ 不认。根因通常是update-alternatives只切了 gcc 没切 g++,或者--slave没绑对。

解决:先分别确认两个版本,再重新绑定:

# 分别确认,版本不一致就是问题所在 gcc --version | head -1 g++ --version | head -1 # 重新注册,确保 g++ 作为 slave 跟随 gcc sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 \ --slave /usr/bin/g++ g++ /usr/bin/g++-7 sudo update-alternatives --set gcc /usr/bin/gcc-7

4.2 现象:dpkg -i报dependency problems prevent configuration

原因:dpkg不解决依赖,zip 包里的 deb 依赖的libgcc-7-dev、libstdc++-7-dev等没装。这在离线机器上尤其常见,因为apt-get install -f也需要源可用。

解决:有网就跑sudo apt-get install -f -y自动补;离线机器得提前把依赖 deb 一起放进 zip 包,按依赖顺序手动装:

# 离线场景:先装依赖包,再装主包 sudo dpkg -i libgcc-7-dev_*.deb libstdc++-7-dev_*.deb sudo dpkg -i gcc-7_*.deb g++-7_*.deb

4.3 现象:装完apt命令本身报错,提示 gcc 相关包损坏

原因:用dpkg -i强装了一个和系统已有 gcc 包冲突的版本,把系统自带的gcc-7-base或libgcc1覆盖了,导致apt依赖链断裂。这是最危险的一种,因为apt坏了连修复工具都装不上。

解决:先看dpkg -l | grep -E 'gcc|libgcc'找出状态异常的包(iU、iF),然后用dpkg --configure -a尝试重配,不行就apt-get install --reinstall重装系统自带版本:

# 修复半配置状态的包 sudo dpkg --configure -a # 重装系统自带的 gcc 基础包,恢复 apt 依赖链 sudo apt-get install --reinstall -y gcc-7-base libgcc1

4.4 现象:update-alternatives --config gcc里看不到 gcc-7

原因:装是装上了,但没注册进 alternatives,或者注册时组名写错(比如写成了gcc-7而不是gcc)。组名是 alternatives 的索引键,写错就等于注册到了另一个组,--config gcc自然看不到。

解决:用--display确认组名,重新用正确的组名注册:

# 看所有 alternatives 组,确认 gcc 组下有哪些候选 update-alternatives --get-selections | grep gcc # 组名统一用 gcc,重新注册 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70

4.5 现象:apt upgrade之后 gcc 又变回旧版本

原因:dpkg装的本地包版本低于源里的候选版本,apt upgrade按「升级到源里最新」的逻辑把它覆盖了。这是 dpkg 装本地包的固有风险,不是配置错误。

解决:对需要锁定的包用apt-mark hold,阻止自动升级:

# 锁定 gcc-7 相关包,防止 apt upgrade 覆盖 sudo apt-mark hold gcc-7 g++-7 libgcc-7-dev libstdc++-7-dev # 需要升级时再解锁 sudo apt-mark unhold gcc-7 g++-7

提示:apt-mark hold只挡apt upgrade,挡不住手动dpkg -i覆盖。锁了版本之后,装别的包时留意别把 gcc 相关依赖一起带升级了。

5. 装完怎么验证:从版本号到真实编译跑通

装完不验证等于没装。我见过太多「gcc --version显示 7.5.0 就以为完事」的情况,结果一编译就露馅。这一章给一套从浅到深的验证流程,最后落到一个真实编译用例上,跑通了才算这套环境真的立住了。

5.1 三层验证:版本、路径、编译行为

第一层看版本,第二层看路径一致性,第三层看真实编译行为。三层都过,才算干净。

# 第一层:版本号 gcc --version | head -1 g++ --version | head -1 # 第二层:路径一致性,三者应指向同一个实际文件 which gcc g++ readlink -f $(which gcc) readlink -f $(which g++) # 第三层:编译行为,用 C++17 特性做探针 echo 'int main(){ auto x = 1; return x; }' > /tmp/probe.cpp g++ -std=c++17 /tmp/probe.cpp -o /tmp/probe && /tmp/probe && echo "C++17 OK"

第二层的readlink -f是关键,它把软链一路解到最终文件。如果gcc和g++解出来的最终路径不在同一个目录、或者版本号后缀不一致(比如一个是gcc-7一个是g++-6),说明 alternatives 绑定有问题,回到 3.3 节重新绑。

第三层用-std=c++17编译一个最小程序,能编过且能跑,说明 g++ 7 的 C++17 支持是完整的。这一步比看版本号可靠得多,因为有些机器上版本号对但实际链接的libstdc++是旧的,编译能过、运行时报GLIBCXX_3.4.24 not found。

5.2 用一个小项目做端到端编译验证

单文件探针过了,再拿一个带多文件、带链接的小项目跑一遍,验证头文件搜索路径和链接路径都对。我一般用下面这个最小结构:

# 建一个两文件的小项目,验证编译+链接全链路 mkdir -p /tmp/gcc7-check && cd /tmp/gcc7-check cat > util.h <<'EOF' #pragma once int add(int a, int b); EOF cat > util.cpp <<'EOF' #include "util.h" int add(int a, int b) { return a + b; } EOF cat > main.cpp <<'EOF' #include <iostream> #include "util.h" int main() { std::cout << add(2, 3) << std::endl; return 0; } EOF # 分步编译再链接,暴露头文件和库路径问题 g++ -std=c++17 -c util.cpp -o util.o g++ -std=c++17 -c main.cpp -o main.o g++ util.o main.o -o app ./app

分步编译的好处是能把「编译期头文件找不到」和「链接期符号找不到」分开定位。如果-c阶段就报util.h: No such file,是头文件搜索路径问题;如果-c过了但链接报undefined reference to add,是目标文件没参与链接或者链接顺序问题。一步到位g++ *.cpp -o app虽然省事,但出错时定位不到具体阶段。

跑出5就说明这套 gcc 7 环境从编译到链接到运行全通了。最后再确认一下libstdc++的版本,避免运行时库错配:

# 确认运行时链接的 libstdc++ 版本 ldd ./app | grep stdc++ strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX_3.4.2

ldd看的是运行时实际加载的库路径,strings看的是这个库里包含哪些 GLIBCXX 符号版本。gcc 7 对应的libstdc++.so.6应该包含到GLIBCXX_3.4.24左右,如果只到3.4.21,说明运行时库还是旧的,得检查libstdc++-7-dev是不是真装上了。

5.3 一个收尾习惯:把验证写进部署脚本

从那以后我每次装完 gcc,不管多急,都强制把 5.1 那三层验证写进部署脚本末尾,跑不过就exit 1,不让半成品环境流到下一环。血泪经验是:gcc 这种基础工具,装的时候省一步验证,后面编译报错时就得花十倍时间往回查,而且往往查不到根上,最后只能重装。把验证前置,是给自己留后悔药。

# 部署脚本末尾的验证段,跑不过直接失败 set -e gcc --version | grep -q '7\.' || { echo "gcc 版本不对"; exit 1; } g++ --version | grep -q '7\.' || { echo "g++ 版本不对"; exit 1; } [ "$(readlink -f $(which gcc))" = "$(readlink -f $(which g++))" ] \ || echo "警告:gcc 与 g++ 路径不一致,请检查 alternatives" echo 'int main(){return 0;}' | g++ -std=c++17 -x c++ - -o /tmp/v && /tmp/v \ && echo "gcc7 环境验证通过"

这段脚本里set -e让任何一步失败就中断,grep -q '7\.'做版本前缀匹配,路径一致性用字符串比较兜底,最后用管道把源码直接喂给 g++ 编译,省去临时文件。写进 CI 或部署脚本后,每次环境变更都会自动过一遍,比人肉检查靠谱。希望这套流程帮到你,少走几次重装系统的弯路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进