
簡介DJGPP 2.04 是一套面向 DOS 環(huán)境的開源 C/C 開發(fā)工具鏈主要解決在老式 PC、虛擬機(jī)或嵌入式場景中源碼編譯與程序構(gòu)建的問題適合復(fù)古計(jì)算愛好者、系統(tǒng)原理學(xué)習(xí)者以及需要在 DOS 下開發(fā)游戲或系統(tǒng)級軟件的開發(fā)者。該版本由 GNU 工具移植而來整合了 GCC 編譯器、匯編器、鏈接器、GNU Make、調(diào)試器 GDB、庫管理器及大量標(biāo)準(zhǔn)頭文件和庫文件可在沒有現(xiàn)代操作系統(tǒng)的條件下完成從編輯、編譯到鏈接的完整開發(fā)流程。整套壓縮包共 372 個文件以 h 頭文件、exe 可執(zhí)行程序、info 說明文檔以及 djl、lib、bat 等輔助文件為主壓縮后約 6.95MB體積緊湊便于在早期硬件環(huán)境部署。資源與《Linux0.01內(nèi)核分析與操作系統(tǒng)設(shè)計(jì)配書光盤》教學(xué)場景緊密相關(guān)同時附帶 man 手冊頁與示例代碼幫助讀者理解早期操作系統(tǒng)內(nèi)核、底層啟動流程及 DOS 平臺編程方式。目前已有 405 人學(xué)習(xí)下載對研究編譯工具發(fā)展史和操作系統(tǒng)教學(xué)均有獨(dú)特價值。1. DJGPP 2.04 是什么一個讓 32 位 C/C 在 DOS 里跑起來的編譯器套件DJGPP 2.04 這個名字在今天的開發(fā)者眼里很陌生但只要你碰 DOS 下的復(fù)古游戲、老式工業(yè)電腦或者想用現(xiàn)代 C 標(biāo)準(zhǔn)給老系統(tǒng)寫點(diǎn)正經(jīng)工具它就繞不開。它做的事是把 GCC、make、gdb 和 binutils 整個搬進(jìn) DOS 環(huán)境讓你在命令行下敲一條 gcc 命令就編譯出能在純 DOS 里運(yùn)行的 32 位保護(hù)模式程序而不是 Turbo C 那種只能摸 640KB 內(nèi)存的 16 位產(chǎn)物。DJGPP 2.04 是這一系列的最后一個主流發(fā)布解決了一批老內(nèi)核的兼容性問題補(bǔ)齊了 C 標(biāo)準(zhǔn)庫在 DOS 下的可用度。適合它的場景很明確老硬件上的實(shí)用工具、DOS 演示和游戲開發(fā)、操作系統(tǒng)的教學(xué)實(shí)驗(yàn)。它的目標(biāo)用戶不是要做現(xiàn)代 GUI 的人而是需要讓代碼在舊環(huán)境下可靠運(yùn)行的人。2. 搭建 DJGPP 2.04 開發(fā)環(huán)境解壓、環(huán)境變量與第一個可執(zhí)行文件DJGPP 2.04 的安裝不是雙擊一個 exe 就完事它更像一套綠色軟件官方把整套工具鏈打散成一批 zip 包下載回歸 DOS 目錄結(jié)構(gòu)后解壓、設(shè)兩個環(huán)境變量就能用。這個流程二十多年沒變過反而成了它的優(yōu)點(diǎn)——不寫注冊表不留服務(wù)刪掉整個目錄就是卸載換機(jī)器遷移也只需要拷走整個目錄。2.1 下載與目錄規(guī)劃為什么堅(jiān)持干凈的根目錄我一般會把所有 zip 包解壓到同一個根目錄比如 C:\DJGPP。這里的第一個坑是路徑里不能有空格DJGPP 自身的腳本和 GNU make 在解析帶空格的路徑時會切錯參數(shù)頭文件路徑拼接失敗報錯卻指向 stdio.h排查起來很繞。第二個坑是解壓時必須保留 zip 內(nèi)自帶的目錄層次很多人習(xí)慣右鍵“解壓到當(dāng)前文件夾”一下子把 bin、include、lib 全攤平在一個目錄里gcc 找不到標(biāo)準(zhǔn)庫各種 No such file 跟著來。解壓完成后檢查五個位置bin\gcc.exe、bin\g.exe、bin\make.exe、lib\libc.a、include\stdio.h。這五個在主體就算齊了。C 用戶還要確認(rèn) lib\libstdcxx.a 存在DJGPP 2.04 的 C 運(yùn)行庫拆成 libstdcxx 和 libsupcxx 兩個包漏了其中一個鏈接階段會報一堆 undefined reference 到 operator new看不出是庫缺失還是代碼問題。提示不要圖省事下載別人精簡過的“綠色版 / 一鍵版”。DJGPP 工具鏈整體耦合編譯器、binutils、libc 之間對目標(biāo)文件格式的版本非常敏感混用精簡包會踩到 4.5 節(jié)的鏈接錯誤。2.2 配置 DJGPP 與 PATH兩個環(huán)境變量決定編譯器的認(rèn)知DJGPP 2.04 靠兩個環(huán)境變量定位整個工具鏈一個是 DJGPP指向 djgpp.env 文件一個是 PATH指向 bin 目錄。Windows cmd 里的最小配置如下。set DJGPPC:\DJGPP\DJGPP.ENV set PATHC:\DJGPP\BIN;%PATH%第一行的邏輯是gcc、gdb、make 啟動后會讀取 DJGPP 變量指向的 djgpp.env這個 env 文件里寫好了默認(rèn) CPU 類型、默認(rèn)編譯選項(xiàng)、庫搜索路徑和頭文件搜索路徑。變量沒設(shè)DJGPP 工具就不知道自己該按哪個平臺工作等價于一個不知道自己目標(biāo)的編譯器。第二行把 bin 塞進(jìn) PATH讓你在任何目錄下敲 gcc 都能被找到。最容易被忽略的是 DJGPP 這個名字必須全大寫工具鏈內(nèi)部按大寫變量名精確匹配cmd 的 set 不區(qū)分大小寫但傳給子進(jìn)程時變量名是小寫就真的讀不到。我一般會直接把這兩個變量寫進(jìn) Windows 系統(tǒng)環(huán)境變量而不是每次開 cmd 重新 set。因?yàn)榫幾g老代碼時經(jīng)常同時開好幾個終端一個跑 make一個跑 gdb一個盯匯編輸出只在一個窗口 set 會漏掉另外兩個報錯時還以為編譯器壞了。寫完系統(tǒng)變量要記得重啟終端讓新進(jìn)程重新加載環(huán)境當(dāng)前窗口里的舊 PATH 不會自動更新這是很多人配完環(huán)境變量發(fā)現(xiàn)不生效的第一個原因。2.3 最小編譯命令從 hello.c 到 hello.exe 的完整流程環(huán)境變量就緒后在工作目錄新建一個 hello.c。#include stdio.h int main(void) { printf(hello from DJGPP 2.04\n); return 0; }在命令行執(zhí)行編譯cd C:\DJGPP\WORK gcc -O2 -stdgnu99 -o hello.exe hello.c選項(xiàng)拆開看-O2 是常用的優(yōu)化級別對 DOS 目標(biāo)最穩(wěn)妥-stdgnu99 把 C 標(biāo)準(zhǔn)定到 C99 加 GNU 擴(kuò)展DJGPP 的 stdio.h 對 C99 的覆蓋比 C11 完整老代碼也熟悉這套-o hello.exe 指定輸出名不寫的話默認(rèn)生成 a.out在 DOS 下還得改名才能運(yùn)行容易漏。編譯成功會多出 hello.exe。我跑生成的 exe 時被 Windows 彈過一次“不是有效的 Win32 應(yīng)用程序”這是正常現(xiàn)象它是 MZ 頭的 DOS 可執(zhí)行程序頭部綁了一小段 DOS 擴(kuò)展器 stubWindows 的 DOS 虛擬機(jī)不支持 32 位 DPMI 程序所以雙擊報錯不等于編譯失敗。想確認(rèn)產(chǎn)物身份用 16 進(jìn)制編輯器看前幾個字節(jié)MZ 頭后面不是 PE 頭而是 DOS 擴(kuò)展器的實(shí)模式代碼。2.4 運(yùn)行的秘密CWSDPMI 與 DOS 擴(kuò)展器怎么把 32 位程序送進(jìn)實(shí)模式系統(tǒng)要理解 hello.exe 為什么能跑在 DOS 里先得厘清運(yùn)行模型。DOS 內(nèi)核只認(rèn)識 16 位實(shí)模式程序CPU 被限制在 1MB 地址空間內(nèi)而 DJGPP 2.04 編譯出來的是 32 位保護(hù)模式代碼兩者之間隔著一層轉(zhuǎn)換這層就是 DOS 擴(kuò)展器。DJGPP 使用的擴(kuò)展器是 CWSDPMI一個 DPMI 服務(wù)程序。它的執(zhí)行順序是在 DOS 下敲 hello.exeDOS 按 MZ 格式把程序裝入內(nèi)存程序入口處是一小段實(shí)模式 stubstub 會在當(dāng)前目錄或 PATH 里找 CWSDPMI.EXE加載它再把 CPU 從實(shí)模式切到保護(hù)模式建立 DPMI 服務(wù)完成切換后才跳轉(zhuǎn)到真正的 32 位 C 運(yùn)行時入口最后執(zhí)行 main。所以 hello.exe 不是一個獨(dú)立自足的文件發(fā)布時務(wù)必把 CWSDPMI.EXE 一起帶上否則換一臺沒有它的機(jī)器程序會在加載階段就退出界面都不給你一個。如果不想讓用戶帶外部文件可以用 bin 下的 stubedit.exe 把 CWSDPMI 靜態(tài)嵌進(jìn)可執(zhí)行文件。stubedit hello.exe這個命令會打開一個底層的 stub 編輯界面在界面上把 cwsdpmi 的 stub 綁定到當(dāng)前 exe。綁定后文件會增大 10KB 左右但啟動時不依賴外部 CWSDPMI。常見做法是開發(fā)期用外部 CWSDPMI發(fā)布期靜態(tài)綁入兩邊都避免運(yùn)行時缺文件。我自己的習(xí)慣是開發(fā)期也靜態(tài)綁定這樣每次在 DOSBox 里同步文件只拷一個 exe少一個遺漏點(diǎn)。2.5 用 DOSBox 做運(yùn)行測試驗(yàn)收編譯產(chǎn)物前的最后一步只有真正在 DOS 環(huán)境里跑一次才算驗(yàn)收工具鏈。常見做法是裝一個 DOSBox把 C:\DJGPP 整個目錄掛載為 DOS 的 C 盤然后在虛擬機(jī)里執(zhí)行mount c c:\djgpp c: cd work hello.exe運(yùn)行結(jié)果應(yīng)當(dāng)是打印出 hello from DJGPP 2.04然后正常回到 DOS 提示符。如果程序沒有輸出就直接退出先看當(dāng)前目錄有沒有 CWSDPMI.EXE如果打印輸出后系統(tǒng)卡死多半是 DOSBox 的 CPU 核心與保護(hù)模式切換沖突把 dosbox.conf 里的 coredynamic 改成 corenormal 再試。這一章的終點(diǎn)是把代碼、編譯、運(yùn)行三條鏈穿起來。鏈通了下一章聊工具鏈的高級用法才有依托。3. 用好 DJGPP 2.04 的完整工具鏈編譯選項(xiàng)、Makefile 與調(diào)試DJGPP 2.04 不只是 gcc 一家獨(dú)大它還帶了一套完整的 binutils 和調(diào)試器。學(xué)會把它們組合起來老平臺開發(fā)效率才追得上現(xiàn)代環(huán)境而不是每次重編一遍全項(xiàng)目。3.1 gcc 常用編譯選項(xiàng)-O2、-std 和 -march 在 DOS 目標(biāo)下怎么選DJGPP 2.04 的 gcc 是針對 i586 目標(biāo)構(gòu)建的默認(rèn)指令集基線在 Pentium 一代附近。如果你把編譯產(chǎn)物拿到更老的 386/486 上跑默認(rèn)選項(xiàng)生成的指令會讓老 CPU 直接非法指令異常。所以第一個要明確的是目標(biāo)硬件。只在 DOSBox 里跑用默認(rèn)或 -marchi586 都行要兼顧 486 機(jī)器就加 -marchi486。一個常見編譯命令模板gcc -O2 -marchi486 -stdgnu99 -Wall -c video.c gcc -O2 -marchi486 -stdgnu99 -o game.exe main.o video.o input.o參數(shù)說明-O2 是性能與體積的平衡點(diǎn)-Os 在空間緊張時換體積-march 限制生成的指令集決定了產(chǎn)物能跑在什么檔次的 CPU 上-Wall 打開編譯警告老代碼遷移時靠它抓未聲明函數(shù)。需要留意 DJGPP 2.04 里 -stdgnu99 與 -stdc99 的差別不大但涉及內(nèi)聯(lián)匯編和 asm 關(guān)鍵字時gnu99 更省事老 C 代碼里的asm不會被嚴(yán)格模式攔下。還有一個經(jīng)常被忽略的選項(xiàng)組合是 -ffunction-sections 和 -fdata-sections。它把每個函數(shù)、數(shù)據(jù)段拆成獨(dú)立小節(jié)配合鏈接器的 --gc-sections 可以把沒用到的部分整體丟棄。對 DOS 下體積敏感的工具很實(shí)用尤其從老倉庫拉來的代碼經(jīng)常整個目錄都被編譯進(jìn)去實(shí)際只用到其中一小部分函數(shù)。gcc -O2 -ffunction-sections -fdata-sections -c *.c gcc -o tool.exe *.o -Wl,--gc-sections第一行編譯時按函數(shù)和數(shù)據(jù)切分段落第二行鏈接時丟棄沒人引用的段。效果在控制臺工具上通常能省 20% 到 30% 體積代價是調(diào)試時符號表變大行號定位偶爾不準(zhǔn)。所以這個選項(xiàng)組我一般只用在發(fā)布前的最終構(gòu)建調(diào)試階段關(guān)掉。3.2 多文件項(xiàng)目一個可以直接抄的 Makefile 模板DJGPP 自帶 GNU make可以在 DOS 命令行下直接跑 make 命令。寫多文件工程時我通常從 min 依賴規(guī)則的 Makefile 開始。CC gcc CFLAGS -O2 -stdgnu99 -Wall -marchi486 TARGET game.exe OBJS main.o video.o input.o $(TARGET): $(OBJS) $(CC) -o $(TARGET) $(OBJS) main.o: main.c video.h input.h $(CC) $(CFLAGS) -c main.c video.o: video.c video.h $(CC) $(CFLAGS) -c video.c input.o: input.c input.h $(CC) $(CFLAGS) -c input.c clean: del $(OBJS) $(TARGET)這套規(guī)則講了兩件事顯式依賴和隱式命令。每條“目標(biāo): 依賴列表”后面帶 Tab 縮進(jìn)的行是生成目標(biāo)的命令GNU make 執(zhí)行時先比較目標(biāo)和依賴的時間戳依賴比目標(biāo)新才重建目標(biāo)。clean 用的是 del 而不是 rm因?yàn)?DJGPP 的命令行環(huán)境沒有 rmWindows cmd 下 del 同樣有效。這里最經(jīng)典的翻車點(diǎn)是縮進(jìn)用了空格而不是 Tabmake 會報 missing separator 然后直接停止我在這上面丟過不少時間。如果源碼文件很多可以考慮模式規(guī)則和自動推導(dǎo)但不建議新手一上來就用。DJGPP 2.04 在 DOS 下跑的 make 版本較老通配符和二次展開的行為細(xì)節(jié)跟新版不一樣踩到會很難查。先用寫全依賴的笨辦法項(xiàng)目穩(wěn)定后再優(yōu)化省下來的時間比省下的幾行字符值錢。3.3 用 gdb 調(diào)試 DJGPP 程序從黑匣子到斷點(diǎn)DJGPP 2.04 的 gdb 是 DOS 原生程序意味著你可以直接在 DOSBox 里啟動它調(diào)試另一個 DOS 程序。編譯時打開調(diào)試符號并關(guān)閉優(yōu)化gcc -g -O0 -o hello.exe hello.c gdb hello.exe進(jìn)入 gdb 命令行后常用流程是(gdb) break main (gdb) run (gdb) next (gdb) print i (gdb) quitbreak main 在函數(shù)入口設(shè)斷點(diǎn)run 啟動程序在斷點(diǎn)停下后 next 是單步執(zhí)行print 打印變量當(dāng)前值quit 退出。對 DOS 程序來說最實(shí)用的是拿它查段錯誤run 之后如果看到 SIGSEGV用 backtrace 看棧回溯基本能定位到是哪個函數(shù)寫穿了數(shù)組。注意 gdb 在 DOSBox 里運(yùn)行也需要 CWSDPMI.EXE而且調(diào)試器本身要占內(nèi)存DOSBox 默認(rèn)的 XMS 設(shè)置太低時先把 DOSBox 配置里的 memsize 調(diào)到 16 或 32 再跑。遇到難纏的內(nèi)存問題gdb 有一種現(xiàn)代環(huán)境里不常見的玩法直接在保護(hù)模式下訪問顯存和端口。你在 gdb 里用 x/wx 0xB8000 可以直接查看 VGA 文本緩沖區(qū)的內(nèi)容在調(diào)試圖形驅(qū)動時不需要額外打日志函數(shù)比插入一堆調(diào)試代碼干凈得多。3.4 用 binutils 做產(chǎn)物瘦身與檢查strip、objcopy 的實(shí)際場景DJGPP 2.04 自帶的 binutils 里日常最常用的三條是 strip、objcopy 和 strings。strip 去掉符號表把可執(zhí)行文件體型減下來strip -s game.exe對發(fā)布版本做這一步是基本禮儀一個帶完整調(diào)試符號的 DOS 程序體積能差一倍以上。-s 表示 strip all連重定位信息一起去掉副作用是 gdb 無法再對這個 exe 下斷點(diǎn)所以 strip 要放在最后階段調(diào)試期間不要碰。objcopy 在自制操作系統(tǒng)和引導(dǎo)程序場景出場。DJGPP 的鏈接器默認(rèn)產(chǎn)出帶 DOS 擴(kuò)展器的可執(zhí)行格式但你的內(nèi)核最終要交給引導(dǎo)器加載到固定內(nèi)存地址需要的是一段沒有文件頭、沒有重定位的純二進(jìn)制。命令objcopy -O binary kernel.exe kernel.bin-O binary 把 COFF 段按內(nèi)存布局原樣導(dǎo)出生成 flat binary。這個文件沒有了 MZ 頭和保護(hù)模式 stub必須由引導(dǎo)代碼按約定地址加載并自行切換保護(hù)模式。用這個命令前建議先在鏈接腳本里把內(nèi)核段布局排好常見做法是讓 .text 起始地址等于期望的加載地址否則出來的 bin 會在開頭帶一坨填充數(shù)據(jù)加載時錯位。strings 是個被低估的檢查工具strings game.exe | grep -i error\|fail它把所有可打印字符串撈出來配合 grep 看程序內(nèi)置的錯誤提示不用運(yùn)行就能排查資源文件路徑、配置文件格式這類問題。我在處理老游戲漢化時經(jīng)常用它確認(rèn)文本是 ASCII 還是 GBK比盲改二進(jìn)制穩(wěn)得多。4. DJGPP 2.04 避坑指南環(huán)境變量、路徑分隔符與內(nèi)存模型的典型翻車現(xiàn)場DJGPP 2.04 的報錯信息往往不像現(xiàn)代編譯器那么友好很多問題表現(xiàn)為“編譯不過”或“運(yùn)行崩潰”實(shí)際根源卻藏在環(huán)境配置和內(nèi)存模型里。下面按我踩過的順序列出 5 個高頻坑每條按現(xiàn)象、原因、解決的順序說清楚。4.1 stdio.h not found環(huán)境變量 DJGPP 丟失的癥狀與修復(fù)現(xiàn)象gcc 一編譯任何 .c 文件立刻報stdio.h: No such file or directory但 include\stdio.h 明明存在。原因DJGPP 環(huán)境變量沒設(shè)置或設(shè)置了但指向的 djgpp.env 路徑不對。gcc 知道自己是 DJGPP 工具鏈但沒被告知去哪找標(biāo)準(zhǔn)頭文件。另一個隱蔽點(diǎn)是你把 djgpp.env 的名字寫錯比如 Windows 默認(rèn)隱藏擴(kuò)展名文件名真實(shí)是 DJGPP.ENV.txt指針就斷了。解決先用 echo %DJGPP% 確認(rèn)變量存在且路徑正確再看文件管理器里有沒有被系統(tǒng)吞掉擴(kuò)展名。我在 Windows 上習(xí)慣在 cmd 里執(zhí)行 dir C:\DJGPP\DJGPP.E* 看真實(shí)文件名比肉眼省心。配好這兩個變量后gcc -v 打印的 search 路徑里能看到 include 目錄一眼就能驗(yàn)證工具鏈認(rèn)知是否到位。4.2 Makefile 里文件路徑被反斜杠截斷路徑分隔符的坑現(xiàn)象Makefile 里寫 C:\DJGPP\LIB\libc.amake 報錯說找不到文件或把路徑截斷成 C: 加一串亂碼。原因GNU make 把反斜杠當(dāng)作轉(zhuǎn)義字符處理DOS 風(fēng)格路徑里的 \D、\L 會被吃掉一層。這個問題在 DJGPP 2.04 自帶的舊版 make 里尤其明顯。解決Makefile 內(nèi)部統(tǒng)一用正斜杠寫路徑比如 C:/DJGPP/LIB/libc.aWindows 和 DOS 的文件系統(tǒng)層都能識別正斜杠。需要傳給編譯器參數(shù)時也盡量用正斜杠只在用戶交互的命令行里保留反斜杠習(xí)慣?,F(xiàn)在寫任何 DOS 項(xiàng)目 Makefile我第一件事就是全局替換反斜杠省得后面的坑連成串。4.3 長文件名支持LFN 開關(guān)與 8.3 短文件名的取舍現(xiàn)象程序在 DOSBox 里 fopen(config.dat, r) 正常放到純凈 DOS 6.22 上 fopen 返回 NULL或者文件讀到一半數(shù)據(jù)錯亂。原因DJGPP 2.04 通過 LFN 接口訪問長文件名DOS 默認(rèn)沒開啟該接口時需要驅(qū)動程序支持。純凈 DOS 只有 8.3 短文件名而你的源代碼和資源文件在 Windows 下解壓時名字已經(jīng)拉長程序按長名字去找自然找不到。解決兩條路。一是給 DOS 加載 LFN 驅(qū)動但老機(jī)器環(huán)境不好說二是把涉及運(yùn)行期資源訪問的文件名全部壓成 8.3 格式配置叫 config.cfg存檔叫 save.dat。我在移植老游戲時默認(rèn)按短文件名規(guī)劃資源省掉一長串兼容性報告。4.4 C 異?;虼髷?shù)組段錯誤DPMI 內(nèi)存不夠時的現(xiàn)象與處理現(xiàn)象C 程序里 throw 一個異常程序直接崩潰或者 main 里聲明一個 10MB 的局部數(shù)組運(yùn)行到函數(shù)入口就段錯誤。原因DJGPP 2.04 的 C 異常依賴 DPMI 提供的內(nèi)存管理CWSDPMI 分配給程序的堆大小默認(rèn)從系統(tǒng)配置讀取DOSBox 默認(rèn)內(nèi)存不足時異常處理?xiàng):痛蟮臈I蠑?shù)組都放不下。局部數(shù)組超過幾 MB 時往往超出內(nèi)核對堆棧段的預(yù)留運(yùn)行時報段錯誤。解決給 CWSDPMI 配置更大的內(nèi)存上限。常見做法是改 dosbox.conf 的 memsize 到 64或者在發(fā)布前用 stubedit 設(shè)置程序需要的 DPMI 內(nèi)存池大數(shù)組改用 malloc 放堆不要放棧。我給老平臺移植代碼定的規(guī)矩是單塊棧數(shù)組不超過 64KB超過一律堆分配這條規(guī)矩救了不少次。4.5 undefined reference to __main工具鏈版本混用的典型癥狀現(xiàn)象鏈接時報 undefined reference to __main代碼里根本沒寫過這個符號。原因這是 DJGPP 特有的 C 運(yùn)行時初始化符號。GCC 在 C 程序啟動時通過調(diào)用 __main 完成全局構(gòu)造器流程該符號由 libc.a 提供。問題是 binutils、libc 和 gcc 三者版本混搭時GCC 認(rèn)為自己需要 __main而鏈接器鏈接的庫卻改了符號名或沒有導(dǎo)出目標(biāo)文件格式對不上最終鏈接失敗。解決嚴(yán)格按照同一套發(fā)布的 DJGPP 2.04 包安裝不要混用不同分支的 gcc 和 libc。檢查方法是用 nm 在 libc.a 里搜索符號nm libc.a | grep main確認(rèn)存在后再查 gcc 的 specs 文件里的 crt 相關(guān)路徑是否指到同版本目錄。這個問題沒有快速補(bǔ)丁把環(huán)境清理干凈重裝全套是最省時間的做法比對著 ld 腳本折騰半天下班。5. 進(jìn)階用法驗(yàn)證編譯產(chǎn)物與移植老代碼的兩個實(shí)用習(xí)慣工具鏈跑順之后有兩件事值得固定成習(xí)慣拿到陌生 exe 先驗(yàn)證遷移老代碼只動封裝層。這兩件事能幫你把“運(yùn)氣好能過”變成“每次都能過”。5.1 驗(yàn)證產(chǎn)物三種快速檢查手段我不太相信“剛才編譯過了”這句話。拿到一個 DJGPP 產(chǎn)物至少做三道檢查。第一道是 file 確認(rèn)格式file game.exe輸出出現(xiàn) 32 位、DOS 可執(zhí)行、保護(hù)模式這些關(guān)鍵詞說明工具鏈目標(biāo)正確如果顯示 16 位 COM 或 EXE多半是環(huán)境里混入了其他 DOS 工具。第二道是 strings 掃內(nèi)嵌路徑strings game.exe | grep -i config\\如果在輸出里看到反斜杠形式的路徑程序在 DOS 下解析路徑時會出問題趁發(fā)布前改掉。第三道是完整啟動模擬發(fā)布前把 exe、CWSDPMI.EXE 和資源文件放進(jìn)同一個目錄在 DOSBox 里從干凈機(jī)器模擬一次完整啟動。這步不能省我數(shù)不清多少次在開發(fā)機(jī)上有 CWSDPMI 所以沒暴露換目錄后程序直接神秘消失。5.2 把老 Turbo C 代碼遷到 DJGPP只動封裝層不動業(yè)務(wù)邏輯從 Turbo C 遷到 DJGPP 2.0490% 的工作集中在頭文件和平臺 API 替換。我給自己定的規(guī)則是造一個 target-config.h把平臺差異全壓進(jìn)去。#ifndef TARGET_CONFIG_H #define TARGET_CONFIG_H /* Turbo C 的遠(yuǎn)指針在 DJGPP 平坦內(nèi)存里不需要 */ #if defined(__DJGPP__) #include stdlib.h #define _fmalloc malloc #define _ffree free #else #include alloc.h #endif #endif這個頭文件的作用是業(yè)務(wù)代碼繼續(xù)調(diào)用 _fmalloc底層在 DJGPP 下被替換成 malloc。32 位保護(hù)模式的平坦內(nèi)存里所有指針都夠遠(yuǎn)遠(yuǎn)指針概念本來就該廢棄用宏映射一下業(yè)務(wù)代碼不用改。移植時我會先把編譯錯誤全部排干凈再逐個替換圖形和中斷部分最后跑回歸。圖形模式是另一個深水區(qū)保護(hù)模式訪問顯存需要映射gdb 設(shè)斷點(diǎn)查 VGA 緩沖比 printf 調(diào)試高效。這套流程走完一個項(xiàng)目基本就有了自己的移植模板。我現(xiàn)在拿到任何老代碼第一反應(yīng)是先找它的平臺依賴集中在哪幾個頭文件而不是急著編譯。這個習(xí)慣讓后來的移植工作順手不少希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取