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