拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GCC版本与C/C++标准支持全对照:默认标准、安装升级与踩坑指南

GCC版本与C/C++标准支持全对照:默认标准、安装升级与踩坑指南

GCC 这个东西,折腾过 C/C++ 的人没有不熟的,但真要问一句“你当前这个 GCC 版本到底支持哪些 C/C++ 标准”,能一口气答上来的人其实不多。前几天有个刚入职的小朋友跑过来问我:服务器上这套老 GCC,连 C++11 默认都不开,新特性一个都用不了,是不是得让运维升一下级?我瞄了一眼版本,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 开始支持一批特性,仍在完善中暂时观望,需指定 -std=c23

这里说一个经常让新手崩溃的点:C99 里“在 for 循环内声明变量”是非常自然的写法,但如果你用的是老 GCC 4.8.5,默认标准是 gnu90,直接写for (int i = 0; i < 10; i++)会报错。很多人遇到这个问题第一反应是自己代码写错了,其实是编译器默认标准太老。

GCC 5 之后默认标准从 gnu90 跳到了 gnu11,GCC 8 之后默认变成 gnu17。所以如果你用的发行版稍微新一点,默认 C 标准基本都不会太难受。真正难受的是 C++ 那边的默认标准。

1.3 C++语言标准支持情况对照

C++ 的标准线要复杂很多,C++98、C++11、C++14、C++17、C++20、C++23,每一代都有大量新特性,GCC 对这些标准的支持是分批逐步完成的。

C++标准核心新增特性GCC 开始支持的版本建议稳定使用的版本
C++98类、模板、STL、异常、RTTIGCC 3.x 已完整支持所有版本
C++11auto、lambda、nullptr、右值引用、std::thread、智能指针GCC 4.3 开始逐步加入,GCC 4.8.1 标记完整GCC 4.8.1 以上
C++14泛型 lambda、返回值类型推导、std::make_uniqueGCC 4.9 基本支持,GCC 5 完整GCC 5 以上
C++17std::filesystem、结构化绑定、if constexpr、折叠表达式、std::optional/variantGCC 5 开始部分实现,GCC 8 基本完整,GCC 9 完善GCC 9 以上
C++20concepts、ranges、协程、<=> 三路比较符、std::spanGCC 8 开始有 concept 试验,GCC 10 大量落地,GCC 13 较完整GCC 12 以上
C++23std::expected、std::mdspan、deducing this、if consteval 等GCC 11 开始零星支持,GCC 14 提供更多特性基础项目建议 GCC 14 以上

这张表比 C 语言的更值得收藏,因为 C++ 的默认标准升级极其缓慢。GCC 5 及以前默认是 gnu++98,GCC 6 到 GCC 10 默认是 gnu++14,直到 GCC 11 才把默认标准提到 gnu++17。也就是说,你装了最新的稳定版 GCC,如果不加参数,编译器也只是按 C++17 来编译,哪怕它已经完整支持 C++20、C++23 了。

1.4 默认编译标准速查表

为了方便日常排查,我把不同大版本的默认标准整理成一个速查表。你只要看一眼gcc --version的输出,就能立刻知道“不加参数的情况下编译器会用什么标准”。

GCC 版本默认 C 标准默认 C++ 标准
4.8.xgnu90gnu++98
4.9.xgnu90gnu++98
5.xgnu11gnu++98
6.x - 7.xgnu11gnu++14
8.x - 10.xgnu17gnu++14
11.x - 当前gnu17gnu++17

这里面的“gnu”前缀代表编译器使用带 GNU 扩展的方言版本,不是严格意义上的 ISO 标准。比如-std=c11是严格标准模式,-std=gnu11则额外启用了 typeof、__int128、嵌套函数等 GNU 扩展。跨平台代码尽量避免依赖 GNU 扩展,否则换一套编译器就编译不过了。

2. 为什么版本支持情况这么重要:从日常编译说起

2.1 教学环境中常见的编译器版本陷阱

我在不少教学环境里见过同一个问题:教材用的 C++11 写示例代码,学校的服务器或者学生自己电脑上却装着老掉牙的编译器,默认标准还是 C++98,于是一打「auto」就报错,一写「lambda」就报错,甚至nullptr都认不出来。学生一脸懵,以为是自己课本没学透。

