ARTICLE DETAIL

资讯详情

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

Linux下源码编译安装GCC 12.2.0:完整步骤与工具链管理

Linux下源码编译安装GCC 12.2.0:完整步骤与工具链管理 1. 为什么要自己编译GCC 12.2.0而不是用系统自带的版本先说说这个事情的起因。做Linux开发的人迟早会遇到一个尴尬场景系统自带的GCC版本太旧C20的大部分特性编译不过新出的库又要求编译器版本不低于某个门槛而包管理器里能装到的编译器版本又总是落后两三个大版本。比如在CentOS 8或者基于它的各种发行版上系统自带GCC通常停留在8.5.1Ubuntu 20.04默认的GCC是9.4虽然比8代好了不少但要用到C20里的concepts、coroutines、ranges这些新标准特性时还是会碰到各种和预期不一致的编译错误。用老编译器凑合写代码等于穿着雨靴跑百米怎么都不痛快。编译安装GCC 12.2.0本质上是自己从源码构建一套独立的、新版本的编译工具链好处很明显。版本可控且独立安装到独立目录比如/usr/local/gcc-12.2.0不覆盖系统自带的GCC哪个项目需要新特性就切换哪套环境日常系统编译继续用系统默认的GCC互不干扰。特性完整GCC 12系列是第一个比较完整支持C20的版本std::ranges、std::span、std::format虽然当时还有争议、constexpr相关新能力都能用同时还在实验性支持C23的部分内容对研究新标准特性的人来说非常关键。优化选项更多新版本对新CPU的调度、向量化支持更完善项目如果用-marchnative重新编译核心模块性能收益能直接感受到。这个方案适合谁我说得直白一点适合Linux服务器管理员、嵌入式交叉编译链搭建者、维护C/C基础库的开发者以及需要尝鲜新标准的科研和工程人员。你用RHEL系的dnf或者Debian系的apt装个老版本那是图省事但如果项目被卡在“编译器版本不够导致编译不过”看完这篇文章你可以自己完整走一遍从源码编译到全局切换的流程。而且整个编译过程本质上通用换一个GCC新版本、换一台Linux发行版套路完全一样。2. 编译前的准备工作依赖、源码包和目录规划2.1 三大隐藏依赖GMP、MPFR、MPC到底在干嘛编译GCC并不是直接拿源码包就能跑的它自己有一套依赖库最核心的是三个GMP、MPFR、MPC。这三个库是数学运算库名字听起来和编译器八竿子打不着但GCC内部的很多优化过程要做高精度整数、浮点运算和复数的复杂运算。我用大白话解释GMPGNU Multiple Precision Arithmetic Library负责任意精度的大整数和有理数运算。编译器在分析代码时常量折叠、溢出判断、复杂表达式求值都需要做大数运算比如处理128位整数、校验一个常量表达式在目标平台上有没有溢出GMP就是干这个的。MPFR在GMP基础上提供高精度浮点运算并且有严格的四舍五入规则。GCC做浮点优化时要尽量保证编译结果和运行时结果一致MPFR能提供可控精度的浮点行为。MPC又建立在MPFR之上用于处理复数的加减乘除和开方。某些数学变换、复数表达式优化会用到。这三个库不是随便装一下就行版本之间是有搭配关系的。GCC 12.2.0在编译时对这三个依赖的版本有最低要求如果你用系统自带的很老版本经常会遇到版本过旧或者缺少某些符号的报错。最省事、最不容易翻车的办法是直接从GNU官网下载这三个库的源码包放到GCC的源码目录下目录名去掉版本号GCC的构建系统会自动发现同目录下的源码并优先使用它们一起编译。这个做法特别适合离线环境也避免依赖系统的包管理器版本。编译一个正常的GCC只涉及这三个数学库不需要Boost、不需要CMake、不需要Python这点可以放心。相比现在很多开源项目要拉一堆依赖GCC的源码构建反而显得清爽。2.2 下载源码包和版本搭配建议GCC 12.2.0发布是2022年8月的事情当时属于12系列的稳定维护版本。这一版的C标准支持已经很成熟bug少稳定性好。如果你是第一次自行编译GCC我建议就直接装12.2.0别一上来就碰13、14甚至日更的实验版本。12.2.0正好处于C20功能基本齐坑已经填掉大半的合理阶段。整体清单如下组件版本说明GCC12.2.0主编译源码下载gcc-12.2.0.tar.xz即可GMP6.2.1 或以上高精度整数运算库MPFR4.1.0 或以上高精度浮点运算库MPC1.2.1 或以上复数运算库make任何现代版本构建工具3.81以上基本都行gcc/g系统已有版本用来编译GCC的引导编译器4.8以上一般都够binutils系统自带或新版提供汇编器、链接器建议系统自带即可从GNU官网的镜像站点可以下载GCC源码包文件在/pub/gcc/releases/gcc-12.2.0/目录下。三个依赖库也在GNU官网各自的下载目录里完整包分别叫gmp-6.2.1.tar.xz、mpfr-4.1.0.tar.xz、mpc-1.2.1.tar.xz。这里有个非常实用的离线技巧如果目标机器不能联网你可以在能上网的机器上把这三个源码包下载好连同GCC源码一起拷贝过去。把依赖包解压到GCC源码目录后GCC的configure脚本会自动识别这一步会把离线环境下最麻烦的依赖问题直接消解掉。很多内网服务器编译GCC失败十有八九都是卡在缺少GMP或MPFR系统库上。2.3 磁盘空间、内存和基础环境检查编译GCC 12.2.0会对机器资源有要求我先给个经验值免得你编译到一半直接爆盘或者被操作系统OOM杀掉。磁盘空间源码包大概90MB解压后的源码目录约800MB编译后的build目录建议预留不低于6GB我建议直接留10GB因为并行编译的中间文件量很大。如果你要启用全部语言前端C、C、Fortran、Ada、Go等占用空间还会进一步上涨只编译C和C的话5GB是保守下限。内存4GB内存可以勉强顶下来但make -j4时内存会吃得比较快我之前在2GB的云服务器上编译过一次cc1plus编译大文件时直接被内核杀掉了。个人建议至少4GB内存8GB能跑得很从容。如果是交叉编译做嵌入式工具链内存占用会低一些但主机构建GCC时给足内存是值得的。编译时间同样CPU下时间差异可以从二十分钟到两个多小时不等。我实测过8核16线程的机器编译C和C两个前端大概四十分钟左右4核虚拟机大概要一个半小时到两小时。建议编译时连上电源、关掉不必要的负载让CPU吃满也不心疼。环境检查方面你要确保系统里已经有足够新的make、g和binutils。现在任何主流的Linux发行版都自带这些基础工具如果连系统自带GCC都没有先执行一下包管理器安装基础开发工具CentOS系用dnf groupinstall Development ToolsUbuntu用apt install build-essential。编译GCC存在鸡生蛋的问题第一个编译器总得靠另一个编译器来编。3. configure配置参数和编译过程详解3.1 解压源码并把三个依赖捆绑进GCC源码树先建个工作目录习惯上叫tools或者src这个随个人习惯。我把整个过程的路径写成一个可以直接照抄的版本mkdir -p /opt/src cd /opt/src # 下载或拷贝gcc-12.2.0.tar.xz、gmp-6.2.1.tar.xz、mpfr-4.1.0.tar.xz、mpc-1.2.1.tar.xz到当前目录 tar xvf gcc-12.2.0.tar.xz cd gcc-12.2.0 # 将依赖包解压后放置到gcc源码目录注意目录名要去掉版本号 tar -xf ../gmp-6.2.1.tar.xz mv gmp-6.2.1 gmp tar -xf ../mpfr-4.1.0.tar.xz mv mpfr-4.1.0 mpfr tar -xf ../mpc-1.2.1.tar.xz mv mpc-1.2.1 mpc这样处理之后gcc-12.2.0目录下会有gmp、mpfr、mpc这三个子目录。在运行configure时GCC的构建系统会检测到它们已经存在从而跳过找系统库这个步骤直接使用这些源码一并编译。这个技巧我从GCC官方文档里学来的后来在离线环境里反复验证确实稳定好用。需要注意目录名必须是纯gmp、mpfr、mpc这种去掉版本号的样子。如果你解压完忘了改名configure会找不到对应的目录然后退回去找系统库结果就是系统库版本太老报错“The GMP library is not available”之类的信息。3.2 关键的configure指令为什么这么写GCC 12.2.0官方推荐的做法是不要直接在源码目录里编译而是新建一个独立的build目录。这样源码目录干净随时可以重新配置也方便日后删除编译产物。我在/opt/src下建一个build-gcc-12.2.0目录mkdir -p /opt/src/build-gcc-12.2.0 cd /opt/src/build-gcc-12.2.0 /opt/src/gcc-12.2.0/configure \ --prefix/usr/local/gcc-12.2.0 \ --enable-languagesc,c \ --disable-multilib \ --enable-bootstrap \ --enable-shared \ --enable-threadsposix \ --enable-checkingrelease \ --with-system-zlib \ --with-default-libstdcxx-abinew一个个拆开讲。--prefix/usr/local/gcc-12.2.0指定安装路径这是整个方案能否独立共存的关键。我不会把GCC装到/usr/local的顶层那样万一想回退就会污染系统的公共目录。装到带版本号的独立目录之后想切换就切换想删就删目录干净利落。--enable-languagesc,c只开启C和C两个前端。如果你是Fortran或Ada用户可以在这里加上fortran但大多数场景完全用不到那么多种语言编译时间会拉长一截。--disable-multilib是禁止生成32位兼容库。在64位系统上GCC默认可能会尝试编译32位版本的libgcc和libstdc如果系统缺少32位的glibc头文件和库就会报错。普通服务器应用根本用不到32位兼容直接关掉能省去大量麻烦。--enable-bootstrap是GCC的一个自我验证机制它要用新编译出来的编译器去重新编译一遍自身确认新的编译器能正常工作。这个过程会增加编译时间但能保证工具链的可靠性生产用途建议开启。如果你对环境干净度有绝对把握也可以通过--disable-bootstrap关掉加速编译但我不建议第一次就没骨气地用这招。--enable-checkingrelease表示只做最低限度的检查不启用那些重度断言这能保证编译出的编译器在优化时快一些。如果按默认的--enable-checkingyes编译出的GCC自检代码很多构建出来的编译器运行时会慢不少。--enable-shared、--enable-threadsposix这俩不用多想保持默认行为即可前者把libgcc和libstdc编成动态库后者确保支持POSIX线程模型。--with-default-libstdcxx-abinew指定libstdc使用新的C11 ABI。如果你跳过了这个选项GCC 12系列默认也用新ABI写出来只是明确意图防止某些发行版配置默认值被改动。配置阶段结束后屏幕会显示一串配置摘要包括配置的目标平台、启用的语言、最后的安装路径。我建议扫一眼--prefix有没有生效再确认checking whether the C compiler works... yes这些关键项都通过了后面再翻车就不是配置这个层面能补救的了。3.3 编译、踩坑和安装所有配置都通过后进入编译阶段# 获取CPU核心数按核数并行编译 make -j$(nproc) # 如果中途出错不要盲目加-j先降级到-j2或者单线程把报错看清楚 make -j2编译过程中通常会看到很多编译进展最长的一段是4个大阶段的bootstrap前前后后会把libstdc编两三遍。如果终端输出长时间卡在某个文件上不动弹先检查是不是内存不足而不是怀疑死机。我的一次真实经历是4核虚拟机、2GB内存make -j4时cc1plus进程直接报virtual memory exhausted后来调整成-j2才顺利编过去。所以-j并不是越大越好内存小的时候多用几个进程反而拖垮系统。编译成功后执行安装sudo make install这一步会把所有编译好的二进制度量放入/usr/local/gcc-12.2.0目录下其中可执行程序在bin/头文件在include/动态库在lib64/。安装完成后先做个自检/usr/local/gcc-12.2.0/bin/gcc --version /usr/local/gcc-12.2.0/bin/g --version如果版本号显示gcc (GCC) 12.2.0说明本位安装还没有被PATH干扰至少在物理这个绝对路径下是正常的。下一步要做的是让全局环境都能识别这套新工具链。4. 升级后为啥还是旧版本PATH、软链接和动态库全解析4.1 最常见的坑bash命令哈希表很多网友反馈的一个现象是明明编译安装成功了执行gcc -v显示的还是旧版本。这个问题我在一开始就遇到过当时也一头雾水。其实有90%的概率是bash的命令哈希表在捣乱。bash会缓存你敲过的命令的完整路径。当你之前执行过/usr/bin/gcc -vbash会把gcc这个命令的路径记在哈希表里。之后就算你改了PATH在同一个终端窗口里敲gccbash仍然会用缓存里的旧路径。解决办法是执行hash -r或者干脆关掉当前终端重新打开一个。重新登录终端后如果还显示旧版本就要看下一个原因PATH的顺序是不是有问题。4.2 配置PATH和环境变量并解释优先级我把新GCC的bin目录放到PATH的最前面确保系统在查找gcc命令时优先找到新的那一版。编辑个人的~/.bashrc追加export PATH/usr/local/gcc-12.2.0/bin:$PATH export LD_LIBRARY_PATH/usr/local/gcc-12.2.0/lib64:$LD_LIBRARY_PATH export MANPATH/usr/local/gcc-12.2.0/share/man:$MANPATH然后source ~/.bashrc。执行which gcc看是否指向/usr/local/gcc-12.2.0/bin/gcc执行gcc -v看版本是否变成12.2.0。需要注意$PATH放在右边意味着新路径优先。如果你写成export PATH$PATH:/usr/local/gcc-12.2.0/bin那系统在匹配时会先从系统路径查找到旧GCC新GCC放在末尾基本上就永远轮不到它。这种细节最容易坑到人同样一段配置换个顺序表现就完全不同。如果只想给某个用户切换在~/.bashrc里改就够了。如果是给整台服务器的所有用户切换更建议在/etc/profile.d/下新建一个gcc-12.2.0.sh文件写入同样的三行export。这样所有用户登录时都会自动加载也更符合Linux的常规管理套路。4.3 别忘了动态库缓存LD_LIBRARY_PATH和ldconfig配合GCC装了但编译程序时提示找不到libstdc.so.6或者运行编译好的程序时报GLIBCXX_3.4.30 not found这个问题的本质是动态链接器没有找到新版GCC的库路径。新版GCC的动态库默认放在/usr/local/gcc-12.2.0/lib64和lib目录里。系统链接器的搜索路径里如果没有这个目录就算gcc命令已经切到了新版本用它编出的程序在运行时也找不到合适的libstdc.so.6。除了用LD_LIBRARY_PATH环境变量做临时的用户级修改更规范的做法是把库路径写进系统的动态链接器配置echo /usr/local/gcc-12.2.0/lib64 | sudo tee /etc/ld.so.conf.d/gcc-12.2.0.conf sudo ldconfigldconfig会重新生成/etc/ld.so.cache。之后执行ldconfig -p | grep libstdc如果看到libstdc.so.6同时指向了系统旧路径和新路径一般来说新路径会排在前面。写不写入系统路径取决于你有没有系统级的需求但就算只给自己用LD_LIBRARY_PATH该配还是要配不然编译的过程可能没问题后面运行产物时就会各种“浮点数例外”“找不到共享库”。4.4 软链接要不要做什么时候做还有一个常见操作是把新GCC的二进制软链到/usr/local/bin下sudo ln -s /usr/local/gcc-12.2.0/bin/gcc /usr/local/bin/gcc sudo ln -s /usr/local/gcc-12.2.0/bin/g /usr/local/bin/g这个操作有没有必要我的建议是除非你有特定构建系统要求调用/usr/local/bin/gcc这个固定路径否则不做也行。做了反而容易和管理员的预期冲突。因为后期安装别的软件时如果它的构建脚本写死了/usr/local/bin/gcc一旦你删掉了这个软链整个系统会出现奇奇怪怪的编译错误。软链接不是不能做但一定要清楚它绕过了PATH配置逻辑属于固定路径优先的操作能不做尽量不做。如果你在某个项目里需要临时用新版GCC编译最优雅的方式是在项目的构建脚本里显式指定CC/usr/local/gcc-12.2.0/bin/gcc CXX/usr/local/gcc-12.2.0/bin/g或者用cmake时传-DCMAKE_C_COMPILER/usr/local/gcc-12.2.0/bin/gcc -DCMAKE_CXX_COMPILER/usr/local/gcc-12.2.0/bin/g。这样连改全局PATH都不需要一个项目一套编译器配置互不打扰比任何软链接方案都干净。5. 常见问题与排查技巧实录5.1 编译常见错误速查表我把实际操作中遇到过的问题按现象整理成一张速查表方便按图索骥。报错现象可能原因解决办法The GMP library is not available系统缺少GMP或源码目录下的gmp没放对位置把GMP源码解压到GCC源码目录并去掉版本号或者用yum/apt装系统GMP开发包gmp.h: No such file or directory缺GMP头文件安装libgmp-dev或gmp-devel确保头文件路径被找到mpfr.h: No such file or directory缺MPFR头文件同上针对mpfr开发包安装cc1plus: fatal error: Killed (signal 9)内存不足进程被内核杀掉降低并行度如make -j2增加swapconfigure: error: C compiler cannot create executables系统的g或相关头文件缺失先确认系统的g能用再检查build-essential是否完整make: *** No rule to make target ...源码目录被污染新建干净的build目录重新跑configure/usr/lib64/libstdc.so.6: version GLIBCXX_3.4.30 not found运行时找不到新版libstdc配置LD_LIBRARY_PATH或者运行ldconfiggcc -v显示旧版本bash哈希缓存或PATH顺序不对执行hash -r确认export PATH/usr/local/gcc-12.2.0/bin:$PATH这张表里的前几条在离线环境下出现的概率尤其高。原因很简单很多离线系统根本没装gmp、mpfr、mpc的开发包configure时直接就不认账。控制台提示又往往比较委婉第一次看到类似error: Building GCC requires GMP 4.2, MPFR 3.1.0, MPC 0.8.0这种信息很容易一头雾水。现在看到这种字符串直接想GCC源码下的依赖有没有放好八成就对了。5.2 编译中途卡住和内存不足的处理思路GCC编译到中后期尤其是libstdc和libbacktrace这些大模块时会冒出大量编译任务。如果你的机器内存比较紧张编译进程并发的峰值能把内存吃满Linux内核OOM killer会挑一个进程杀掉最常见的受害者就是cc1plus。表现就是终端窗口突然刷出一个Killed整个构建进程就直接退出了。排查时可以开个新终端实时观察内存和交换分区free -h如果可用内存低到几百兆而且swap也用得七七八八就要果断降低并行度。我个人的经验比例是4GB内存最多跑-j28GB内存最多跑-j416GB以上再放开到-j$(nproc)。别迷信-j$(nproc)一定最快内存不够时多开线程反而带来额外的上下文切换整体更慢。还有一种比较少见的情况磁盘写入太慢。编译时的临时文件巨多如果用机械硬盘CPU再快也在等IO。这种情况下make输出可能长时间停在某个文件名上不动iotop能看到大量磁盘写。解决办法是给build目录换到SSD或tmpfs上但tmpfs占的是内存要先确认内存富余。一般正常服务器不存在这个问题但如果你在老旧台式机上跑值得留个心眼。5.3 确认所装GCC真的生效一整套验证命令安装并配置完之后我习惯用一套命令快速验证整个工具链可用which gcc which g gcc -v g -v echo int main(){return 0;} | g -x c - -o /tmp/test_gcc /tmp/test_gcc echo OK第二个命令把一个纯标准库的空程序编译成可执行文件并运行它。这一条能把动、静态链接问题都带出来。只要g能找到头文件、链接器能找到libstdc就不会报错。如果还想验证libstdc是不是新版本可以执行strings /usr/local/gcc-12.2.0/lib64/libstdc.so.6 | grep GLIBCXX输出里应该能看到GLIBCXX_3.4.30或更高的符号版本。看到这个说明你的新GCC配套的标准库是对的后面编译C20代码时才算真正有底气。还可以顺便用小段C20代码验证特性支持echo #include vector #include ranges #include iostream int main(){ std::vectorint v{1,2,3}; for(auto x : v | std::views::reverse) std::cout x ; } | g -stdc20 -x c - -o /tmp/test_ranges /tmp/test_ranges输出是3 2 1说明C20的ranges特性已经可用。如果这里编译不过说明某些特性支持还没达标需要去查GCC的features页面。6. 从编译步骤上升到工具链管理思维编译安装GCC 12.2.0这件事表面看就是一条configure加make的流水线但真正做完一遍你会发现它其实是一个很好的工具链管理训练。我个人的体会是版本独立目录加显式环境配置比无脑替换系统GCC要稳妥得多。有些教程会教你把新GCC装到/usr顶层覆盖掉系统自带的版本。这样做的后果是很多用旧ABI编译的系统自带软件比如一些动态链接库、内核模块相关的头文件匹配关系可能会被打乱。尤其是一些发行版的glibc其实是和GCC版本紧密耦合的盲目覆盖会让后期维护非常难受。我现在在服务器上管理多个GCC版本的做法是这样的每个版本装在自己独立的/opt/gcc/或者/usr/local/下的带版本号目录里比如/opt/gcc/12.2.0、/opt/gcc/13.2.0、/opt/gcc/14.1.0。用的时候在项目的构建配置文件里直接指定CC和CXX或者写一个小的环境切换脚本把PATH、LD_LIBRARY_PATH一次性设置好。没有特殊情况不动系统的全局默认GCC。如果只是想临时用某个版本编译一个第三方库我给一个更轻量的操作安装完GCC后直接用完整路径调用。很多构建系统允许传编译器路径参数比如./configure CC/usr/local/gcc-12.2.0/bin/gcc CXX/usr/local/gcc-12.2.0/bin/g这样环境变量完全不用改做完一个项目就完事整个过程和系统现有工具链零冲突。再说一个和MSYS2相关的应用场景。Windows上想用比较新的GCC最常见的方案是装MSYS2用pacman安装mingw-w64-x86_64-gcc。其实Windows平台上MSYS2的方案和Linux源码编译的逻辑是相通的。MSYS2的包管理器能给你提供大量预先编译好的GCC工具链适合做Windows本地开发但如果需要定制某些编译选项或者支持特定的目标平台源码编译这套思路依然适用。只是 Windows 下的编译环境和 Linux 差异较大我更推荐普通使用者直接用发布好的二进制工具链不需要自己折腾源码编译。回到Linux。无论你是用CentOS、Ubuntu、Debian还是其他发行版编译安装GCC的底子逻辑都一样除非碰到非常特殊的CPU架构编译选项只需要微调。比如在ARM架构的机器上--prefix这些配置思路完全一致只是编译时间会因CPU性能有所不同。掌握一次以后装任何GCC版本都不慌了。最后再分享一个小技巧。GCC编译源码配置花的时间很少90%的时间都耗在编译和链接上。如果编译到一半失败了修好问题后再次执行make它不会从头再来而是只编译未完成的部分。很多人担心编译失败就要全盘重来其实GNU的构建系统早就帮你做好了增量处理。同理如果configure阶段改了某个参数想重来也只需要在干净目录下重新跑一次不必删除整个源码仓库。好了工具链的事就掰扯到这里希望这篇文章能让你把GCC 12.2.0的编译安装从照着抄命令变成理解着去配置。真正跑通一次你会有一种给服务器换了一套更顺手工具箱的踏实感。
返回列表