ARTICLE DETAIL

资讯详情

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

GCC 12.2.0 源码编译安装实战与避坑指南

GCC 12.2.0 源码编译安装实战与避坑指南 编译安装 GCC 12.2.0 这个事说难不难说简单也真坑不少。我最早接触 GCC 源码编译是在老旧的服务器上系统自带编译器版本太旧连 C11 都支持不全更别提跑新一点的库和框架那时候只能自己动手从源码把新版 GCC 装到自定义目录里避免把系统环境搞乱。后来陆陆续续在 CentOS、Ubuntu、甚至内网离线环境都做过同样的事发现很多细节你要是没注意轻则编译过程反复报错重则编译完成之后gcc -v还是旧版本白忙活一场。这篇文章就围绕 GCC 12.2.0 的编译安装展开从版本选择、依赖处理、configure 参数、编译调优、安装切换、常见坑排查一路讲到底。适合三类人看一是系统自带 GCC 太老、想升级但不敢动系统包的运维和开发二是需要交叉编译或定制编译器功能的嵌入式工程师三是想搞懂 GCC 内部构建逻辑、顺便踩踩坑的 Linux 爱好者。内容尽量按实际操作的顺序来每一步都会解释为什么这么做方便你举一反三。1. 为什么要自己编译 GCC 而不是装系统包1.1 系统自带 GCC 版本往往够用但不够新绝大多数 Linux 发行版出于稳定性和兼容性考量自带的 GCC 版本都会刻意保持保守。比如 CentOS 7 自带的 GCC 4.8.5CentOS 8 是 GCC 8.xUbuntu 18.04 是 7.5Ubuntu 20.04 是 9.4。这些版本在执行gcc --version时看着没啥问题可一旦你要编译 C17/C20 的代码、用上较新的 OpenMP 特性或者需要编译某些对新编译器版本有硬性要求的项目旧版本立刻就捉襟见肘了。我遇到过最典型的场景是想在 CentOS 7 上源码安装某个新版本 Redis 和 Node.js 的依赖模块结果底层 C 库死活编不过最后定位到是 GCC 4.8.5 对 C14 的支持不完整。系统包管理器里又找不到更新版本的 GCC唯一靠谱的办法就是手动编译安装一个新版 GCC。而且手动编译不碰系统自带的/usr/bin/gcc完全装到/usr/local或自定义目录不破坏系统原有编译环境对线上机器来说安全得多。1.2 选择 GCC 12.2.0 是基于什么考虑GCC 版本号演进到 12 这个大版本以后整体稳定性已经很成熟。12.2.0 是 12 系列的一个修复版本相对于 12.1.0 修了不少 bug在 C23 的部分特性、Fortran 运行时、以及一些架构后端的代码生成上都有改进。选择 12.2.0 而非更新的 13.x、14.x主要有几个原因12.2.0 发布至今已有足够多的生产环境使用验证踩坑资料丰富。它完整支持 C17并且对 C20 的支持已经相当可靠这对大多数现代 C 项目都够用了。在老系统比如 CentOS 7 的 glibc 2.17上GCC 12 的运行时要求适中不像新版本那样可能需要更高版本的 glibc 或 binutils不然很容易出现编译出来无法运行的情况。很多第三方库PyTorch、TensorFlow 的某些源码模块、OpenCV 等编译时对 GCC 版本的要求上限往往还没有覆盖到太新的大版本选一个不算激进也不算太老的 12.2.0 是最稳妥的平衡点。当然如果你不追求某个具体小版本直接选最新的 12.4.0 或者干脆上 13 系列也没问题下面讲的编译安装流程通用。1.3 自己编译带来的额外价值除了版本新自己编译 GCC 还能做几件系统包做不了的事一是定制安装路径比如装到/opt/gcc-12.2.0方便随时切换或卸载二是在 configure 阶段只启用你需要的语言前端比如只要 C 和 C缩短编译时间三是可以结合--with-arch、--with-cpu等参数为特定 CPU 做优化四是如果公司内部有代码评审或安全审计要求源码安装的可追溯性也更好你清楚每一个二进制是从哪段源码构建出来的而不是依赖二进制包。2. 编译前的环境准备与依赖盘点2.1 检查系统现状编译器、make、binutils、gmp/mpfr/mpc在动手之前先把底子摸清楚。以 CentOS/RHEL 系为例一条命令能看大部分信息gcc --version g --version make --version ld --version如果系统是全新安装的裸机很可能连 make 都没有。CentOS 可以用 yum 装基础工具链yum groupinstall Development ToolsUbuntu/Debian 则用apt update apt install build-essential这一步的目标是保证系统里至少存在一个可用的老版 GCC用于编译新 GCC以及 make、binutils。因为 GCC 本身的构建过程也需要一个能跑的 C 编译器来孵化它这个叫 bootstrap 机制。整个流程大体是先用系统老 GCC 把新 GCC 的 C 编译器编出来再用这个新编的 C 编译器去编译 C 编译器然后用编出来的完整编译器再去编译一遍自身确保编译产物是自洽且经过优化的。所以系统自带的 gcc 和 make 几乎是必需品不要试图在完全没有编译器的机器上直接编 GCC。2.2 GCC 构建依赖的三大库gmp、mpfr、mpcGCC 内部做复杂运算和浮点优化时需要依赖三个算术库GMP大整数与高精度浮点运算、MPFR正确舍入的浮点运算、MPC复数运算。这三个库在大多数系统的包管理器里都能装到但版本要注意不能太老。CentOS 7 上yum install gmp-devel mpfr-devel libmpc-develUbuntu 20.04 上apt install libgmp-dev libmpfr-dev libmpc-dev如果你在 configure 阶段看到类似 configure: error: Building GCC requires GMP 4.2, MPFR 2.4.0, MPC 0.8.1 的报错大概率就是这三个依赖没装或者版本太老。还有一种情况是在内网离线环境里包管理器没有对应的 RPM/DEB 包这时有三个选择第一找一台能联网的同版本系统把依赖包装成离线包带进去。CentOS 可以使用 yumdownloader 或者repotrack把gmp-devel、mpfr-devel、libmpc-devel连同依赖一起拉下来拷到内网用rpm -Uvh *.rpm安装。Ubuntu 可以借助apt-get download或apt-offline工具实现类似效果。第二直接下载 gmp、mpfr、mpc 的源码包放到 GCC 源码目录下GCC 的构建系统会自动连同源码一起编译并静态链接到二进制里。这是最推荐的做法因为它完全绕开了系统包而且不污染系统库。具体操作是把三个源码包解压后重命名成gmp、mpfr、mpc放入 GCC 12.2.0 的源码目录中GCC 的 configure 会自动检测并使用。第三在 configure 时通过--with-gmp、--with-mpfr、--with-mpc手动指定库路径但这种方式需要你自己把这三个库提前源码编译到某个目录步骤更多不太建议。我的经验是只要不是必须离线直接用系统包管理器装这三个依赖最省事。但如果你能联网也不在乎多编译一点东西第二种源码目录直接内嵌的方案其实最稳妥因为它严格保证了 GCC 在编译和运行期间使用的 gmp/mpfr/mpc 版本完全一致不会出现系统库版本互相打架的问题。2.3 磁盘空间、内存和 CPU 核数要心里有数GCC 12.2.0 完整编译下来占用的磁盘空间相当可观。源码包解压后大约 1.2G 左右编译中间产物目录建议留出 20G 以上的空间安装目录至少准备 5G。我实际遇到过一次只给 /home 分了 15G 的机器编译到一半磁盘写满直接报 No space left on device前功尽弃。所以建议在动手之前用df -h看一眼最好把编译目录放在剩余空间充足的挂载点上。内存方面用-j$(nproc)全核编译GCC 同时编译多个文件每个编译任务吃内存非常厉害4 核 8G 内存的机器经常会出现 out of memory编译进程被内核 kill。建议内存小的机器限制并行任务数例如 4 核机器用-j28 核机器用-j4编译时间稍微长一点但至少不会被 OOM 中断。3. 编译安装完整过程3.1 下载源码与校验文件GCC 官方源码可以从 GNU 镜像站下载也可以选择国内高校镜像加速。以 GCC 12.2.0 为例核心文件名是gcc-12.2.0.tar.xz一般几十 MB下载链接形如wget https://ftp.gnu.org/gnu/gcc/gcc-12.2.0/gcc-12.2.0.tar.xz国内网络环境如果连接官方源太慢可以用清华、中科大镜像wget https://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-12.2.0/gcc-12.2.0.tar.xz wget https://mirrors.ustc.edu.cn/gnu/gcc/gcc-12.2.0/gcc-12.2.0.tar.xz建议同时下载.sig签名文件做校验前提是你本地有 GNU 的 GPG 公钥。如果不熟悉 GPG至少用 md5/sha256 对一下镜像和官方值避免下到被篡改或不完整的包md5sum gcc-12.2.0.tar.xz sha256sum gcc-12.2.0.tar.xz解压tar -xf gcc-12.2.0.tar.xz cd gcc-12.2.0从这一步开始有一个几乎所有人都会忽略的细节强烈建议不要直接在源码目录里运行 configure而是单独建一个 build 目录在 build 目录里执行 configure。也就是源码目录和构建目录分离的 out-of-tree 构建方式。原因很简单一旦配置或编译过程出错你可以直接清空 build 目录重来源码目录始终保持干净不需要重新解压。这一点在反复调试 configure 参数时能省很多时间。mkdir -p /opt/compile/gcc-12.2.0-build cd /opt/compile/gcc-12.2.0-build3.2 configure 参数详解前缀、语言、线程模型、架构configure 是整个编译安装的灵魂参数设置得对不对直接决定你装出来的 GCC 好不好用。我一般用的参数组合是../gcc-12.2.0/configure \ --prefix/opt/gcc-12.2.0 \ --enable-languagesc,c,fortran \ --disable-multilib \ --enable-shared \ --enable-threadsposix \ --enable-checkingrelease \ --with-system-zlib \ --without-headers逐个说下这些参数的意思--prefix/opt/gcc-12.2.0指定安装目录。这个路径决定了后面装完的gcc和g可执行文件在/opt/gcc-12.2.0/bin下面。我习惯安装到/opt而非/usr/local好处是目录干净一眼就能认出版本。--enable-languagesc,c,fortran只启用 C、C 和 Fortran。如果你不编 Fortran建议去掉 fortran毕竟多一个语言前端就多一堆编译时间。Javagcj在 GCC 12 里已经没了AdaGNAT和 Go 如果没有特殊需要也不建议启用。--disable-multilib禁用多库架构。在 x86_64 机器上默认 multilib 会额外编一套 32 位库既耗时又占空间大多数情况用不到 32 位所以直接关掉。--enable-shared生成共享库 libgcc_s、libstdc 等。这个一般默认是开的显式写上是让配置行为更可控。--enable-threadsposix指定线程模型为 POSIX。不写的话 GCC 会自己探测但某些偏门环境可能检测出不对的模型显式指定更稳。--enable-checkingreleaseGCC 内部有很多自检逻辑在开发版里会全开导致编译出的编译器运行缓慢。release 模式只保留必要的检查性能更好。--with-system-zlib用系统 zlib避免 GCC 静态带一份。如果你系统里没有 zlib-devel可以去掉这个参数。--without-headers这个参数通常用于构建交叉编译器的阶段一如果你只是本机使用不要加。我列在这里是提醒大家网上很多交叉编译教程会带上它照搬到本机编译会导致生成的 GCC 缺少标准头文件路径编出来的程序各种缺头文件。如果机器 CPU 比较新还可以加--with-archx86-64-v3 --with-tunenative前者启用 CPU 较新的指令集扩展后者让 GCC 默认生成针对本机调优的代码。注意--with-tunenative编译出的编译器在别的机器上不一定最优但对单机使用够用。configure 执行完毕后终端会有一大堆检测输出最后出现类似 checking for default BUILD_CONFIG... bootstrap 或者直接结束没关系。如果中途报错说明依赖环境有问题自动退出回到前面排查。3.3 make 阶段耐心等待与合理控制并行度配置完成之后进入编译阶段make -j$(nproc)这个命令会调用系统全部 CPU 核并行编译。对于 8 核 16 线程的机器编译过程大约需要 40-60 分钟如果是 4 核老机器可能要 2-3 小时。你可以先去干别的但要留意两件事第一编译日志如果重定向到文件建议这样执行make -j$(nproc) /tmp/gcc-build.log 21发生错误时可以直接grep -i error定位不用在终端里翻历史记录。第二如果编译中途 OOM 被 kill终端会看到 Killed 字样这时不要继续 make应该先清理残留再降低并行度重试make distclean make -j2值得一提的是GCC 的 bootstrap 过程会编译三轮编译器和两轮运行时库具体日志里能看到stage1、stage2、stage3类似字样这是 GCC 用自己编出来的新编译器再编译自己确保编译结果和编译器自身行为自洽。如果你只想快速编出一个能用的 GCC且有自信新版 GCC 编译器没有自身递归编译的问题可以让 configure 带上--disable-bootstrap但我不推荐因为关闭 bootstrap 编出来的编译器质量没有经过完整自验证偶尔会出现奇怪的问题。除非你只是要测试某个前端特性否则保留默认 bootstrop。3.4 make install 与 install 目录清理编译完成后make install安装过程比较快几分钟就结束。装完之后可以看一眼目录结构ls /opt/gcc-12.2.0/bin正常情况下会出现gcc、g、gfortran、gcov等可执行文件。同时lib目录里有libstdc.so.6、libgcc_s.so.1等运行时库。由于我们用了 out-of-tree 构建安装完毕之后源码目录和解压目录都可以保留但如果磁盘紧张编译生成的 build 目录可以整个删除重新编的时候再建即可。源码包也可以删未来想重装或换参数时重新下载也不是什么大问题。3.5 让新 GCC 成为默认编译器PATH、符号链接、动态库安装是装好了但如果你直接执行gcc --version大概率看到的还是系统旧版本。原因很简单Linux 只会在 PATH 环境变量中列出的目录里查找命令而系统自带 GCC 通常位于/usr/bin早就排在前面了。想让新 GCC 生效有几个办法。第一个是把/opt/gcc-12.2.0/bin放在 PATH 的最前面并且设置成当前用户的默认环境。以 bash 为例export PATH/opt/gcc-12.2.0/bin:$PATH想永久生效写到~/.bashrc或/etc/profile.d/gcc-12.2.0.shcat /etc/profile.d/gcc-12.2.0.sh EOF export PATH/opt/gcc-12.2.0/bin:$PATH export LD_LIBRARY_PATH/opt/gcc-12.2.0/lib64:$LD_LIBRARY_PATH export MANPATH/opt/gcc-12.2.0/share/man:$MANPATH EOF source /etc/profile.d/gcc-12.2.0.sh注意这里LD_LIBRARY_PATH必须加上否则运行 C 程序时动态链接器找的还是系统老版本的libstdc.so.6就可能出现 version GLIBCXX_3.4.30 not found 这类经典报错。特别是你编译的 C 程序动态链接到一个新版的 libstdc而系统动态库搜索路径里没有这个新库时几乎必现这个问题。第二个方法是只改当前 shell适合临时验证或单个项目使用。第三种方法是直接改系统符号链接把/usr/bin/gcc换成新版本但这会影响到系统内所有调用 gcc 的逻辑包括内核模块编译、dhclient、或者其他依赖系统工具链的脚本强烈不建议在服务器上这么干。把新 GCC 放在PATH前面而不是替换系统命令是最安全的做法。另外升级之后仍然出现旧版本还有一个常见原因你通过which gcc看到的路径是/usr/bin/gcc但/opt/gcc-12.2.0/bin明明在前面这时候要检查一下.bashrc里是不是有 hash 缓存可以执行hash -r或者直接重新登录一次终端。4. 常见问题与排查技巧实录4.1 升级后为啥还是旧版本的完整排查流程这个问题是很多人第一次编译完 GCC 后第一个遇到的坑。你明明记得编译安装成功了但gcc -v显示的版本号和以前一模一样。我有一个标准排查顺序which gcc type -a gcc echo $PATH如果which gcc还指向/usr/bin/gcc说明 PATH 顺序有问题请参考 3.5 部分设置环境变量。如果which gcc已经指向/opt/gcc-12.2.0/bin/gcc但版本依然显示旧版本那就需要直接执行新二进制确认/opt/gcc-12.2.0/bin/gcc --version如果输出依然不是 12.2.0说明你之前可能编译或者安装过程有问题或者源码包版本不对。这时候回看 configure 和 make 日志检查--prefix参数是否生效。还有一种很隐蔽的情况某些发行版会把 gcc 封装成gcc-11、gcc-12这样的多版本并存的 update-alternatives 模式/usr/bin/gcc只是一个指向/etc/alternatives/gcc的软链接这个链最终指向哪里由 alternatives 机制管理。因此即使你把/opt/gcc-12.2.0/bin放在 PATH 前面某些构建脚本仍会因为CXX环境变量写死了/usr/bin/g而使用系统旧编译器。解决办法是在构建项目的命令前显式设置export CC/opt/gcc-12.2.0/bin/gcc export CXX/opt/gcc-12.2.0/bin/g4.2 configure 阶段提示找不到 gmp/mpfr/mpc这个报错信息非常直观例如configure: error: Building GCC requires GMP 4.2, MPFR 2.4.0 and MPC 0.8.1.出现原因分两种。一是确实没装依赖库按 2.2 节的方法用包管理器安装即可。二是装了但版本过老比如 CentOS 7 自带的libmpc-devel只有 1.0.1版本号满足要求却因为头文件路径不对而找不到。这时可以试试显式指定路径或者直接用源码内嵌方案cd gcc-12.2.0 wget https://ftp.gnu.org/gnu/gmp/gmp-6.2.1.tar.bz2 wget https://ftp.gnu.org/gnu/mpfr/mpfr-4.1.0.tar.bz2 wget https://ftp.gnu.org/gnu/mpc/mpc-1.2.1.tar.gz tar -xf gmp-6.2.1.tar.bz2 mv gmp-6.2.1 gmp tar -xf mpfr-4.1.0.tar.bz2 mv mpfr-4.1.0 mpfr tar -xf mpc-1.2.1.tar.gz mv mpc-1.2.1 mpc然后把它们放进 GCC 的源码根目录gcc-12.2.0/下面GCC 的 configure 就会自动关联编译。注意解压后的目录名必须是gmp、mpfr、mpc不能带版本号否则 GCC 检测不到。这个做法在离线环境尤其好用因为不用碰系统库也不会出现版本互相覆盖的问题。4.3 编译过程中内存不足被 kill如果编译时内核日志里经常出现oom-killer或者 make 输出里有 Killed 字样别急着加内存先降并行度make -j1单线程编译肯定能过但时间非常夸张。比较合理的做法是观察内存占用free -h看一下系统记忆体总量然后设定合适的并行度。比如 8G 内存机器用-j2或-j416G 内存可以用-j8。还有个技巧是限制 GCC 编译阶段最大内存占用ulimit -v 8388608 make -j4通过ulimit限制每个进程虚拟内存上限可以防止个别编译过程把内存吃满。不过这个信号量在嵌入式交叉编译场景里更容易用到本机编译一般配合并行度和可用内存做配置就够了。4.4 编译某些项目时,还是报 GLIBCXX 版本缺失这恐怕是最常见的后续问题。你高高兴兴编译完 GCC 12.2.0用它编了一个 C 程序拿到另一台机器或者本机换了个终端运行时报错/lib64/libstdc.so.6: version GLIBCXX_3.4.30 not found原因很简单编译时链接的是新版libstdc.so.6运行时动态链接器加载的却是旧版本系统库。解决办法有几种在运行程序前设置 LD_LIBRARY_PATH把/opt/gcc-12.2.0/lib64放进去。编译时用-Wl,-rpath,/opt/gcc-12.2.0/lib64把搜索路径硬编码进二进制以后在任何机器上都会优先找/opt/gcc-12.2.0/lib64下面的库。直接把新版库路径写入/etc/ld.so.conf.d/gcc-12.2.0.conf内容为/opt/gcc-12.2.0/lib64然后执行ldconfig。第三种方法的全局影响力比较大如果是服务器上有多个用户多个项目建议谨慎使用优先在具体项目里通过 rpath 控制。4.5 源码包与目录权限问题sudo 与编译缓存有时候为了省事直接用 sudo 执行 configure 和 make这会造成目录所有权混乱后续清理或反复编译会遇到权限问题。强烈建议编译阶段都用普通用户执行只有最后的make install才用 sudo。如果 configure 和 make 用了 root后面想删除 build 目录就必须 root稍微麻烦点。如果你已经用 root 编译过可以把 build 目录整个 chown 给普通用户再继续chown -R youruser:yourgroup /opt/compile/gcc-12.2.0-build另外一个容易忽略的细节是源码目录里如果有之前 configure 生成的config.cache或者Makefile而你修改了编译参数后重新 configure必须先把这些中间文件清干净。这也是 out-of-tree 构建的优势所在删掉整个 build 目录就是最彻底的清理。下表总结了几个高频问题的快速定位方向现象可能原因优先排查方向编译后 gcc 版本没变PATH 顺序不对或 shell hash 缓存which gcc、hash -r、检查.bashrcconfigure 报缺 GMP/MPFR/MPC依赖没装或版本太老包管理器安装或源码内嵌到 GCC 目录编译中途 Killed内存不足降低-j并行度加大 swapC 程序运行报 GLIBCXX 版本缺失动态库搜索路径没指向新 libstdc设置LD_LIBRARY_PATH或用 rpathmake install 权限不足安装目录没有写权限用 sudo 执行 make install编译出的 gcc 找不到头文件configure 错加--without-headers取消该参数重新 configure5. 操作心得与后续建议编译安装 GCC 12.2.0 这件事我在不同环境反复做过多次总结下来最想分享的就是三条第一不要在零基础或者时间特别紧的时候直接上手编译。先花十分钟把环境检查、依赖安装、configure 参数想清楚比编到一半再回头查问题节省几倍时间。特别是 out-of-tree 构建目录我用习惯了之后任何一次编译失败都只需要rm -rf重建根本不用碰源码。第二离线环境下别慌。GCC 编译的离线难点主要就是 gmp、mpfr、mpc 这三个依赖以及系统里有没有基础工具链。实际上有网络时先把系统工具链和依赖包装好把源码包和依赖包都存到本地仓库再进内网操作是最稳妥的。当然如果你连内网都是全新的那退而求其次把源码内嵌方案学会也一样能编出来。第三GCC 装好后一定要记得验证动态库版本和头文件版本的一致性。新手最容易踩的两个坑一个是gcc -v显示新版本但编译 C 程序时找不到头文件另一个是编译时头文件找到了但运行时加载的是旧libstdc。前者要把/opt/gcc-12.2.0/include/c/12.2.0加入 CPLUS_INCLUDE_PATH其实安装到标准位置后这个一般自动可用后者就是 4.4 节说的动态库路径问题。可以简单写一个测试 C 程序cat test.cc EOF #include iostream int main() { std::cout __GNUC__ . __GNUC_MINOR__ . __GNUC_PATCHLEVEL__ std::endl; return 0; } EOF /opt/gcc-12.2.0/bin/g test.cc -o test ./test如果输出12.2.0说明编译器、头文件、运行库三者已经对齐这次安装才算真正完成。后续如果觉得每个项目都要手动设置 PATH 麻烦可以在项目根目录写一个env.sh统一 exportCC、CXX、LD_LIBRARY_PATH团队其他人也这样用。这是我个人比较推荐的团队协作方式比每个人在.bashrc里各自写一套要可控得多。最后再分享一个小技巧编译前在 configure 时加上--with-pkgversionMy GCC 12.2.0这个参数可以让gcc --version显示你自定义的标记。这样当你机器上存在多套 GCC 时一眼就能区分自己编的和系统自带的。这个小细节看似不起眼但在排查别人机器环境问题时能省下不少嘴皮子功夫。
返回列表