实际上这些都是典型的“标准支持版本”问题。比如 GCC 4.8.5 虽然已经完整支持 C++11,但需要你手动加-std=c++11才能开启。而 GCC 4.8.5 之前的版本,就算加了参数也有一批 C++11 特性完全不可用。教学环境里如果不想让学生被这些环境问题折磨,建议至少统一到 GCC 9 以上,配好默认标准,或者干脆在课程作业的编译命令里写清楚-std=c++17。

2.2 生产环境与 libstdc++ 的关系

GCC 对 C++ 的支持不仅限于语言层面的语法解析,还高度依赖标准库 libstdc++。GCC 5 提供的 libstdc++ 和 GCC 13 提供的 libstdc++ 完全是两个体量。很多 C++17 特性,比如std::filesystem,不仅要求编译器能解析语法,还要求标准库里真的有这个头文件和实现。

这就带来了一个非常实际的问题:你用旧 GCC 编译出来的程序,链接的 libstdc++ 也是旧版本的,新标准库里的符号没有,程序跑起来就会报 undefined reference。反过来,用新 GCC 编译出的程序放到老的运行环境里,也可能因为找不到对应版本 libstdc++ 的符号而无法运行。生产环境的容器镜像也好、嵌入式设备也好,一定要保证编译器和运行库版本匹配。这也是为什么很多大项目在 CI 里会把 GCC 版本写死,而不是随手拿一个gcc命令就开始编译。

2.3 GCC 为什么在默认标准上这么保守

说了这么多,可能会有人问:既然新标准这么好,为什么 GCC 不直接把默认标准设为最新?答案是兼容性成本太高。

C/C++ 都是有着三十年历史的老语言,市面上存量代码极其庞大。如果某个大版本把默认标准直接改成 C++23,一堆老代码编译不过,用户第一反应永远是“GCC 出新版本了,一堆旧工程全崩了”,然后就不敢升级了。GCC 团队选择的是非常保守的渐进策略:先在命令行里用-std=c++20开启新标准,让用户尝鲜,经过几年的打磨验证,再把默认标准往上抬一档。因此在实际工程中,尽量不要依赖“默认标准”写代码,明确在构建配置里指定标准版本才是正道。

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-languages=c,c++ --disable-multilib make -j$(nproc) sudo make install

--disable-multilib这个参数建议加上,否则很多 64 位系统上还要配 32 位库,容易报错。源码编译非常耗时,四核机器编译 GCC 也得半小时起步,如果内存小于 2GB,甚至可能因为内存不足被杀死。编译完成后把/opt/gcc-13.2.0/bin加入 PATH 即可。

3.3 Windows:MinGW-w64、MSYS2 与 VSCode 配置

Windows 上写 C/C++ 最常见的方式是装 MinGW-w64。但千万不要下载那些停留在 GCC 4.9 时代的“远古安装包”,建议直接使用 MSYS2 或 WinLibs。

MSYS2 的优点是自带完整的包管理工具,安装时先下载安装脚本,然后打开 MSYS2 终端:

pacman -Syu pacman -S mingw-w64-x86_64-gcc

WinLibs 更省事,下载压缩包解压,把解压目录下的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": [ "-std=c++17", "-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 macOS:Command Line Tools 与真正的 GCC

macOS 上最简单的方式是运行:

xcode-select --install

这会装上 Xcode Command Line Tools,内置 clang 编译器,同时在系统里提供了gcc这个命令。注意,这里的gcc实际是 clang 的符号链接,并不是真正的 GCC。如果你要编译 Linux 风格的 GCC 代码、或者需要 GCC 特有的扩展,建议用 Homebrew 安装真正的 GCC:

brew install gcc@13

安装完成后,命令名是gcc-13和g++-13,因为系统的/usr/bin/gcc已经被 clang 占用了。直接用gcc-13 --version就能看到 GCC 自己的版本信息。不直接覆盖系统的 gcc,是为了避免把 macOS 底层构建依赖弄坏,这个心机一定要理解。

4. 编译选项与标准检测实战

