踐)
1. 為什么Ubuntu上的GCC版本切換是個(gè)繞不開的坎在Ubuntu上做開發(fā)早晚會(huì)遇到這么一件事項(xiàng)目代碼在同事機(jī)器上編譯得好好的拉到自己這邊make一跑就紅一片報(bào)錯(cuò)信息看著像是語法問題實(shí)際是編譯器版本不對(duì)。Ubuntu、GCC、版本這三個(gè)詞湊在一起幾乎就是C/C工程師日常最常打交道的組合。系統(tǒng)自帶的GCC一般是跟著發(fā)行版走的Ubuntu 20.04默認(rèn)給的是GCC 922.04默認(rèn)GCC 1124.04默認(rèn)GCC 13這個(gè)默認(rèn)版本你不動(dòng)它就在那兒但你一旦要編譯內(nèi)核模塊、跑老項(xiàng)目、適配某個(gè)深度學(xué)習(xí)框架或者用CUDA做GPU編程就會(huì)發(fā)現(xiàn)默認(rèn)版本根本不夠用。這篇文章我想聊的就是怎么在Ubuntu上干凈利落地切換GCC版本包括用系統(tǒng)工具切換、手動(dòng)編譯多版本共存、切換完版本沒變的排查思路以及一些工程里踩過的坑。內(nèi)容適合剛接觸Linux編譯環(huán)境的同學(xué)也適合已經(jīng)會(huì)敲命令、但每次切版本都要搜一遍的人。我會(huì)盡量把每個(gè)命令背后的邏輯講清楚而不是只丟一句這樣就能切。1.1 版本錯(cuò)配到底會(huì)引發(fā)哪些連鎖反應(yīng)很多人以為編譯器版本不對(duì)頂多報(bào)個(gè)錯(cuò)改改代碼就行了實(shí)際上影響比想象中廣。最直接的是C標(biāo)準(zhǔn)支持差異GCC 9默認(rèn)按C14編譯GCC 11之后默認(rèn)跳到C17GCC 13的支持范圍又不一樣。如果你的代碼寫了std::optional或者結(jié)構(gòu)化綁定在GCC 9上直接編譯失敗而-stdc17加了也不一定全支持。更深一層的影響在ABI和運(yùn)行時(shí)庫。GCC的大版本之間libstdc的符號(hào)版本會(huì)變。你在一臺(tái)機(jī)器上用GCC 13編出來的動(dòng)態(tài)庫拿到只有GCC 9運(yùn)行庫的機(jī)器上跑很可能報(bào)GLIBCXX_3.4.29 not found。這不是代碼問題是運(yùn)行庫版本對(duì)不上。所以切GCC版本不只是換個(gè)編譯器還得留意目標(biāo)環(huán)境的運(yùn)行庫。第三層影響是內(nèi)核模塊編譯。Ubuntu裝內(nèi)核頭文件后編譯模塊必須用與當(dāng)前內(nèi)核匹配的GCC版本否則內(nèi)核會(huì)拒絕加載模塊報(bào)module verification failed或者直接版本魔法不匹配。這種場景下你沒法隨便選版本系統(tǒng)內(nèi)核是什么GCC編的就得用什么。注意切換GCC版本前先想清楚是編譯用戶態(tài)程序還是內(nèi)核模塊兩者的約束完全不同。1.2 常見的幾種切換需求場景我梳理過自己和周圍人遇到的情況切換GCC大致集中在幾類場景。一類是新項(xiàng)目要新標(biāo)準(zhǔn)老系統(tǒng)默認(rèn)GCC太舊需要裝個(gè)新版本編現(xiàn)代C代碼。另一類正好相反老項(xiàng)目依賴舊GCC的行為新系統(tǒng)默認(rèn)GCC太新編譯報(bào)一堆棄用警告甚至錯(cuò)誤得往回切。第三類是CUDA開發(fā)CUDA Toolkit對(duì)宿主GCC版本有上限要求比如早期CUDA 11.x只支持到GCC 10或11你系統(tǒng)默認(rèn)GCC 13就跑不了nvcc。第四類是交叉編譯或嵌入式工具鏈工具鏈自帶一個(gè)GCC和系統(tǒng)GCC并行存在經(jīng)常搞混。還有一類容易被忽略容器和CI環(huán)境。Docker鏡像里裝的GCC版本決定了構(gòu)建結(jié)果本地切了版本CI沒切編譯產(chǎn)物不一致排查起來很費(fèi)時(shí)間。所以版本管理不只是本機(jī)的事要貫穿開發(fā)、構(gòu)建、部署整條鏈。場景明確之后選哪種切換方式就有依據(jù)了。接下來先搞清楚Ubuntu下GCC到底是怎么被系統(tǒng)管理的。2. 先搞清楚Ubuntu下GCC的版本管理邏輯動(dòng)手之前得先明白一件事Ubuntu上gcc這個(gè)名字本質(zhì)是個(gè)符號(hào)鏈接指向具體的版本可執(zhí)行文件。你敲gcc系統(tǒng)去/usr/bin/gcc找而這個(gè)路徑通常鏈接到/etc/alternatives/gcc再指向/usr/bin/gcc-xx。這一層套一層的鏈接機(jī)制就是update-alternatives在管的事。理解這個(gè)鏈條后面切換和排查都會(huì)順很多。2.1 系統(tǒng)里到底裝了幾個(gè)GCC第一步永遠(yuǎn)是先看清現(xiàn)狀。我習(xí)慣用這幾條命令確認(rèn)# 查看當(dāng)前默認(rèn)gcc版本和路徑 gcc --version which gcc readlink -f $(which gcc) # 列出系統(tǒng)里所有已安裝的gcc版本 ls /usr/bin/gcc-* ls /usr/bin/g-* # 查看alternatives里注冊(cè)了哪些gcc update-alternatives --list gcc update-alternatives --list gls /usr/bin/gcc-*會(huì)列出像gcc-9、gcc-11、gcc-12、gcc-13這樣的文件。如果只有g(shù)cc沒有帶版本號(hào)的說明系統(tǒng)沒裝多版本需要先apt裝。update-alternatives --list gcc則告訴你哪些版本已經(jīng)注冊(cè)到alternatives系統(tǒng)里只有注冊(cè)過的才能用--config切換。這里有個(gè)常見誤區(qū)裝了兩個(gè)版本的GCC并不代表alternatives里自動(dòng)就有兩個(gè)候選。apt安裝某些版本時(shí)不會(huì)自動(dòng)注冊(cè)alternatives需要手動(dòng)--install這是后面切換失敗的頭號(hào)原因。2.2 update-alternatives的軟鏈接機(jī)制update-alternatives本質(zhì)是個(gè)符號(hào)鏈接管理器。它維護(hù)三樣?xùn)|西一組候選路徑、每個(gè)候選的優(yōu)先級(jí)、當(dāng)前指向哪個(gè)。當(dāng)你執(zhí)行update-alternatives --config gcc它把/usr/bin/gcc指向你選的候選。關(guān)鍵點(diǎn)在于優(yōu)先級(jí)這個(gè)參數(shù)。注冊(cè)的時(shí)候要給它一個(gè)數(shù)字sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 130后面的100和130就是優(yōu)先級(jí)。數(shù)字越大優(yōu)先級(jí)越高在非交互模式下比如腳本里自動(dòng)切換會(huì)選優(yōu)先級(jí)高的那個(gè)。手工--config選擇時(shí)優(yōu)先級(jí)只影響默認(rèn)建議你選哪個(gè)就是哪個(gè)。我一般按版本號(hào)乘10來設(shè)優(yōu)先級(jí)直觀又好記。同一套機(jī)制要同時(shí)管gcc、g、gcov、gcc-ar、gcc-nm等一大家子因?yàn)樗鼈儽仨毎姹疽恢路駝t編譯鏈接階段會(huì)出現(xiàn)C和C混版的詭異問題。2.3 手動(dòng)編譯的GCC和apt裝的GCC區(qū)別apt install gcc-12裝的GCC放在/usr/bin走系統(tǒng)alternatives管理。手動(dòng)./configure --prefix/opt/gcc-13.2.0編譯的GCC整套東西都在/opt/gcc-13.2.0/bin下不碰系統(tǒng)alternatives需要你自己通過PATH或者全路徑調(diào)用。兩者最大區(qū)別是隔離性。apt版本接管方便但和系統(tǒng)綁定較深手動(dòng)編譯版本獨(dú)立干凈但編譯耗時(shí)長半小時(shí)到兩小時(shí)不等且需要自己處理運(yùn)行時(shí)庫路徑LD_LIBRARY_PATH或 rpath。選哪種取決于需求臨時(shí)切換用apt加alternatives最省事要長期多版本共存、且不希望動(dòng)系統(tǒng)默認(rèn)就手動(dòng)編譯到獨(dú)立目錄。提示手動(dòng)編譯的GCC默認(rèn)不注冊(cè)alternatives想讓它進(jìn)alternatives也可以--install時(shí)把路徑換成/opt/gcc-13.2.0/bin/gcc即可但要注意其運(yùn)行庫路徑問題。3. 用update-alternatives切換GCC版本的完整步驟這是我日常最推薦的方式快、干凈、可回滾。前提是目標(biāo)版本已經(jīng)通過apt裝好。下面按完整流程走一遍以從系統(tǒng)默認(rèn)GCC 11切到GCC 13為例。3.1 安裝需要的多版本GCCUbuntu官方倉庫里的GCC版本有限一般覆蓋幾個(gè)相鄰大版本。先更新索引再裝sudo apt update # 假設(shè)要裝12和13 sudo apt install -y gcc-12 g-12 gcc-13 g-13如果官方倉庫里沒有你要的版本比如想裝更新的GCC 14需要加ubuntu-toolchain-r/test這個(gè)PPA。命令大致是sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt update sudo apt install -y gcc-14 g-14裝完之后立刻用gcc-13 --version驗(yàn)證別急著切換。如果這一步報(bào)command not found說明包沒裝上或者PATH有問題先解決這個(gè)再往下走。我遇到過一次apt install gcc-13提示找不到包原因是系統(tǒng)版本太老源里根本沒有13。這種情況要么升級(jí)系統(tǒng)要么老老實(shí)實(shí)手動(dòng)編譯。3.2 配置alternatives鏈接裝好之后把新版本注冊(cè)進(jìn)alternatives。注意要成對(duì)注冊(cè)gcc和gg千萬別落下# 注冊(cè)gcc sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 130 # 注冊(cè)g sudo update-alternatives --install /usr/bin/g g /usr/bin/g-11 110 sudo update-alternatives --install /usr/bin/g g /usr/bin/g-13 130然后交互式選擇sudo update-alternatives --config gcc sudo update-alternatives --config g會(huì)出來一個(gè)列表輸入選擇編號(hào)回車。想一條命令搞定、不進(jìn)交互可以用sudo update-alternatives --set gcc /usr/bin/gcc-13 sudo update-alternatives --set g /usr/bin/g-13腳本化部署時(shí)我更傾向--set省得在CI里卡在交互提示。順便把常用的輔助工具也處理一下避免工具鏈混版for tool in gcov gcc-ar gcc-nm gcc-ranlib; do sudo update-alternatives --install /usr/bin/$tool $tool /usr/bin/${tool}-13 130 sudo update-alternatives --install /usr/bin/$tool $tool /usr/bin/${tool}-11 110 done3.3 驗(yàn)證與回滾切完必須驗(yàn)證別只看一條命令gcc --version g --version readlink -f $(which gcc) echo int main(){return 0;} /tmp/t.c gcc /tmp/t.c -o /tmp/t /tmp/t echo compile okgcc --version看主版本readlink -f看最終鏈接目標(biāo)最后真編一個(gè)小程序確認(rèn)能用。三樣都對(duì)上才算切換成功?;貪L很簡單重新--config選回舊版本或者sudo update-alternatives --set gcc /usr/bin/gcc-11 sudo update-alternatives --set g /usr/bin/g-11alternatives的好處就在這里隨便切切錯(cuò)了立刻切回來不會(huì)破壞系統(tǒng)。真要徹底刪掉某個(gè)候選用--removesudo update-alternatives --remove gcc /usr/bin/gcc-13以上是前半部分的思路和操作。下面繼續(xù)把手動(dòng)編譯多版本共存、切換后版本沒變的排查、工程實(shí)踐技巧和踩坑記錄補(bǔ)完。