Ubuntu 18.04离线安装gcc-7与默认切换避坑指南
简介:面向 Ubuntu 18.04 环境下需要离线部署 C/C++ 编译工具链的开发与运维人员,这份压缩包提供了一套可直接落地的 GCC 7 安装来源,重点解决无外网或受限网络机器上依赖缺失、版本不匹配导致的编译环境搭建困难。包内共 25 个文件,以 24 个 deb 安装包和 1 个 sh 脚本为主,deb 覆盖编译器本体、cpp 预处理组件、binutils 二进制工具及 libgcc、libasan、libgomp 等运行与开发依赖,脚本可用于辅助执行安装流程;压缩包约 20.05MB,体积适中,便于拷贝到目标主机。已有 3205 人学习下载,说明其在同类离线部署场景中具有一定参考价值。读者可据此在隔离环境中补齐 GCC 7 及相关依赖,减少逐个查找软件包的重复劳动,并为后续驱动编译、项目构建和依赖排错提供可复用的包集合与安装思路。
1. 别急着下载:Ubuntu 18.04 的 gcc7 多数是被换掉了,并非没装
一台跑着 Ubuntu 18.04 的内网机器,编译某相机 SDK 的驱动时冒出cc1: error: unrecognized command line option '-Wno-format-truncation',敲gcc --version一看是 5.4.0。前一个同事为了跑别的老工程装了 gcc-5,把/usr/bin/gcc直接指了过去,谁也没记着改回来。这就是 gcc7 在 ubuntu18.04 上最典型的一幕:系统本来自带 7.5.0,可你手上这台机器的 gcc 早就不是你以为了。于是大家开始搜 gcc7 ubuntu18.04 dpkg 的装法,方向没错,但动手前得先分清自己是“没有 gcc7”还是“gcc7 被顶掉了”。这篇记录的是后者,也是离线内网机最难受的那种:不能联网 apt,只能拿一组 deb 包,靠 dpkg 一层层铺回去。适合手上有包、机器不通外网、又必须把 gcc-7 固定成默认编译器的人。
2. 装前摸底:ubuntu18.04 上的 gcc-7 版本、依赖和架构怎么确认
装 gcc-7 这件事,翻车最多的不是命令写错,是没确认现场。同一句sudo dpkg -i gcc-7_*.deb在两种机器上结果完全相反:一种是干净的最小化系统,缺一堆运行库,dpkg 直接停在依赖检查;另一种是被人来回折腾过的开发机,包全在,只是链接指错了地方,你装第二遍等于白装。所以第 2 章不写安装,只写怎么在动手前把这三件事问清楚:当前真实的编译器是谁、这套系统里哪些 gcc 包已经在、目标机器的架构和运行库能不能接住 gcc-7。
2.1 三条命令摸清现状:你缺的是 gcc-7 还是别的
先别管压缩包,先把“现在到底是谁在干活”钉死。很多人习惯which gcc看一眼就完事,这不够,因为which只看 PATH 里第一个命中的,看不到被 alternatives 藏起来的候选,也看不到/usr/local/bin里那份从源码编译的野版本。
# 一次性看清 PATH 里所有同名命令的位置和顺序 type -a gcc g++ cc c++ # 解析软链接的最终落点,这一步才看得到真实二进制 readlink -f "$(command -v gcc)" # 输出版本和目标三元组,确认架构别装错 gcc -dumpversion gcc -dumpmachinetype -a会按 PATH 顺序把所有命中列出来,如果/usr/local/bin/gcc排在最前面,那你后面怎么折腾 alternatives 都没用,它优先级天然高于/usr/bin。readlink -f用来剥掉/usr/bin/gcc -> /etc/alternatives/gcc -> /usr/bin/gcc-7这层层跳转,直接告诉你最终落在哪个版本上。-dumpversion只吐主版本号,比--version好解析;-dumpmachine输出x86_64-linux-gnu这类三元组,是判断“该装 amd64 还是 arm64 包”的第一手依据。
接着看包的状态,这一步比看gcc --version更有信息量:
# 只看 gcc 相关的包,前缀两个字母是 dpkg 的状态位 dpkg -l | grep -E '^(ii|rc|iU|iF)[[:space:]]+(gcc|g\+\+|cpp|libstdc|libgcc)'状态位里ii表示已装好,rc表示卸载了但配置文件还留着,iU是解包了没配置,iF是配置失败。后三种都是“装过但坏了”的痕迹,出现iU或iF时,你直接重装同一个包大概率还是失败,得先走一遍sudo dpkg --configure -a把半成品收尾。还有一个信号值得留意:如果gcc-5和gcc-7同时是ii,那基本可以确认这台机器之前被人手动切过版本,你真正要做的就是把链接切回来,而不是重新装一遍。
2.2 版本对照:18.04 上 gcc-5 / gcc-6 / gcc-7 / gcc-8 到底该选哪个
Ubuntu 18.04 官方源里同时躺着 5、6、7、8 四个版本,选错一个后续能烦你半个月。
| 编译器 | 包名 | 常见版本 | C++ 头文件目录 | 典型场景 |
|---|---|---|---|---|
| gcc-5 | gcc-5 / g++-5 | 5.4.0 | /usr/include/c++/5 | 16.04 遗留工程、老内核模块 |
| gcc-6 | gcc-6 / g++-6 | 6.4.0 | /usr/include/c++/6 | 极少用,多为过渡 |
| gcc-7 | gcc-7 / g++-7 | 7.5.0 | /usr/include/c++/7 | 18.04 默认,4.15 内核模块 |
| gcc-8 | gcc-8 / g++-8 | 8.4.0 | /usr/include/c++/8 | 需要更完整的 C++17 支持 |
这张表里最容易忽略的是“头文件目录”这一列。g++-7真正提供的头文件在/usr/include/c++/7,这个目录是libstdc++-7-dev包铺出来的,不是gcc-7包。所以只装gcc-7不装libstdc++-7-dev,gcc能用、g++一编译 C++ 就找不到<iostream>,这是最经典的半成品状态。
版本选择上我一般这么定:目标机器是 18.04、要编译的东西带着内核模块或者老驱动的,锁 gcc-7,跟着系统默认走,兼容性最稳;纯应用层、代码里用了std::optional、std::string_view、结构化绑定这些的,gcc-7 也够(-std=c++17在 7.5 上支持度已经不错),除非踩到某些库的实现缺陷才上 gcc-8。至于 gcc-5,除非你接手的工程明确写了“必须 5.4”,否则别往回退,退回去以后GLIBCXX的符号版本会一路往下掉,连带影响运行期。
还有一层是运行库。18.04 的libstdc++6实际是 8.x 系列,对外导出的GLIBCXX_3.4.25是上限,所以用 gcc-7 编出来的程序在这台机器上跑没有任何问题。真正出问题的是反过来:gcc-8 编的程序拿到只装了旧libstdc++6的机器上跑,报GLIBCXX_3.4.25 not found。你把编译版本降回 7,这个坑自然就绕开了,这也是很多团队宁愿锁 7 不愿意升 8 的真实原因。
2.3 环境清单:架构、libc、多架构和磁盘
动手之前把这四项抄在纸上,后面每条报错都能对得上号:
# 主架构和开启的额外架构,装错架构的包 dpkg 会直接拒 dpkg --print-architecture dpkg --print-foreign-architectures # 运行库版本,用来判断 GLIBCXX 上限 ldd --version | head -1 # 空间,gcc-7 全套解包后对 /usr 的占用不算小 df -h /usr /opt--print-foreign-architectures如果输出了i386,说明这台机器开过多架构支持,你在解包时就要留意别把 i386 的 deb 混进 amd64 的安装序列里,dpkg -i遇到架构不符会直接报package architecture (i386) does not match system (amd64),虽然加--force-architecture能硬上,但我不建议在编译器这种基础组件上硬来。
磁盘这块给个经验值:gcc-7、g++-7、cpp-7、libgcc-7-dev、libstdc++-7-dev这一套解包后落在/usr下大致是百来兆到两百兆的量级,/usr剩不到 500M 的机器先清一清再装,装到一半No space left on device会让包卡在iU状态,比一开始就失败更麻烦。
最后确认一件事:目标机器上有没有/usr/local/bin/gcc这种从源码装的版本。有的话,它优先级高于/usr/bin,你把 alternatives 配得再漂亮,gcc --version还是那份源码版。这种机器上要做的不是装,是先决定这份源码版留不留,留就把路径写死到项目配置里,不留就make uninstall掉,别留个黑匣子在那儿谁都说不清。
3. dpkg 离线铺包:gcc-7 与 g++-7 的安装顺序和命令
到了真正动手的环节。离线装 gcc-7 和联网apt install最大的差别是:apt 会替你算依赖、按顺序解包配置,dpkg 不会,它老老实实按你给的顺序来,缺一个就在配置阶段停住,留下一堆iU状态的包。所以这一章的重点不是“敲哪条命令”,而是把包按依赖链排好队,再决定用哪种参数形态交给 dpkg。下面这套流程我在几台不通外网的机器上重复过,顺序没变过。
3.1 解包清点:先看清手里这组 deb 是谁
拿到压缩包先别急着dpkg -i *.deb,先在临时目录里解开、列清单。原因很简单:*.deb的通配顺序是按文件名字典序来的,g++-7排在gcc-7前面,cpp-7排在gcc-7-base后面,这个顺序跟真实依赖链基本对不上,虽然 dpkg 会先解包后配置、顺序影响没那么绝对,但一旦中间某个包配置失败,你排查时面对的就是一团乱麻。
# 解到独立目录,别在下载目录里直接操作 mkdir -p /opt/gcc7-deb && cd /opt/gcc7-deb unzip ~/ubuntu18.04gcc.zip -d ./pkg # 只列包名和版本,确认这套包是不是同一个版本线 for f in pkg/*.deb; do dpkg-deb -f "$f" Package Version Architecture donedpkg-deb -f直接读控制信息,不安装、不解包,是最安全的清点手段。三个字段里,Package告诉你这是哪个组件,Version要确认它们属于同一个版本线(比如都是7.5.0-3ubuntu1~18.04),版本混装是离线安装最常见的暗坑——gcc-7是 7.5.0 而cpp-7是 7.3.0,dpkg 会报依赖版本不满足,且报错信息不会直说是版本对不上。
顺手再看一眼每个包依赖谁:
# 打印 Depends 字段,用来拼安装顺序 dpkg-deb -f pkg/gcc-7_*.deb Depends dpkg-deb -f pkg/libgcc-7-dev_*.deb Depends这一步的意义在于:如果目标机器上缺libisl19、libmpfr6这类基础库,你能在装之前就发现,而不是装到一半被dependency problems打断。18.04 的完整系统里这些库基本都在(因为默认就是 gcc-7),最小化安装或容器镜像里则经常缺,这是两套完全不同的处置方式。
3.2 依赖链条:gcc-7-base → cpp-7 → libgcc-7-dev → gcc-7 → g++-7
gcc-7 这套包的依赖关系是条清晰的链,记不住就按下面这个顺序摆:
| 顺序 | 包名 | 作用 | 依赖谁 |
|---|---|---|---|
| 1 | gcc-7-base | 基础数据、license、arch 信息 | 无 |
| 2 | cpp-7 | C 预处理器,提供/usr/lib/gcc/x86_64-linux-gnu/7/cc1 | gcc-7-base |
| 3 | libgcc-7-dev | 运行时支持库和开发文件(crtbegin、libgcc.a 等) | gcc-7-base、libgomp1、libitm1、libatomic1、libasan4、libubsan0、liblsan0、libtsan0 等 |
| 4 | gcc-7 | C 编译器驱动 | cpp-7、gcc-7-base、libgcc-7-dev、binutils |
| 5 | libstdc++-7-dev | C++ 标准库头文件与静态库 | gcc-7-base、libgcc-7-dev、libstdc++6 |
| 6 | g++-7 | C++ 编译器驱动 | gcc-7、libstdc++-7-dev |
顺序背后的道理只有一句话:驱动(gcc-7 / g++-7)依赖前端(cpp-7)和后端支持(libgcc-7-dev / libstdc++-7-dev),所以支持类的包必须先到位。你把这个顺序倒过来装,dpkg 会在配置gcc-7时找不到cc1,报出来的错是dependency problems prevent configuration,看着像缺包,实际是顺序问题。
按这个顺序落命令,一次传多个包、按依赖先后排列:
cd /opt/gcc7-deb/pkg # 分两批:先铺底座,再上驱动 sudo dpkg -i gcc-7-base_*.deb cpp-7_*.deb libgcc-7-dev_*.deb sudo dpkg -i gcc-7_*.deb libstdc++-7-dev_*.deb g++-7_*.deb分两批而不是一口气全传,是为了让第一批的失败在第二批之前暴露。第一批如果因为缺libisl19之类的基础库失败,你补库就行,不会牵连到后面四个包;一口气全传的话,失败信息会混在一起,逐个dpkg -l | grep -E 'iU|iF'去捞很费时间。
第一批成功后别急着往下走,先验一下预处理器能不能跑:
# 直接调 cpp-7,能打印版本说明前端装好了 /usr/bin/cpp-7 --version # 看 cc1 是否落在正确位置,这是 gcc-7 能不能干活的关键 ls -l /usr/lib/gcc/x86_64-linux-gnu/7/cc1cc1是真正的 C 编译器后端,gcc只是个驱动,负责解析参数、调cc1、再调as和ld。cc1缺失时gcc --version照样能输出版本号,但一编译就报gcc: error: /usr/lib/gcc/x86_64-linux-gnu/7/cc1: No such file or directory,这个报错在只装了gcc-7元包、没装cpp-7的机器上出现频率极高。
3.3 dpkg 参数取舍:一次装完、分开装,还是 --force-depends
三种形态各有适用场景,选错会把小问题放大。
第一种是上面用的“分批dpkg -i指定包名”,推荐度和可控性最高,失败点清晰,也方便在中间插入检查。第二种是“dpkg -i *.deb一把梭”,通配顺序不可控,只在包数量多、你已确认它们之间没有版本冲突时用,比如整套包都是同一个 build 出来的。第三种是--force-depends,很多人一看到dependency problems就条件反射加上它:
# 应急可以,别当常规手段 sudo dpkg -i --force-depends gcc-7_*.deb--force-depends的作用是“依赖不满足也强行配置”,包会进入iF状态但文件已经铺到磁盘上,gcc --version甚至能正常输出。问题是它跳过了依赖检查,不保证libgcc-7-dev真的在,你会在几周后编译某个需要crtbegin.o的工程时突然撞墙,而且那时候已经想不起来当初装了什么。真要用它救急,必须紧跟一步补依赖,再复查状态:
# 有本地源或联网时,让它把缺的依赖补齐 sudo apt --fix-broken install # 离线机器没有源,只能手工把缺的 deb 找齐再 dpkg -i dpkg -l | grep -E 'iU|iF'apt --fix-broken install在离线机器上会失败并提示找不到包,这时候看它提示的包名,去压缩包里翻对应 deb 手工补,是唯一的路。补完再跑一遍dpkg --configure -a,让处于iU状态的包走完配置流程。
3.4 装完立刻做的两件事:configure -a 和包状态复查
装完最后一批 deb,别急着写代码验证,先做这两步收尾,能挡掉后面一大半的玄学问题。
# 把所有解包了没配置的包走完配置流程,幂等,多跑无害 sudo dpkg --configure -a # 复查:确认六个包都是 ii,且版本一致 dpkg -l gcc-7 g++-7 cpp-7 gcc-7-base libgcc-7-dev libstdc++-7-dev | grep -E '^(ii|iU|iF)'dpkg --configure -a是幂等的,已经配置好的包会被跳过,所以它可以无脑跑。有些人装完deb直接gcc --version能出版本号就以为成了,实际上包可能停在iU(文件铺了、配置脚本没跑),配置脚本负责的是建立/usr/bin/gcc-7之类的符号链接、更新alternatives、写dpkg的 trigger。停在iU的机器上/usr/bin/gcc-7可能根本不存在,你后面配 alternatives 会直接报alternative path /usr/bin/gcc-7 doesn't exist。
版本一致性这步也别省。六个包必须是同一版本线,混装时dpkg -l的输出会整整齐齐全是ii,但编译时会出各种诡异错误,比如头文件和库 ABI 对不上导致的链接失败。dpkg -l那一行里第三列就是版本号,扫一眼是否一致,比事后查半天强。
到这一步,/usr/bin/gcc-7和/usr/bin/g++-7应该都在了。但系统默认的gcc未必指向它们——这就是下一章要处理的事。
4. 让 gcc-7 成为默认:update-alternatives 的优先级与项目级锁定
装完包和“用上 gcc-7”之间还隔着一层符号链接。Ubuntu 的编译器不是直接调/usr/bin/gcc-7,而是走/usr/bin/gcc -> /etc/alternatives/gcc -> /usr/bin/gcc-X这条链。这条链存在的意义是让多个版本共存、随时切换;它带来的麻烦是,装完包之后链可能还指着别人。这一章讲怎么把链接配好、优先级数字怎么定、以及更重要的——什么时候不该动系统默认。
4.1 update-alternatives 的三层结构:主链接与 slave
先把结构说清楚,不然参数会一直写错。/usr/bin/gcc是主链接(master link),/usr/bin/g++、/usr/bin/gcov这些是从链接(slave)。主链接切换时,所有 slave 跟着一起切,这就是为什么不能单独给g++配一条 alternative——那样会出现gcc是 7、g++是 5 的错位,编 C 正常、编 C++ 报语法错误,排查起来极其费劲。
# 一条命令把主链接和从链接一起注册,成组切换 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 \ --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-7参数逐个拆:--install后面跟四段,依次是“链接路径 / 链接名 / 实际二进制路径 / 优先级”。链接名gcc是这套 alternative 组的标识,不是文件名,后面--config gcc用的就是它。--slave后面跟三段:链接路径 / 链接名 / 实际路径,从链接没有独立优先级,完全跟随主链接。
gcc-ar、gcc-nm、gcc-ranlib这三个看着不起眼的 slave 值得带上。它们对应-flto(链接时优化)场景,LTO 编译需要与编译器版本匹配的 ar/nm/ranlib,不带的话开了 LTO 的工程会报plugin needed to handle lto object,这个错在构建系统里藏得很深,很多人查到最后才发现是 alternatives 少配了这三个。
注册完之后看状态:
# 看当前指向、优先级、以及所有候选 update-alternatives --query gcc # 交互式选择,多版本共存时用这个 sudo update-alternatives --config gcc--query的输出里,Value:是当前生效的路径,Best:是优先级最高的候选,Priority那一列是各候选的数字。auto 模式下实际生效的是Best;你手动--config选过之后会切到 manual 模式,之后哪怕新装一个更高优先级的版本也不会自动切走。
4.2 优先级数字怎么定:别把别人的 gcc-5 顶掉
优先级是个纯数字,大的赢。系统自带的gcc-7注册时用的常见值是 60 上下,你要让目标版本稳定生效,把它设成 70 就够了,没必要设 200 那么夸张。
# 检查有没有更高优先级的候选在抢 update-alternatives --query gcc | grep -E '^(Value|Best|Priority)'为什么不建议设成极大的数字:这台机器上如果还有别人的工程依赖 gcc-5,你把 gcc-7 的优先级拉到 999,等于永久性地把默认编译器换掉了,对方下次编译会莫名其妙失败,而且他不一定会想到是 alternatives 被改了。优先级 70 的含义是“在常见候选里优先我”,真有人手动--config选了别的版本,也不会被一次apt upgrade之类的操作给悄悄切回来。
设完优先级,有一个必做的收尾:
# 清掉 shell 对命令路径的缓存 hash -r # 确认三个命令都指向同一版本,别出现错位 type -a gcc g++ gcovbash会缓存命令的绝对路径,你在另一个窗口改完 alternatives,当前窗口里的gcc可能还指向旧路径,表现就是“明明配了但版本没变”。hash -r清缓存,type -a复查,这两步花不了五秒,能省掉大量自我怀疑。
4.3 项目级锁定:不改系统默认也能指定 gcc-7
还有一种情况比改系统默认更常见:这台机器上跑着好几个工程,A 工程要 gcc-7,B 工程要 gcc-5,谁都不能动系统默认。这时候正确的做法是把版本锁在项目里,而不是全局切。
最直接的是 Makefile 层覆盖:
# 命令行覆盖 Makefile 里的 CC/CXX,不用改文件 make CC=/usr/bin/gcc-7 CXX=/usr/bin/g++-7 -j4前提是工程的 Makefile 用了CC ?= gcc或CC = gcc这种写法,如果它硬编码了CC := /usr/bin/gcc,命令行覆盖无效,得去改文件。
CMake 工程在 configure 阶段就把编译器路径记进CMakeCache.txt了,后面改环境变量没用,必须清缓存重来:
# 删掉旧缓存,用指定编译器重新 configure rm -rf build && mkdir build && cd build cmake -DCMAKE_C_COMPILER=/usr/bin/gcc-7 \ -DCMAKE_CXX_COMPILER=/usr/bin/g++-7 \ -DCMAKE_BUILD_TYPE=Release ..这里有个高频翻车点:CMakeCache.txt里已经写了编译器路径,你只改-D参数不删缓存,CMake 会忽略新值并提示CMAKE_C_COMPILER changed, but the cache is not cleared,很多人不看这行提示,编完发现还是旧版本在干活。rm -rf build是最省事的做法。
纯脚本或 configure 类的工程,用环境变量:
# 只对当前 shell 生效,不污染系统 export CC=/usr/bin/gcc-7 export CXX=/usr/bin/g++-7 ./configure --prefix=/usr/localexport的作用域是当前 shell 和它派生的子进程,关掉窗口就没了,这正好符合“临时用一下”的需求。要注意的是有些 configure 脚本会用CC去探测编译器特性,如果这个变量指向一个不存在的路径,脚本不报错,而是默默回退到cc,最后编出来的东西版本不对还查不出原因。所以export之后紧跟一句$CC --version确认一下,是个好习惯。
5. 避坑排查:五个「dpkg 装完了但编译不过」的现场
离线装 gcc-7 的报错有个共同特征:安装阶段的输出看着挺正常,问题全在编译或者运行的时候才炸出来。下面五条是按我实际遇到的频率排的,每条都按“现象 → 原因 → 解决”走一遍,对着症状找就行。
5.1 依赖未满足:dependency problems prevent configuration
现象:dpkg -i输出里出现dpkg: dependency problems prevent configuration of gcc-7,后面跟着一串gcc-7 depends on cpp-7 (>= 7.5.0-3~); however: Package cpp-7 is not installed.,命令返回非零,dpkg -l里gcc-7停在iU。
原因:包没按依赖顺序给,或者压缩包里压根没带齐依赖。最常见的是只解出gcc-7和g++-7两个包就开始装,cpp-7、libgcc-7-dev落在压缩包里没被一起处理。
解决:先看 dpkg 到底在抱怨谁,别猜:
# 让它把所有未满足的依赖列全 sudo dpkg -i gcc-7_*.deb 2>&1 | grep -E 'depends on|however' # 按报出来的包名回压缩包里找,补齐后重新配置 sudo dpkg -i cpp-7_*.deb libgcc-7-dev_*.deb sudo dpkg --configure -a补包之后--configure -a一定要跑,因为之前失败的包会一直卡在iU,不跑配置流程它不会自己好。离线机器上apt --fix-broken install会因找不到源而失败,别在它身上耗时间。
5.2 装到一半中断:dpkg was interrupted
现象:再敲任何dpkg或apt命令,第一行都是dpkg was interrupted, you must manually run 'dpkg --configure -a' to correct the problem.,后面的操作全部被拦住。
原因:上一次安装被 Ctrl+C 打断、SSH 断连、或者机器在装包过程中重启了,dpkg 的锁文件和状态没清理干净。这个不是 gcc-7 特有的问题,但只要装包中途出过岔子就一定会遇到。
解决:照它说的做,别去手工删/var/lib/dpkg/lock:
# 先让 dpkg 把上次没干完的活干完 sudo dpkg --configure -a # 如果它卡在某个包的配置脚本上,看是哪个包,单独处理 dpkg -l | grep -E 'iU|iF'删锁文件是很多人第一反应,但那只解开了互斥锁,包的状态还是半成品,下次装照样出问题。真遇到配置脚本死循环(某些第三方包会),才考虑--force-remove掉那个包再重来,而且要做之前先确认它不是你正在装的东西的依赖。
5.3 装完版本没变:hash 缓存和 /usr/local 里的旧 gcc
现象:dpkg -l | grep gcc-7显示ii,update-alternatives --query gcc的Value也对,但gcc --version还是 5.4.0。
原因:两种可能。一是当前 shell 的命令路径缓存没清;二是/usr/local/bin/gcc存在,PATH 里它排在/usr/bin前面,alternatives 管不到它。
解决:按顺序排查这两个:
# 先清缓存再说 hash -r # 再看所有同名命令的落点,重点看 /usr/local/bin 有没有货 type -a gcc ls -l /usr/local/bin/gcc* 2>/dev/nulltype -a输出里排第一的就是真正执行的那个。如果/usr/local/bin/gcc存在且指向一个源码编译的版本,你有两个选择:把它删掉(sudo make uninstall或者直接rm),或者在项目里写死/usr/bin/gcc-7。我一般倾向后者,因为源码装的版本可能还有人用,删了容易引发新问题。
5.4 cc1 找不到:只装了 gcc-7 没装 cpp-7
现象:gcc --version正常输出 7.5.0,但一编译就报gcc: error: /usr/lib/gcc/x86_64-linux-gnu/7/cc1: No such file or directory或gcc: internal compiler error: Killed。
原因:gcc-7只是驱动,真正的编译器前端在cpp-7包里。有些从别处捞的 deb 集合只包含gcc-7和g++-7两个元包,cpp-7被漏掉了。
解决:确认并补装:
# 看 cc1 是不是真不在 ls -l /usr/lib/gcc/x86_64-linux-gnu/7/ dpkg -l cpp-7 # 补装 cpp-7,然后重新验证 sudo dpkg -i cpp-7_*.deb gcc -v -E -x c /dev/null最后那条gcc -v -E -x c /dev/null是最轻量的验证手段:只跑预处理、不做编译链接、不需要任何源文件,能完整打出gcc调用cc1的搜索路径。如果cc1缺失,这条命令会立刻报错,比写个 hello world 再编译快得多。
5.5 运行期 GLIBCXX 版本不够
现象:编译一切正常,程序一运行就报./app: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version 'GLIBCXX_3.4.24' not found。
原因:编译用的 g++-7,运行期链接的是更老的libstdc++6。这在“编译机和运行机不是同一台”的场景里最常见,比如交叉编译后拷到另一台机器上跑。
解决:先看运行机上现有的libstdc++6支持到哪:
# 列出支持的最高 GLIBCXX 版本 strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX_3.4.2 | sort -V | tail -3如果最高只到GLIBCXX_3.4.21,说明运行机上的libstdc++6是 5.x 时代的,需要升级到 18.04 对应的 8.x 包。离线环境下升级基础运行库风险不小,更稳妥的做法是编译时静态链接标准库:
# 静态链接 libstdc++ 和 libgcc,摆脱运行机版本依赖 g++ -O2 -static-libstdc++ -static-libgcc -o app app.cpp-static-libstdc++把标准库静态编进可执行文件,代价是体积变大、跨模块共享标准库对象时有隐患。只在部署机版本不可控时用,别一刀切全项目都加上。
6. 验证跑通:用三段代码确认 gcc-7 真的能干活
装完不验证,等于没装。gcc --version只能证明驱动在,证明不了预处理器、后端、C++ 标准库、链接器这几环都通。我习惯用三段递进的代码收尾,从纯 C 到 C++17 再到符号检查,任何一段挂了都能直接定位到是哪一层的问题。
第一段验 C 编译器整条链路,同时确认版本号真的是 7:
/* ver.c:编出来的程序直接打印编译器主版本 */ #include <stdio.h> int main(void) { printf("GCC_MAJOR=%d\n", __GNUC__); return 0; }gcc -O2 -o ver ver.c && ./ver # 期望输出 GCC_MAJOR=7,输出别的数字说明驱动不对第二段验 C++ 标准库和 C++17 支持,这一段能同时暴露“只有 gcc 没有 g++”和“libstdc++-7-dev 没装”两种半成品状态:
// t17.cpp:用到 optional 和结构化绑定,都是 C++17 的硬指标 #include <iostream> #include <optional> #include <tuple> int main() { std::optional<int> v = 42; auto [a, b] = std::make_tuple(1, 2); std::cout << *v + a + b << "\n"; // 期望 45 }g++ -std=c++17 -O2 -o t17 t17.cpp && ./t17 # 报 "unrecognized command line option '-std=c++17'" 说明用错了编译器 # 报 "fatal error: optional: No such file" 说明 libstdc++-7-dev 没装第三段不写代码,直接查符号和搜索路径,这是最快的一眼判决:
| 检查项 | 命令 | 正常表现 |
|---|---|---|
| 驱动落点 | readlink -f $(command -v gcc) | 指向/usr/bin/gcc-7 |
| 主从一致 | type -a gcc g++ gcov | 三者版本号一致 |
| 内建搜索路径 | gcc -v -E -x c /dev/null 2>&1 | grep -A2 'search starts here' | 含/usr/include/c++/7 |
| 运行库上限 | strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX_3.4.2 | sort -V | tail -1 | 不低于GLIBCXX_3.4.24 |
gcc -v -E -x c /dev/null这条值得单独记住:-E只做预处理,-x c指定语言(因为/dev/null没有扩展名),/dev/null当空输入,所以它不产生任何实际输出,只把gcc内部的搜索路径、cc1调用、版本信息全打出来。编译出问题时先跑这条,能立刻分清是“找不到头文件”还是“根本调不到 cc1”。
有次在一台改过 PATH 的机器上,gcc --version显示 7.5.0,type -a也正常,但工程编译死活找不到<string>。跑了上面第三条才看到搜索路径里是/usr/include/c++/5——/etc/profile.d里有个早年留下的CPLUS_INCLUDE_PATH,把所有工程的 C++ 头文件路径都钉死在 5 上了。这个变量优先级高于编译器内建路径,g++ --version显示什么都不影响它。从那以后我每次装完编译器,都会先env | grep -E 'INCLUDE|LIBRARY_PATH'看一眼有没有残留的路径变量,再跑那三段验证代码,一个不落地走完才把这台机器交给别人。这套流程烦是烦了点,但比事后被人追着问“为什么我这儿编不过”省心多了。希望帮到你。
本文还有配套的精品资源,点击获取