錯(cuò)D8021:MSVC與GCC參數(shù)沖突解決指南)
在 Cygwin64 終端里敲下make等了半天沒看到編譯進(jìn)度反而被一行c1: 命令行 error D8021: 無效的數(shù)值參數(shù) “/Wfatal-errors“整懵了。如果你正在用 YOLOv3 相關(guān)源碼嘗試生成marknet.exe這個(gè)報(bào)錯(cuò)十有八九會找上門。別急著去改參數(shù)這個(gè)錯(cuò)誤的真正含義是你的 make 調(diào)用的編譯器根本不是 Cygwin 里的 GCC而是 Windows 的 MSVC 前端。本文從報(bào)錯(cuò)機(jī)制、排查命令、修復(fù)方案到后續(xù)暗坑一次性講清楚。1. D8021 不是參數(shù)拼錯(cuò)而是工具鏈跑錯(cuò)片場1.1c1: 命令行 error D8021到底在說什么先拆開看這條報(bào)錯(cuò)。c1是 Windows 平臺下 C/C 編譯器的前端程序也就是負(fù)責(zé)處理命令行參數(shù)、預(yù)處理、語法分析和代碼生成的前端。它和 GNU 編譯器家族完全不是一套體系命令行參數(shù)的風(fēng)格也完全不同。GCC 用-Wfatal-errors表示“遇到第一個(gè)錯(cuò)誤立即停止編譯”而 MSVC 家族的參數(shù)通常用/開頭比如/W4、/WX、/O2。當(dāng)你把 GCC 風(fēng)格的-Wfatal-errors原封不動(dòng)傳給 MSVC 時(shí)c1 會嘗試把它當(dāng)作自己的參數(shù)來解析結(jié)果發(fā)現(xiàn)Wfatal-errors既不是一個(gè)合法的警告級別也不是一個(gè)數(shù)值參數(shù)于是拋出D8021。D8021對應(yīng)的文字就是“無效的數(shù)值參數(shù)”意思是“你給我傳了一個(gè)我不認(rèn)識、也沒法解析的開關(guān)”。很多人在這一步會去搜索引擎查“D8021”然后開始懷疑是不是參數(shù)拼錯(cuò)了。實(shí)際上參數(shù)沒有拼錯(cuò)參數(shù)在 GCC 下是合法且有用的問題根本在于編譯器認(rèn)錯(cuò)了。就好比你給一個(gè)只會中文的同事發(fā)了一份英文郵件對方看不懂但他的回復(fù)不是“我不認(rèn)識英文”而是“你這封郵件格式不對”——你改郵件格式是沒用的得先確認(rèn)收件人是不是那位同事。1.2 為什么 YOLOv3 的 Makefile 會傳出這個(gè)參數(shù)YOLOv3 的實(shí)現(xiàn)代碼里Makefile 默認(rèn)是為 Linux 下的 GCC 寫的。它會在CFLAGS里加入一串 GNU 編譯器特有的優(yōu)化和警告選項(xiàng)除了-Wfatal-errors還經(jīng)常能看到-Wno-unused-result、-Wno-unknown-pragmas、-Wno-maybe-uninitialized、-mavx、-mfma這些。這些選項(xiàng)在 GCC 下沒有任何問題但一旦編譯器被替換成 MSVC任何一個(gè)都會引發(fā)類似 D8021 的報(bào)錯(cuò)。那為什么 Cygwin 環(huán)境下會跑出 MSVC這里有個(gè)關(guān)鍵背景Cygwin 本身只是提供了一個(gè) Unix 模擬環(huán)境它不負(fù)責(zé)決定 make 使用哪個(gè)編譯器。make 在運(yùn)行時(shí)會使用環(huán)境變量里的CC和CXX來找到 C/C 編譯器。如果你在啟動(dòng) Cygwin 之前打開過 Visual Studio 的開發(fā)者命令提示符或者把 Visual Studio 的編譯工具路徑加到了系統(tǒng) PATH 里又或者某個(gè)腳本往你的~/.bashrc里寫了export CCcl那 make 拿到的CC就是 MSVC 的cl.exe它自然就會調(diào)用 c1。還有一種情況是某些 Windows 移植版的 Makefile 內(nèi)部寫了CC cl。網(wǎng)上流傳的 YOLOv3 源碼版本很多有些作者為了能在 Windows 上直接用 Makefile 編譯會做一層兼容但兼容做得不徹底結(jié)果就是把編譯器硬編碼成了 MSVC卻忘了把 GCC 的參數(shù)清干凈。這一來make一執(zhí)行c1 就成了編譯器-Wfatal-errors就成了非法參數(shù)。2. 三步揪出真正在干活的編譯器2.1 檢查環(huán)境變量與命令位置不要一上來就改 Makefile先花三十秒確認(rèn)當(dāng)前環(huán)境里 make 認(rèn)為的編譯器是誰。在 Cygwin 終端依次執(zhí)行下面幾條命令echo CC[$CC] CXX[$CXX] type -a cc gcc cl 2/dev/null第一條命令會直接顯示環(huán)境變量里是否已經(jīng)預(yù)設(shè)了編譯器。如果輸出類似CC[cl]那問題就浮出水面了。第二條命令會列出所有同名的命令路徑。正常情況下cc會指向/usr/bin/ccgcc會指向/usr/bin/gcc而cl要么不存在要么出現(xiàn)在某個(gè) Windows SDK 路徑下。如果cl出現(xiàn)在輸出里說明當(dāng)前 Cygwin 進(jìn)程已經(jīng)繼承了 Visual Studio 工具鏈的環(huán)境變量。哪怕你沒有手動(dòng)設(shè)置只要系統(tǒng) PATH 里有 MSVC 的目錄某些版本的 make 也可能通過cc找到那邊去。不過更常見的還是環(huán)境變量CC被寫死。2.2 用make -n查看真實(shí)編譯命令光看環(huán)境變量還不夠因?yàn)?Makefile 內(nèi)部可能再次覆蓋。最可靠的辦法是讓 make 把將要執(zhí)行的命令打印出來但不真正執(zhí)行。這就是make -n的作用make clean make -n 21 | grep -E cc|gcc|cl|Wfatal-errors | head -20make clean是為了清掉可能存在的舊對象文件避免干擾判斷。make -n會輸出完整的編譯命令然后我們用 grep 過濾關(guān)鍵字。如果你能看到類似這樣的輸出cl /nologo /O2 /Wfatal-errors ...那就可以百分百確定make 調(diào)用的就是 MSVC。如果你看到的是gcc -Iinclude -Wall -Wfatal-errors ...那說明當(dāng)前環(huán)境變量和 Makefile 的設(shè)置其實(shí)是 GCC此時(shí)若還報(bào) D8021就要懷疑是不是有子目錄里的另外一個(gè) Makefile 在搗亂或者你當(dāng)前所在的目錄根本不是主 Makefile 所在目錄。2.3 審查 Makefile 中的編譯器賦值確定是 MSVC 在干活之后再回頭看看 Makefile 里到底哪里把CC設(shè)成了cl。執(zhí)行g(shù)rep -nE ^(CC|CXX|CFLAGS|CPPFLAGS|LDFLAGS) Makefile grep -nE CC\s* Makefile重點(diǎn)看開頭的變量賦值位置。如果在 Makefile 頂部附近看到CC cl或者某個(gè)條件分支里寫著ifeq ($(OS),Windows_NT) CC cl endif那基本就是它了。有些版本還可能在子模塊里調(diào)用另一個(gè).mk文件比如include config.mk你也得把config.mk翻出來看看。另外順手查一下用戶目錄下的配置文件grep -n CC ~/.bashrc ~/.bash_profile ~/.profile 2/dev/null如果看到export CCcl或者export CC/c/Program Files/.../cl.exe那就說明是啟動(dòng)腳本污染了環(huán)境變量光改 Makefile 還不夠得先把這個(gè)環(huán)境變量清掉。3. 讓 marknet.exe 的編譯命令回到 GCC 軌道3.1 最快修復(fù)make 命令行覆蓋 CC一條命令立刻驗(yàn)證根因make CCgccGNU make 允許在命令行通過CCgcc覆蓋 Makefile 里的CC變量除非 Makefile 里明確寫了override CC...。執(zhí)行這條命令后如果之前一直報(bào) D8021 的地方不再報(bào)錯(cuò)說明根因就是編譯器被錯(cuò)誤指定成了 MSVC。這是最推薦的第一反應(yīng)因?yàn)樗恍枰娜魏挝募?yàn)證完再?zèng)Q定下一步。如果用了make CCgcc之后依然報(bào) D8021那說明 Makefile 里可能有override CCcl或者有某個(gè) include 文件里設(shè)置了cl并且用了override。這時(shí)候再上編輯工具強(qiáng)行處理。3.2 修改 Makefile把編譯器寫死成 GCC確認(rèn) GCC 能編過之后建議直接在 Makefile 里做一次干凈利落的修改。打開 Makefile找到這一行CC cl改成CC gcc如果找不到說明是用環(huán)境變量傳入的你也可以在 Makefile 靠上的位置新增一行override CC gccoverride可以確保命令行或環(huán)境變量都無法再覆蓋這個(gè)值適合當(dāng)前項(xiàng)目里環(huán)境變量污染嚴(yán)重的情況。改完之后-Wfatal-errors這個(gè)參數(shù)就別動(dòng)了它是 GCC 完全支持的選項(xiàng)保留它有助于出現(xiàn)編譯錯(cuò)誤時(shí)快速停下方便定位問題。同時(shí)檢查CFLAGS里有沒有其他 MSVC 特征明顯的選項(xiàng)比如/MD、/EHsc、/nologo。如果混著這種東西GCC 也會報(bào)錯(cuò)。一個(gè)經(jīng)驗(yàn)是/開頭的選項(xiàng)在 GCC 下幾乎都會出問題統(tǒng)一刪掉。3.3 清理環(huán)境變量防止下次再翻車如果前面在~/.bashrc里發(fā)現(xiàn)了export CCcl光改 Makefile 夠不夠不夠因?yàn)槟鞘侨治廴?。下次新開終端環(huán)境變量又會把CC指回 cl被override擋住還好沒擋住就是另一場噩夢。干脆把那一行注釋掉或者去系統(tǒng)環(huán)境變量里刪掉CC。清完以后在當(dāng)前終端里執(zhí)行unset CC unset CXX export CCgcc export CXXg再跑一次make試試。我個(gè)人建議把export CCgcc這行加進(jìn)~/.bashrc因?yàn)槭羌?Cygwin 環(huán)境你大概率不想讓其他 Windows 工具鏈來攪局。這樣以后每次打開 Cygwin 終端make 都會穩(wěn)定使用 GCC。3.4 備選方案繞過 Makefile 用 CMake如果當(dāng)前源碼里存在CMakeLists.txt另一個(gè)干凈路線是直接用 CMake 重新配置工程讓它無論如何都使用 GCCmkdir -p build cd build cmake -G Unix Makefiles -DCMAKE_C_COMPILERgcc -DCMAKE_CXX_COMPILERg .. make-G Unix Makefiles是讓 CMake 在 Windows 平臺借用 Cygwin/MSYS 環(huán)境生成 Unix 風(fēng)格的 Makefile-DCMAKE_C_COMPILERgcc則顯式指定編譯器。這個(gè)方案的好處是 CMake 會根據(jù)編譯器自動(dòng)過濾掉不支持的參數(shù)較新的版本不會把-Wfatal-errors傳給 MSVC。缺點(diǎn)是你得確認(rèn)這個(gè)源碼版本提供了完整的 CMake 配置否則還得自己補(bǔ)依賴項(xiàng)。4. 過了編譯關(guān)還有鏈接與運(yùn)行的暗坑4.1 來自 Unix 的依賴項(xiàng)缺失D8021 只是第一關(guān)過了編譯器這關(guān)后面還有鏈接關(guān)。YOLOv3 的實(shí)現(xiàn)通常依賴 pthread 和 OpenCV。Cygwin 默認(rèn)安裝的編譯器版本未必帶這些庫鏈接時(shí)會報(bào)找不到pthread_create或者找不到cv::imread、cv::Mat之類的符號。此時(shí)需要回到 Cygwin 安裝程序額外安裝這些包gcc、g、makepkg-configlibopencv-devellibpthread-stubs0-dev如果已經(jīng)裝了apt-cyg可以直接執(zhí)行apt-cyg install gcc g make pkg-config libopencv-devel libpthread-stubs0-dev注意Cygwin 的 OpenCV 包可能叫l(wèi)ibopencv-devel不同鏡像略有差異安裝前先用apt-cyg show libopencv看一下可用的包名。4.2 舊對象文件導(dǎo)致的殘余錯(cuò)誤如果你在修復(fù) D8021 之前已經(jīng)用 MSVC 編譯出過一部分.o文件那這些二進(jìn)制對象是 MSVC 生成的格式和 GCC 生成的目標(biāo)文件不兼容。即使你把編譯器切回 GCC鏈接時(shí)也可能出現(xiàn)“文件格式無法識別”或“未解析的外部符號”。遇到這種情況優(yōu)先做一次徹底清理make clean make CCgcc -j4make clean會刪除 Makefile 規(guī)定的中間文件。如果clean不徹底可以手動(dòng)刪除所有.o和.exefind . -name *.o -delete rm -f *.exe之后再編譯就能確保所有目標(biāo)文件都是 GCC 生成避免新舊對象文件混用帶來的怪異鏈接錯(cuò)誤。4.3 運(yùn)行 marknet.exe 時(shí)的 DLL 依賴就算編譯順利生成marknet.exe雙擊運(yùn)行也可能閃退或提示找不到入口點(diǎn)。罪魁禍?zhǔn)淄ǔJ侨笔?DLL。Cygwin 下編譯出的 exe 依賴 Cygwin 的運(yùn)行時(shí)動(dòng)態(tài)庫常見的有cygwin1.dllcyggcc_s-1.dllcygstdc-6.dllcygopencv_core4xx.dll可以用cygcheck查看可執(zhí)行文件的依賴清單cygcheck ./marknet.exe它會列出所有需要的 DLL 以及對應(yīng)的來源路徑。有兩種處理方法把 Cygwin 的安裝目錄下的bin目錄通常是C:\cygwin64\bin手動(dòng)加進(jìn) Windows 系統(tǒng) PATH。把上述 DLL 從 Cygwin 的bin目錄復(fù)制到marknet.exe所在目錄和 exe 放在同個(gè)文件夾。第二種方式更適合分發(fā)拷貝出來的 exe 文件夾可以直接壓縮給別人用不需要對方安裝 Cygwin。注意cygopencv系列 DLL 體積不小復(fù)制前先確認(rèn)需要哪些避免塞一大包進(jìn)去。4.4 經(jīng)驗(yàn)談Windows 下編譯這類項(xiàng)目的三條路線這次修完 D8021 之后我建議你重新審視一下編譯環(huán)境的選擇。Cygwin 思路本身沒問題但它生成的 exe 依賴 Cygwin 模擬層性能、部署成本都不是最優(yōu)。如果你后續(xù)要做 Windows 原生開發(fā)有兩條更省心的路線。第一條是用 WSL2 編譯。里頭的 Ubuntu 環(huán)境接近原生 LinuxYOLOv3 的 Makefile 幾乎不用改直接make就完事。但生成的是 Linux 可執(zhí)行文件不是 Windows exe所以這條路線適合做訓(xùn)練、推理驗(yàn)證不適合交付給 Windows 用戶。第二條是用 MSVC 加 CMake生成 Visual Studio 工程編出來的是原生 Windows exe。這條路需要處理 OpenCV 和 pthread 的 Windows 版本配置成本稍高但好處是最終產(chǎn)物能直接發(fā)給任何 Windows 電腦運(yùn)行不需要額外 DLL。如果你只是想把 YOLOv3 跑通、看看檢測效果在 Cygwin 里把環(huán)境調(diào)通也完全夠用。我自己的習(xí)慣是遇到類似D8021這種“編譯器報(bào)錯(cuò)”永遠(yuǎn)先問三個(gè)問題——make 用的編譯器是誰Makefile 里寫死的編譯器是誰環(huán)境變量里有沒有殘留這三個(gè)問題查完八成的問題已經(jīng)能定位了。剩下兩成多半出在依賴庫和 PATH 上。把排查順序理順之后這種報(bào)錯(cuò)基本三五分鐘就能解決而且再也不容易踩第二次。