4.1 -std 写法速查

GCC 指定语言标准靠的是-std参数。C 语言和 C++ 语言各有一套值。

C 语言常用值:

写法含义
-std=c89 或 -std=c90严格 C89/C90 模式
-std=gnu90C89/C90 + GNU 扩展
-std=c99严格 C99 模式
-std=gnu99C99 + GNU 扩展
-std=c11严格 C11 模式
-std=gnu11C11 + GNU 扩展
-std=c17严格 C17 模式
-std=gnu17C17 + GNU 扩展
-std=c23C23 部分特性模式
-std=gnu23C23 + GNU 扩展

C++ 语言常用值:

写法含义
-std=c++98严格 C++98 模式
-std=gnu++98C++98 + GNU 扩展
-std=c++11严格 C++11 模式
-std=gnu++11C++11 + GNU 扩展
-std=c++14严格 C++14 模式
-std=gnu++14C++14 + GNU 扩展
-std=c++17严格 C++17 模式
-std=gnu++17C++17 + GNU 扩展
-std=c++20严格 C++20 模式
-std=gnu++20C++20 + GNU 扩展
-std=c++23C++23 部分特性模式
-std=gnu++23C++23 + GNU 扩展

实际使用中,我非常建议直接写成-std=c++17或-std=c++20,而不是用 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/C95,199901L 表示 C99,201112L 表示 C11,201710L 表示 C17。
  • C++ 语言:__cplusplus为 199711L 表示 C++98,201103L 表示 C++11,201402L 表示 C++14,201703L 表示 C++17,202002L 表示 C++20。

我在很多次编译报错时,第一件事就是跑echo | gcc -dM -E - | grep __STDC_VERSION__,确认编译器当时处于哪个标准状态,比反复读报错信息高效得多。

4.3 用特性测试宏做条件编译

如果你的代码需要同时兼容多个编译器或多个标准版本,可以用编译期宏做条件判断。比如想判断当前环境是否支持 C++17 的文件系统库,在代码里可以这么写:

#if __has_include(<filesystem>) #include <filesystem> #define HAS_FILESYSTEM 1 #else #define HAS_FILESYSTEM 0 #endif

对于语言特性,可以直接判断__cplusplus:

#if __cplusplus >= 202002L // 这段代码在 C++20 及以上标准才参与编译 #endif

这种写法在写通用库和跨平台代码时非常实用,比靠编译器版本号判断可靠得多。

4.4 完整示例:编译一个用到 C++20 特性的程序

为了更直观地验证标准支持,我写了一个特别简单但能戳中关键特性的程序,用到了 concepts 和 std::span:

#include <iostream> #include <span> #include <vector> #include <type_traits> template <typename T> concept Numeric = std::is_arithmetic_v<T>; long long sum_span(std::span<const int> nums) { long long total = 0; for (int n : nums) { total += n; } return total; } int main() { std::vector<int> v{10, 20, 30, 40}; std::cout << sum_span(v) << '\n'; static_assert(Numeric<int>); return 0; }

如果直接用老编译器编译,会报一堆不认识concept、span的错误。用 GCC 11 以上版本编译就非常平滑:

g++ -std=c++20 -Wall -Wextra test.cpp -o test ./test

输出结果为 100。不加-std=c++20的话,即使你用的是 GCC 13,默认 gnu++17 模式下也会报错说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] name=Local Repository baseurl=file:///opt/gcc_rpms enabled=1 gpgcheck=0

最后清理缓存并安装:

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 版本和语言标准的关系,本质上是“能力”和“默认策略”两条线。能力上,新版本当然越来越强;但默认策略又非常保守,经常出现“编译器支持 C++20 但你得手动开”的错觉。我个人的习惯是,新项目开始前先在构建脚本里写死-std=c++17或-std=c++20,然后跑一次g++ -dM -E -x c++ - < /dev/null确认环境状态。这样换机器、换平台的时候,至少能保证编译行为是可预测的。如果你经常要在多个环境之间切来切去,这篇文章的对照表和安装命令应该能帮你省下不少折腾时间。希望各位少踩编译器的坑,把精力留在真正有价值的事情上。

返回列表