GCC版本与C/C++标准支持全对照:默认标准、安装升级与踩坑指南
GCC 这个东西折腾过 C/C 的人没有不熟的但真要问一句“你当前这个 GCC 版本到底支持哪些 C/C 标准”能一口气答上来的人其实不多。前几天有个刚入职的小朋友跑过来问我服务器上这套老 GCC连 C11 默认都不开新特性一个都用不了是不是得让运维升一下级我瞄了一眼版本RHEL 自带的 GCC 4.8.5确实挺让人抓狂。这不是个例很多人写代码写得好好的一换环境就编译失败最后查下来根本不是代码问题而是编译器版本和语言标准支持范围的问题。这篇文章我打算一次性把 GCC 各个版本对 C 和 C 标准的支持情况讲透包括默认编译标准、各个标准从哪个版本开始支持、哪个版本开始建议生产使用再配上各个平台下安装、升级、切换 GCC 的实操命令以及我这些年踩过的坑。写代码、搭教学环境、配 CI、做嵌入式开发只要你的编译器和 GCC 有关这份对照绝对值得存一份。1. GCC与C/C标准支持全景图1.1 GCC版本演进与发布节奏GCC 从 1987 年诞生到现在跨度非常大。早期 1.x、2.x、3.x 时代且不谈真正影响我们今天日常工作的是两个阶段4.x 时代和 5.x 之后。4.x 系列走得特别慢从 4.0 一路磨到 4.9.4零零散散支撑了无数个发行版。CentOS 7 默认的 GCC 4.8.5、Ubuntu 16.04 默认的 GCC 5.4都是这个时代的产物。很多人电脑里的 Dev-C 自带的 TDM-GCC 也停留在 4.9.2这些老版本最大的问题是默认语言标准太老导致了大量“代码没问题编译不过”的尴尬。从 2015 年 GCC 5.1 开始项目组改成了每年一个大版本的节奏5、6、7、8、9、10、11、12、13、14、15……现在主分支已经走到 15 甚至 16 的开发阶段。版本号也不再是 4.x 那样挤牙膏而是每个大版本都会引入一批新特性同时逐步提升对 C 和 C 新标准的支持程度。理解了这个节奏下面看支持表就不会懵。1.2 C语言标准支持情况对照C 语言的标准演进线比较简单C89/C90、C99、C11、C17、C23中间还有一些修正版本。GCC 对 C 语言标准的支持一直比较积极但默认标准改得很保守。C标准核心新增特性GCC 开始支持的版本建议稳定使用的版本C89/C90基本语法、函数原型等从最早的 GCC 就支持所有版本C99for 循环内声明变量、// 注释、stdint.h、复合字面量、可变长数组GCC 3.0 开始推进GCC 4.x 基本完善GCC 4.x 以上C11_Atomic 原子操作、_Generic 泛型、threads.h、对齐控制GCC 4.6 开始试验GCC 4.9 较完整GCC 5 以上C17修正 C11 中的缺陷没有大的新特性GCC 8 开始完整支持GCC 8 以上C23typeof、nullptr、#embed、增强的预处理、多维数组等GCC 14 开始支持一批特性仍在完善中暂时观望需指定 -stdc23这里说一个经常让新手崩溃的点C99 里“在 for 循环内声明变量”是非常自然的写法但如果你用的是老 GCC 4.8.5默认标准是 gnu90直接写for (int i 0; i 10; i)会报错。很多人遇到这个问题第一反应是自己代码写错了其实是编译器默认标准太老。GCC 5 之后默认标准从 gnu90 跳到了 gnu11GCC 8 之后默认变成 gnu17。所以如果你用的发行版稍微新一点默认 C 标准基本都不会太难受。真正难受的是 C 那边的默认标准。1.3 C语言标准支持情况对照C 的标准线要复杂很多C98、C11、C14、C17、C20、C23每一代都有大量新特性GCC 对这些标准的支持是分批逐步完成的。C标准核心新增特性GCC 开始支持的版本建议稳定使用的版本C98类、模板、STL、异常、RTTIGCC 3.x 已完整支持所有版本C11auto、lambda、nullptr、右值引用、std::thread、智能指针GCC 4.3 开始逐步加入GCC 4.8.1 标记完整GCC 4.8.1 以上C14泛型 lambda、返回值类型推导、std::make_uniqueGCC 4.9 基本支持GCC 5 完整GCC 5 以上C17std::filesystem、结构化绑定、if constexpr、折叠表达式、std::optional/variantGCC 5 开始部分实现GCC 8 基本完整GCC 9 完善GCC 9 以上C20concepts、ranges、协程、 三路比较符、std::spanGCC 8 开始有 concept 试验GCC 10 大量落地GCC 13 较完整GCC 12 以上C23std::expected、std::mdspan、deducing this、if consteval 等GCC 11 开始零星支持GCC 14 提供更多特性基础项目建议 GCC 14 以上这张表比 C 语言的更值得收藏因为 C 的默认标准升级极其缓慢。GCC 5 及以前默认是 gnu98GCC 6 到 GCC 10 默认是 gnu14直到 GCC 11 才把默认标准提到 gnu17。也就是说你装了最新的稳定版 GCC如果不加参数编译器也只是按 C17 来编译哪怕它已经完整支持 C20、C23 了。1.4 默认编译标准速查表为了方便日常排查我把不同大版本的默认标准整理成一个速查表。你只要看一眼gcc --version的输出就能立刻知道“不加参数的情况下编译器会用什么标准”。GCC 版本默认 C 标准默认 C 标准4.8.xgnu90gnu984.9.xgnu90gnu985.xgnu11gnu986.x - 7.xgnu11gnu148.x - 10.xgnu17gnu1411.x - 当前gnu17gnu17这里面的“gnu”前缀代表编译器使用带 GNU 扩展的方言版本不是严格意义上的 ISO 标准。比如-stdc11是严格标准模式-stdgnu11则额外启用了 typeof、__int128、嵌套函数等 GNU 扩展。跨平台代码尽量避免依赖 GNU 扩展否则换一套编译器就编译不过了。2. 为什么版本支持情况这么重要从日常编译说起2.1 教学环境中常见的编译器版本陷阱我在不少教学环境里见过同一个问题教材用的 C11 写示例代码学校的服务器或者学生自己电脑上却装着老掉牙的编译器默认标准还是 C98于是一打「auto」就报错一写「lambda」就报错甚至nullptr都认不出来。学生一脸懵以为是自己课本没学透。实际上这些都是典型的“标准支持版本”问题。比如 GCC 4.8.5 虽然已经完整支持 C11但需要你手动加-stdc11才能开启。而 GCC 4.8.5 之前的版本就算加了参数也有一批 C11 特性完全不可用。教学环境里如果不想让学生被这些环境问题折磨建议至少统一到 GCC 9 以上配好默认标准或者干脆在课程作业的编译命令里写清楚-stdc17。2.2 生产环境与 libstdc 的关系GCC 对 C 的支持不仅限于语言层面的语法解析还高度依赖标准库 libstdc。GCC 5 提供的 libstdc 和 GCC 13 提供的 libstdc 完全是两个体量。很多 C17 特性比如std::filesystem不仅要求编译器能解析语法还要求标准库里真的有这个头文件和实现。这就带来了一个非常实际的问题你用旧 GCC 编译出来的程序链接的 libstdc 也是旧版本的新标准库里的符号没有程序跑起来就会报 undefined reference。反过来用新 GCC 编译出的程序放到老的运行环境里也可能因为找不到对应版本 libstdc 的符号而无法运行。生产环境的容器镜像也好、嵌入式设备也好一定要保证编译器和运行库版本匹配。这也是为什么很多大项目在 CI 里会把 GCC 版本写死而不是随手拿一个gcc命令就开始编译。2.3 GCC 为什么在默认标准上这么保守说了这么多可能会有人问既然新标准这么好为什么 GCC 不直接把默认标准设为最新答案是兼容性成本太高。C/C 都是有着三十年历史的老语言市面上存量代码极其庞大。如果某个大版本把默认标准直接改成 C23一堆老代码编译不过用户第一反应永远是“GCC 出新版本了一堆旧工程全崩了”然后就不敢升级了。GCC 团队选择的是非常保守的渐进策略先在命令行里用-stdc20开启新标准让用户尝鲜经过几年的打磨验证再把默认标准往上抬一档。因此在实际工程中尽量不要依赖“默认标准”写代码明确在构建配置里指定标准版本才是正道。3. 各平台GCC安装与升级实操3.1 Ubuntu/Debian 系安装与升级在 Ubuntu 上安装 GCC 非常直接sudo apt update sudo apt install gcc g make gcc --version如果你需要确定某个具体版本可以用下面的方式搜索apt list | grep gcc-Ubuntu 的官方源里一般只保留一个默认版本版本更新要靠 toolchain-test PPA。比如你希望把 GCC 升到 13 或更新版本可以这样sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt update sudo apt install gcc-13 g-13装好之后系统里会同时存在多个版本的 gcc但此时直接执行gcc --version用的还是系统原来的老版本。这里推荐用 update-alternatives 来管理默认编译器sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 60 sudo update-alternatives --install /usr/bin/g g /usr/bin/g-13 60 sudo update-alternatives --config gcc sudo update-alternatives --config g之后输入gcc --version就会看到已经切换过去了。这个机制特别适合系统里同时存在多个编译工具的开发者。3.2 CentOS/RHEL 系常规安装与源码编译CentOS、RHEL 系列的 Yum 安装很简单sudo yum install gcc gcc-c make但老系统的 GGC 版本往往很旧比如 CentOS 7 默认 4.8.5。想用新编译器最困难的是离线内网环境这里给一套我实测过的方案。先在有外网的机器上用 yumdownloader 把 gcc 及其依赖包全部拉下来yumdownloader --resolve gcc gcc-c make然后把下载的 rpm 文件拷贝到内网机器比如放到/opt/gcc_rpms目录下直接安装cd /opt/gcc_rpms yum localinstall *.rpm依赖特别复杂的情况下建议把目录做成一个本地 yum 源用createrepo生成元数据再写一个.repo文件指过去这样后续装其它工具时还能复用这个源。如果 rpm 方式搞不定或者你想要一个非常新的版本可以选择源码编译安装 GCC。下载 gcc 源码包后先解压并进入目录tar xf gcc-13.2.0.tar.xz cd gcc-13.2.0 ./contrib/download_prerequisites这个脚本会自动下载编译 GCC 必需的 gmp、mpfr、mpc 三个依赖库省去手动找依赖的麻烦。然后建一个 build 目录来编译避免污染源码目录mkdir build cd build ../configure --prefix/opt/gcc-13.2.0 --enable-languagesc,c --disable-multilib make -j$(nproc) sudo make install--disable-multilib这个参数建议加上否则很多 64 位系统上还要配 32 位库容易报错。源码编译非常耗时四核机器编译 GCC 也得半小时起步如果内存小于 2GB甚至可能因为内存不足被杀死。编译完成后把/opt/gcc-13.2.0/bin加入 PATH 即可。3.3 WindowsMinGW-w64、MSYS2 与 VSCode 配置Windows 上写 C/C 最常见的方式是装 MinGW-w64。但千万不要下载那些停留在 GCC 4.9 时代的“远古安装包”建议直接使用 MSYS2 或 WinLibs。MSYS2 的优点是自带完整的包管理工具安装时先下载安装脚本然后打开 MSYS2 终端pacman -Syu pacman -S mingw-w64-x86_64-gccWinLibs 更省事下载压缩包解压把解压目录下的bin文件夹路径加到系统 PATH 里。比如解压到C:\mingw64就把C:\mingw64\bin加进去。PATH 修改后需要重新打开终端才能生效很多人加完 PATH 后发现gcc命令还是找不到基本都是没重开终端。VSCode 配置 C/C 环境时先安装 C/C 官方扩展ms-vscode.cpptools然后写两个文件。tasks.json负责编译核心内容如下{ version: 2.0.0, tasks: [ { label: g build active file, type: shell, command: g, args: [ -stdc17, -g, ${file}, -o, ${fileDirname}\\${fileBasenameNoExtension}.exe ], group: { kind: build, isDefault: true } } ] }launch.json负责启动调试器{ version: 0.2.0, configurations: [ { name: C Debugger, type: cppdbg, request: launch, program: ${fileDirname}\\${fileBasenameNoExtension}.exe, args: [], cwd: ${fileDirname}, externalConsole: false, MIMode: gdb, miDebuggerPath: C:\\mingw64\\bin\\gdb.exe, preLaunchTask: g build active file } ] }关键在于miDebuggerPath要和你的 MinGW-w64 安装目录一致。如果iostream头文件都找不到通常就是编译器路径或者 IntelliSense 模式没配对。3.4 macOSCommand Line Tools 与真正的 GCCmacOS 上最简单的方式是运行xcode-select --install这会装上 Xcode Command Line Tools内置 clang 编译器同时在系统里提供了gcc这个命令。注意这里的gcc实际是 clang 的符号链接并不是真正的 GCC。如果你要编译 Linux 风格的 GCC 代码、或者需要 GCC 特有的扩展建议用 Homebrew 安装真正的 GCCbrew install gcc13安装完成后命令名是gcc-13和g-13因为系统的/usr/bin/gcc已经被 clang 占用了。直接用gcc-13 --version就能看到 GCC 自己的版本信息。不直接覆盖系统的 gcc是为了避免把 macOS 底层构建依赖弄坏这个心机一定要理解。4. 编译选项与标准检测实战4.1 -std 写法速查GCC 指定语言标准靠的是-std参数。C 语言和 C 语言各有一套值。C 语言常用值写法含义-stdc89 或 -stdc90严格 C89/C90 模式-stdgnu90C89/C90 GNU 扩展-stdc99严格 C99 模式-stdgnu99C99 GNU 扩展-stdc11严格 C11 模式-stdgnu11C11 GNU 扩展-stdc17严格 C17 模式-stdgnu17C17 GNU 扩展-stdc23C23 部分特性模式-stdgnu23C23 GNU 扩展C 语言常用值写法含义-stdc98严格 C98 模式-stdgnu98C98 GNU 扩展-stdc11严格 C11 模式-stdgnu11C11 GNU 扩展-stdc14严格 C14 模式-stdgnu14C14 GNU 扩展-stdc17严格 C17 模式-stdgnu17C17 GNU 扩展-stdc20严格 C20 模式-stdgnu20C20 GNU 扩展-stdc23C23 部分特性模式-stdgnu23C23 GNU 扩展实际使用中我非常建议直接写成-stdc17或-stdc20而不是用 gnu 开头的版本。虽然 gnu 模式会额外支持一些便捷的扩展但将来代码要跨编译器、跨平台时会非常被动。4.2 用宏快速确认编译器的能力你可以不查文档直接在终端里跑一条命令看编译器到底支持哪些预定义宏gcc -dM -E - /dev/null | sort g -dM -E -x c - /dev/null | sort尤其关注两个宏__STDC_VERSION__和__cplusplus它们会直接告诉你当前编译模式对应的标准版本。C 语言__STDC_VERSION__为 199409L 表示 C90/C95199901L 表示 C99201112L 表示 C11201710L 表示 C17。C 语言__cplusplus为 199711L 表示 C98201103L 表示 C11201402L 表示 C14201703L 表示 C17202002L 表示 C20。我在很多次编译报错时第一件事就是跑echo | gcc -dM -E - | grep __STDC_VERSION__确认编译器当时处于哪个标准状态比反复读报错信息高效得多。4.3 用特性测试宏做条件编译如果你的代码需要同时兼容多个编译器或多个标准版本可以用编译期宏做条件判断。比如想判断当前环境是否支持 C17 的文件系统库在代码里可以这么写#if __has_include(filesystem) #include filesystem #define HAS_FILESYSTEM 1 #else #define HAS_FILESYSTEM 0 #endif对于语言特性可以直接判断__cplusplus#if __cplusplus 202002L // 这段代码在 C20 及以上标准才参与编译 #endif这种写法在写通用库和跨平台代码时非常实用比靠编译器版本号判断可靠得多。4.4 完整示例编译一个用到 C20 特性的程序为了更直观地验证标准支持我写了一个特别简单但能戳中关键特性的程序用到了 concepts 和 std::span#include iostream #include span #include vector #include type_traits template typename T concept Numeric std::is_arithmetic_vT; long long sum_span(std::spanconst int nums) { long long total 0; for (int n : nums) { total n; } return total; } int main() { std::vectorint v{10, 20, 30, 40}; std::cout sum_span(v) \n; static_assert(Numericint); return 0; }如果直接用老编译器编译会报一堆不认识concept、span的错误。用 GCC 11 以上版本编译就非常平滑g -stdc20 -Wall -Wextra test.cpp -o test ./test输出结果为 100。不加-stdc20的话即使你用的是 GCC 13默认 gnu17 模式下也会报错说concept是未知关键字。这个例子最能说明“编译器版本新”和“默认支持新标准”是两回事。5. 高频问题排查实录5.1 我明明升级了 gcc为什么版本还是旧的这个问题真的十个人里有八个会碰到。装了新 GCC 之后运行gcc --version显示的还是旧版本原因通常是以下几种。第一PATH 环境变量顺序问题。你新装的 GCC 在/usr/local/bin或/opt/gcc/bin但系统原来的/usr/bin/gcc在 PATH 中排在前面。执行which gcc看一下返回的路径基本就能确定。第二没有用 update-alternatives 切换默认版本尤其是 Ubuntu 系身上最常犯。解决办法见上面 3.1 节的命令。第三你装的所谓“新 GCC”只是装了包却没有实际放在命令搜索范围内。比如源码编译装了/opt/gcc-13.2.0/bin但没有改 PATH。临时用可以这样export PATH/opt/gcc-13.2.0/bin:$PATH想永久生效就写进~/.bashrc或~/.zshrc。改完以后一定要重新打开终端或执行source ~/.bashrc不然 shell 还是缓存旧路径。这个细节我踩过多次每次都能看到有人忘记。5.2 Ubuntu 安装 gcc 失败Ubuntu 上apt install gcc失败常见原因有几种。一种是软件源问题添加过一些 PPA 或者改过 sources.list 之后源失效会导致安装失败。先执行sudo apt update如果 update 就报错多半是源的问题建议把废弃 PPA 删掉或者换回官方源。另一种是依赖损坏过可以用sudo apt --fix-broken install还有一种情况是/var/lib/dpkg/lock之类的锁文件卡住了 apt最常见原因是还有一个 apt 进程在跑等一会儿或者重启机器就好。不要一上来就删锁文件删坏了 dpkg 的库会导致更大的麻烦除非你能确认没有其它 apt 进程在运行。5.3 VSCode 配置 C/C 环境的几个典型报错VSCode 里配 C/C 环境报错大多是三个原因。一是终端里有没有装 MinGW-w64 并且把bin目录加进 PATH二是 tasks.json 里的编译命令和参数不对三是 IntelliSense 配置与编译器不匹配。“无法打开源文件 iostream”这类错误基本是编译器路径没配对或者根本没装编译器。“launch: program does not exist”说明调试器要启动的程序路径不对检查 launch.json 里的 program 字段是否指向了实际生成的 exe。“检测到 #include 错误请更新 includePath”则是 IntelliSense 的 includePath 没生效最简单的方法是打开命令面板执行 “C/C: Reset IntelliSense Database”再把 C_Cpp.default.compilerPath 指向真实的 gcc.exe。另外很多人会把npm : 无法加载文件 ... 因为在此系统上禁止运行脚本这个报错和 C 环境混在一起。这里要说清楚那是 PowerShell 执行策略的问题和编译链无关解决办法是在 PowerShell 里执行Set-ExecutionPolicy -Scope CurrentUser RemoteSigned然后再重开终端。5.4 Redhat Linux 离线安装 gcc 的方法前面 3.2 节提到的是常规 rpm 安装这里再补充一个更系统的思路把依赖包全部下载好之后放到内网机器上做一个本地 yum 源。具体操作如下。在有外网的 Redhat/CentOS 机器上mkdir /tmp/gcc_rpms cd /tmp/gcc_rpms yumdownloader --resolve gcc gcc-c make tar czf gcc_rpms.tar.gz *.rpm拷贝压缩包到内网机器解压到/opt/gcc_rpms。如果内网没有 createrepo 命令就用 yum localinstall 直接装cd /opt/gcc_rpms yum localinstall *.rpm -y如果已经装了 createrepo就做一个标准本地源createrepo /opt/gcc_rpms然后新增一个 repo 文件例如/etc/yum.repos.d/local.repo[local] nameLocal Repository baseurlfile:///opt/gcc_rpms enabled1 gpgcheck0最后清理缓存并安装yum clean all yum install gcc gcc-c make -y离线环境下依赖冲突是最大的坑。比如系统已经存在一个老版本的 libgcc新包要求更高版本这时不要强制卸载老包优先用 yum update 去升级它避免把系统的 glibc 搞挂。5.5 关于 “Microsoft Visual C 14.0 or greater is required”这个报错经常在 Windows 上执行 Python 的pip install 某个包时出现很多人会把它和 GCC 混在一起。它不是 GCC 的问题而是 Python 需要调用 MSVC 编译 C/C 扩展但系统里没有对应版本的 Visual C 编译工具链。解决办法是安装 Microsoft C Build Tools下载 Visual Studio Installer在“工作负载”里勾选“使用 C 的桌面开发”然后把组件装全。安装完成后重开终端再执行 pip install基本就能过。如果你实在不想装这套庞大的工具链可以考虑让 pip 只安装预编译的轮子包pip install --only-binary :all: 包名如果确实没有对应平台的 wheel那还是老实装 Build Tools。这个和本文主题相关的一点在于很多人把 GCC 当成了“唯一的 C/C 编译器”但在 Windows 生态里MSVC 是另一套独立的生态。识别每个报错属于哪条编译链比盲目安装工具重要得多。说到底GCC 版本和语言标准的关系本质上是“能力”和“默认策略”两条线。能力上新版本当然越来越强但默认策略又非常保守经常出现“编译器支持 C20 但你得手动开”的错觉。我个人的习惯是新项目开始前先在构建脚本里写死-stdc17或-stdc20然后跑一次g -dM -E -x c - /dev/null确认环境状态。这样换机器、换平台的时候至少能保证编译行为是可预测的。如果你经常要在多个环境之间切来切去这篇文章的对照表和安装命令应该能帮你省下不少折腾时间。希望各位少踩编译器的坑把精力留在真正有价值的事情上。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →