錯(cuò)修復(fù):msinttypes補(bǔ)齊stdint.h,跨平臺(tái)編譯不再卡殼)
簡(jiǎn)介在 Visual Studio 系列編譯器中缺少 C99 標(biāo)準(zhǔn)頭文件 stdint.h 與 inttypes.h 會(huì)觸發(fā) fatal error C1083: 無(wú)法打開包括文件根本原因是 VC 對(duì) C99 標(biāo)準(zhǔn)支持不完整。這份修復(fù)包專門針對(duì)該報(bào)錯(cuò)整理面向使用 VS2008 或其他老版本 VC 進(jìn)行 C/C 項(xiàng)目開發(fā)、需要引入標(biāo)準(zhǔn)整數(shù)類型定義的程序員。包內(nèi)共 3 個(gè)文件包括兩個(gè)頭文件與一個(gè)更新日志文本頭文件提供了 int8_t、uint32_t 等標(biāo)準(zhǔn)整數(shù)類型的聲明與平臺(tái)兼容定義日志則記錄了版本變更信息整體僅 17KB只包含修復(fù)所需的全部?jī)?nèi)容足以直接解決該編譯錯(cuò)誤且不需要額外依賴。資源已有 333 人學(xué)習(xí)下載體積小巧、針對(duì)性強(qiáng)適合在編譯報(bào)錯(cuò)時(shí)快速定位解決對(duì)時(shí)間緊張的項(xiàng)目排障尤其友好。通過該包不僅能獲得可直接復(fù)用的缺失頭文件還能了解舊版 Windows SDK/VC 在 C99 支持上的限制與補(bǔ)全思路對(duì)維護(hù)老項(xiàng)目或跨平臺(tái)代碼有實(shí)用參考價(jià)值。1. 從C1083報(bào)錯(cuò)說起這個(gè)資源到底是干什么的兩年前我接手一個(gè)跨平臺(tái)網(wǎng)絡(luò)庫(kù)的移植代碼在Linux上跑得好好的一挪到Windows上編譯控制臺(tái)直接蹦出一行刺眼的fatal error C1083: stdint.h。當(dāng)時(shí)第一反應(yīng)是“缺頭文件裝個(gè)SDK就行”折騰半天發(fā)現(xiàn)根本不是那回事——老版本的MSVC壓根沒實(shí)現(xiàn)C99的stdint.h而代碼里到處是uint32_t、int64_t。后來從某嵌入式論壇看到msinttypes-r26這個(gè)補(bǔ)丁包解壓后把路徑一配置編譯直接通過。這份資源本質(zhì)上是一個(gè)頭文件包專門幫老編譯器補(bǔ)上標(biāo)準(zhǔn)整數(shù)類型頭文件解決的就是“C1083且錯(cuò)誤指向stdint.h”這類問題。適合所有在Windows上編譯開源代碼、做跨平臺(tái)移植、或者維護(hù)老舊C/C工程的開發(fā)者。2. stdint.h為何會(huì)在Windows上缺席C標(biāo)準(zhǔn)與編譯器的差距2.1 MSVC對(duì)C99支持的緩慢歷史C語(yǔ)言標(biāo)準(zhǔn)在1999年就提出了stdint.h和inttypes.h里面定義了帶固定寬度的整數(shù)類型比如int32_t、uint64_t還有格式化宏P(guān)RId64。這套頭文件在Linux、macOS、各種嵌入式編譯器里早就普及了但Windows平臺(tái)上的老牌C編譯器一直停留在C89/C90的舒適區(qū)對(duì)C99特性的支持能拖就拖。直到VS2010那一代才真正內(nèi)置了stdint.h之前的版本全都需要外部補(bǔ)丁。即使到了VS2010之后部分變體仍然在“有”和“沒有”之間反復(fù)橫跳讓人頭疼。為什么老牌編譯器這么抗拒C99常見理由是ABI兼容性和歷史代碼的穩(wěn)定性。Windows上有大量老程序依賴long類型32位、int類型32位的默認(rèn)行為如果按照C99的嚴(yán)格定義引入stdint.h可能會(huì)讓一些隱式類型轉(zhuǎn)換出現(xiàn)警告甚至錯(cuò)誤。再加上Windows自己的SDK頭文件里也定義了一套類似的類型比如INT32、UINT64沖突風(fēng)險(xiǎn)極高。所以廠商選擇了“不主動(dòng)實(shí)現(xiàn)”讓開發(fā)者自己去想辦法。msinttypes就是這個(gè)場(chǎng)景下的產(chǎn)物。它最初是某個(gè)開源愛好者為了解決自家嵌入式項(xiàng)目的編譯問題而寫的后來不斷修復(fù)邊界情況r26版本就是其中的一個(gè)穩(wěn)定提交。它不編譯、不鏈接就是兩個(gè)頭文件加幾個(gè)輔助文件把C99標(biāo)準(zhǔn)里定義的類型和宏原樣翻譯成老編譯器能識(shí)別的typedef和#define。用上之后源碼里就不用再到處寫“#ifdef WIN32 typedef ...”這種丑陋的兼容層了。實(shí)際使用中你會(huì)發(fā)現(xiàn)很多開源項(xiàng)目在Windows上編譯失敗很大比例都是栽在stdint.h上。比如某個(gè)用C語(yǔ)言寫的物聯(lián)網(wǎng)協(xié)議棧內(nèi)部大量使用了uint16_t來解析報(bào)文在Linux上編譯順暢一拿到Windows上就報(bào)C1083新手往往會(huì)誤判成“SDK沒裝好”反復(fù)重裝環(huán)境最后發(fā)現(xiàn)只是缺一個(gè)頭文件。這種血淚經(jīng)驗(yàn)經(jīng)歷過一次就能記住。2.2 stdint.h在跨平臺(tái)代碼里的實(shí)際價(jià)值先來看為什么跨平臺(tái)代碼離不開stdint.h。C標(biāo)準(zhǔn)只保證了char、int、long這些基礎(chǔ)類型的最小范圍并沒有承諾它們?cè)诿總€(gè)平臺(tái)上的位寬。在主流平臺(tái)上int通常是32位long在Linux 64位下是64位但在Windows 64位下仍然是32位。這意味著同樣的代碼在Linux上運(yùn)行正常換到Windows上就可能出現(xiàn)結(jié)構(gòu)體大小不對(duì)、序列化格式錯(cuò)亂、位運(yùn)算結(jié)果異常等玄學(xué)問題。stdint.h提供了三組東西固定寬度整數(shù)類型intN_t/uintN_t、最小/最快寬度類型int_leastN_t、int_fastN_t、以及一些輔助宏INT_MAX、PRId64等。使用這些類型程序的行為就不會(huì)跟隨平臺(tái)自動(dòng)漂移。比如定義一個(gè)網(wǎng)絡(luò)包頭結(jié)構(gòu)體用uint32_t字段替代unsigned int就能確保它在所有編譯環(huán)境下都是4字節(jié)而不是有的平臺(tái)4字節(jié)、有的平臺(tái)8字節(jié)。還有一點(diǎn)容易被忽略intptr_t和uintptr_t是用來保存指針值的整數(shù)類型。在32位程序里它是32位在64位程序里它是64位。如果你在做哈?;蛘甙阎羔槷?dāng)作數(shù)值運(yùn)算這個(gè)類型就是標(biāo)準(zhǔn)答案。在老編譯器上這些類型全都要靠自己typedefmsinttypes一次性幫你補(bǔ)齊。2.3 三種替代方案對(duì)比面對(duì)缺少stdint.h的編譯器你其實(shí)有三條路可以走。我根據(jù)自己的維護(hù)經(jīng)驗(yàn)列一張對(duì)比表方案優(yōu)點(diǎn)缺點(diǎn)適用場(chǎng)景自己寫一個(gè)自定義stdint.h完全可控?zé)o外部依賴容易寫錯(cuò)類型寬度需要維護(hù)只有一個(gè)臨時(shí)工程代碼量極小用msinttypes補(bǔ)齊標(biāo)準(zhǔn)實(shí)現(xiàn)直接可用社區(qū)驗(yàn)證過需要額外引入文件不能自動(dòng)更新絕大多數(shù)跨平臺(tái)移植和開源代碼編譯升級(jí)編譯器或換用其他工具鏈治本后續(xù)無(wú)需擔(dān)心C99特性可能牽動(dòng)老項(xiàng)目ABI成本高新項(xiàng)目或允許改動(dòng)編譯環(huán)境的場(chǎng)景我一般會(huì)推薦第二種。原因很簡(jiǎn)單自己寫stdint.h你得考慮32位/64位、要不要兼容無(wú)符號(hào)、還要定義各種宏細(xì)節(jié)多到容易翻車。而且一旦有多個(gè)源碼目錄每個(gè)工程都塞一份自定義頭文件維護(hù)起來就是噩夢(mèng)。msinttypes經(jīng)過多年修補(bǔ)對(duì)老編譯器的兼容性做得比較完善r26這個(gè)版本我用了幾年沒出過岔子。選擇它還有一個(gè)隱性好處源碼不用再寫一堆“#ifdef _MSC_VER”繞道代碼了。整個(gè)工程在一個(gè)統(tǒng)一的頭文件視角下編譯后續(xù)如果想切換到新編譯器直接把msinttypes從包含路徑里移除就行代碼不需要?jiǎng)?。這種“后悔藥”的效果經(jīng)歷過老工程現(xiàn)代化改造的人都懂。再說說和標(biāo)準(zhǔn)C語(yǔ)言的對(duì)應(yīng)關(guān)系。stdint.h還定義了一組寬度宏比如INT8_C、INT16_MAX這些在初始化常量時(shí)非常有用。例如你想定義一個(gè)恰好等于INT32_MAX的宏常量直接寫#define LIMIT INT32_C(2147483647)比起手動(dòng)算0x7FFFFFFF更清晰。msinttypes里也完整實(shí)現(xiàn)了這些宏而且針對(duì)老編譯器可能不支持的部分做了變通。如果你之前只關(guān)注了類型定義忽略了這些宏那在寫可移植代碼時(shí)可能會(huì)踩坑。另外inttypes.h里還有strtoimax、strtoumax等函數(shù)聲明以及printf格式宏P(guān)RId32、PRIu64。在Windows上用printf打印長(zhǎng)整型常用的做法是%I64d但這些是非標(biāo)準(zhǔn)擴(kuò)展。用了inttypes.h就可以寫PRId64代碼在Linux和Windows上都能編譯不用再為格式化字符串傷腦筋。這個(gè)細(xì)節(jié)在移植串口調(diào)試代碼時(shí)尤其常見。3. msinttypes-r26上手三步配好編譯器包含路徑3.1 解壓看結(jié)構(gòu)里面到底有什么下載下來的壓縮包體積很小解壓后一多半是說明文檔和測(cè)試代碼真正核心的文件只有兩個(gè)stdint.h和inttypes.h。我以r26版本為例列出常用的文件清單文件作用stdint.h定義全部整數(shù)類型和大小限制宏inttypes.h定義格式化宏和轉(zhuǎn)換函數(shù)聲明LICENSEBSD風(fēng)格的開源許可README使用說明和版本歷史這里的文件結(jié)構(gòu)很干凈不需要額外安裝腳本。你需要做的就是把整個(gè)解壓目錄加入編譯器的頭文件搜索路徑。一個(gè)常見的做法是把該目錄復(fù)制到工程目錄下的third_party/msinttypes或者放到一個(gè)公共的依賴庫(kù)目錄里比如D:\dev\include。復(fù)制好之后建議用文本編輯器打開stdint.h確認(rèn)文件開頭包含“#ifdef _MSC_VER”之類的條件編譯代碼。msinttypes在內(nèi)部會(huì)根據(jù)_MSC_VER宏判斷編譯器版本再?zèng)Q定是否啟用typedef。比如在VC6這樣的老古董上它走一套完全不同的定義。如果你用較新的編譯器它也會(huì)自覺避免和系統(tǒng)自帶的stdint.h沖突。3.2 通過IDE屬性頁(yè)配置包含路徑最常見的使用方式是在Windows IDE里打開項(xiàng)目屬性進(jìn)行配置。操作路徑是項(xiàng)目右鍵-屬性-C/C-常規(guī)-附加包含目錄。點(diǎn)開編輯框手動(dòng)輸入msinttypes所在的目錄路徑或者點(diǎn)旁邊的下拉按鈕選“編輯”再點(diǎn)文件夾圖標(biāo)瀏覽目錄。配置完成后記得先把語(yǔ)言標(biāo)準(zhǔn)設(shè)置成“未設(shè)置”或C89兼容模式否則某些IDE可能強(qiáng)行走新的頭文件搜索邏輯。這里有個(gè)容易忽略的點(diǎn)附加包含目錄的作用范圍是當(dāng)前項(xiàng)目配置不是全局的。如果你同時(shí)維護(hù)Debug和Release兩個(gè)配置并且都在用老編譯器兩邊都得添加一次。我見過有同事只改了DebugRelease還在報(bào)C1083糾結(jié)了半天最后發(fā)現(xiàn)是這條路沒走全。建議在屬性頁(yè)頂部的“配置”下拉框選擇“所有配置”一勞永逸。配置完成后清理并重新編譯整個(gè)工程。如果原來的報(bào)錯(cuò)是“未找到stdint.h”此時(shí)應(yīng)該消失。如果還報(bào)錯(cuò)看看錯(cuò)誤消息里的文件路徑是否指向了別的位置的stdint.h。這里容易中招的是項(xiàng)目里某個(gè)第三方庫(kù)也帶了一個(gè)stdint.h并且它的目錄在附加包含目錄列表里排在更前面那編譯器找到的就不是msinttypes的版本。關(guān)于這個(gè)路徑配置還有一個(gè)小技巧把msinttypes目錄放在附加包含目錄列表的最前面。這樣能保證優(yōu)先級(jí)最高避免和其他庫(kù)頭文件混戰(zhàn)。列表順序在屬性頁(yè)里可以直接上下移動(dòng)值得養(yǎng)成習(xí)慣。3.3 通過命令行cl.exe直接配置不進(jìn)IDE直接用命令行編譯的情況也非常多。比如用cl.exe配合Makefile、批處理腳本或CMake。此時(shí)只需要多傳一個(gè)/I參數(shù)告訴編譯器在哪里找頭文件。cl /I D:\dev\include\msinttypes /Tc test_stdint.c這里的 /I 是附加包含目錄的短參數(shù)后緊跟目錄路徑。如果路徑有空格必須用雙引號(hào)包起來。/Tc 表示把后續(xù)文件當(dāng)作C語(yǔ)言源碼編譯如果想編譯C代碼改成 /Tp 或直接給.cpp擴(kuò)展名。執(zhí)行后如果沒報(bào)錯(cuò)會(huì)在同目錄下生成test_stdint.obj文件。有些項(xiàng)目用的是nmakeWindows下的make工具那就在Makefile里設(shè)置CFLAGS變量加上該路徑。示例CFLAGS /nologo /ID:\dev\include\msinttypes /W3這里 /W3 是警告級(jí)別按需調(diào)整。在Makefile里寫路徑時(shí)要注意反斜杠和引號(hào)的轉(zhuǎn)義習(xí)慣nmake對(duì)這些相對(duì)寬松但遇到空格時(shí)一定要引號(hào)閉合。如果你是在PowerShell或CMD里臨時(shí)測(cè)試不想加進(jìn)工程配置也可以直接用環(huán)境變量INCLUDE來臨時(shí)注入。命令行設(shè)置方式如下set INCLUDE%INCLUDE%;D:\dev\include\msinttypes這個(gè)命令的作用范圍是當(dāng)前控制臺(tái)窗口不會(huì)改動(dòng)系統(tǒng)級(jí)配置適合快速驗(yàn)證。Windows的編譯器在查找頭文件時(shí)會(huì)先查命令行參數(shù)指定的路徑再查環(huán)境變量INCLUDE所指的路徑。所以如果你的INCLUDE已經(jīng)包含了一個(gè)有stdint.h的地方可能不生效這時(shí)需要檢查順序。3.4 集成到CMake工程用CMake組織的跨平臺(tái)工程配置msinttypes比手動(dòng)改項(xiàng)目屬性要優(yōu)雅得多。你不需要碰IDE的界面只要在CMakeLists.txt里加上一行include_directoriesinclude_directories(${PROJECT_SOURCE_DIR}/third_party/msinttypes)這行代碼把msinttypes目錄加入所有編譯目標(biāo)的頭文件搜索路徑。需要確保該目錄實(shí)際存在于源碼樹中并且路徑名稱與目錄真實(shí)名稱一致。如果你的構(gòu)建系統(tǒng)是從網(wǎng)上下載依賴還可以考慮用FetchContent模塊在配置階段自動(dòng)下載但這里不建議因?yàn)轭^文件本身不依賴網(wǎng)絡(luò)直接隨源碼分發(fā)更省事。CMake中還有一個(gè)細(xì)節(jié)MSVC的編譯器在編譯C文件時(shí)會(huì)自動(dòng)開啟C89模式編譯C時(shí)不會(huì)受到影響。如果你的CMake項(xiàng)目里同時(shí)有C和C文件并且在C代碼中也用了stdint.hmsinttypes是可以起作用的因?yàn)镃里同樣會(huì)include頭文件。唯一要注意的是如果你設(shè)置了標(biāo)準(zhǔn)庫(kù)強(qiáng)制使用Windows DLL運(yùn)行時(shí)即/MD或/MT開關(guān)那msinttypes和這些宏是獨(dú)立的互不影響。如果項(xiàng)目還使用了預(yù)編譯頭PCH記得在預(yù)編譯頭文件如stdafx.h里#include stdint.h否則每個(gè).cpp文件單獨(dú)include也可以但會(huì)拖慢編譯。工程里如果已有預(yù)編譯頭而你在某個(gè).cpp里直接#include stdint.h某些IDE會(huì)提示需在預(yù)編譯頭之后include此時(shí)調(diào)整一下順序即可。4. 驗(yàn)證stdint.h生效從編譯通過到類型寬度一致4.1 一個(gè)專門驗(yàn)證固定寬度類型的示例代碼配置完成后直接拿一個(gè)大項(xiàng)目編譯風(fēng)險(xiǎn)太大我習(xí)慣先寫一個(gè)最小的驗(yàn)證文件專門檢驗(yàn)頭文件有沒有生效、類型寬度是否符合預(yù)期。#include stdio.h #include stdint.h #include inttypes.h int main(void) { printf(sizeof(int32_t) %zu\n, sizeof(int32_t)); printf(sizeof(uint64_t) %zu\n, sizeof(uint64_t)); printf(sizeof(intptr_t) %zu\n, sizeof(intptr_t)); int32_t a INT32_C(100); uint64_t b UINT64_C(0x1122334455667788); printf(a % PRId32 , b % PRIx64 \n, a, b); return 0; }這段代碼里用到了三組東西固定寬度類型sizeof檢查、INT32_C宏常量、以及PRId32/PRIx64格式化宏。如果頭文件路徑正確編譯后會(huì)輸出類似這樣的內(nèi)容sizeof(int32_t) 4 sizeof(uint64_t) 8 sizeof(intptr_t) 4 a 100, b 1122334455667788注意第三行如果編譯出來的程序是32位的intptr_t就是4字節(jié)如果是64位會(huì)是8字節(jié)。這行輸出能幫你確認(rèn)編譯目標(biāo)環(huán)境。格式化宏P(guān)RId32在Windows的老編譯器上會(huì)展開成“d”PRIx64會(huì)展開成“I64x”這兩個(gè)宏正是inttypes.h里的關(guān)鍵內(nèi)容。4.2 通過編譯日志確認(rèn)頭文件實(shí)際路徑有時(shí)候編譯通過了但心里還是不踏實(shí)想知道編譯器到底用的是哪個(gè)目錄下的stdint.h。這時(shí)候可以采用預(yù)處理指令強(qiáng)制輸出路徑信息。最簡(jiǎn)單的方法是讓編譯器生成預(yù)處理文件。cl /I D:\dev\include\msinttypes /E test_stdint.c preprocessed.txt這里的 /E 是預(yù)處理后輸出到標(biāo)準(zhǔn)輸出重定向到文本文件。打開preprocessed.txt搜索“stdint.h”就會(huì)看到完整的頭文件路徑。例如#line 1 D:\\dev\\include\\msinttypes\\stdint.h這一行說明編譯器確實(shí)使用了你指定的msinttypes版頭文件。如果你看到路徑是某個(gè)Windows SDK目錄或者項(xiàng)目目錄下的其他頭文件那就說明搜索順序有問題需要回到第3章調(diào)整路徑順序。除了 /E還可以在代碼里用#pragma message直接把頭文件路徑打印到編譯輸出窗口。這個(gè)方法適合在IDE里使用因?yàn)榫幾g器輸出會(huì)直接顯示在“輸出”面板中不用生成中間文件。#ifdef _MSC_VER #pragma message(using msinttypes stdint.h) #endif把這段放在一個(gè)共同的私有頭文件里或者直接放在測(cè)試代碼頂部編譯時(shí)就能看到提示。注意#pragma message是編譯器擴(kuò)展不是標(biāo)準(zhǔn)C語(yǔ)言所以外面包了一層條件編譯避免其他編譯器報(bào)錯(cuò)。4.3 在Makefile和批處理腳本里復(fù)用的細(xì)節(jié)如果你的工程是用Makefile組織的或者你更喜歡批處理腳本一鍵編譯驗(yàn)證邏輯也可以嵌進(jìn)腳本里。我自己的習(xí)慣是寫一個(gè)check_stdint.bat腳本在配置階段自動(dòng)運(yùn)行上面的預(yù)處理命令并檢查輸出里是否出現(xiàn)了預(yù)期的路徑標(biāo)記。echo off cl /I D:\dev\include\msinttypes /E test_stdint.c tmp_pre.txt findstr /C:msinttypes\stdint.h tmp_pre.txt nul if errorlevel 1 ( echo [FAIL] stdint.h not from msinttypes exit /b 1 ) echo [OK] stdint.h found from msinttypes del tmp_pre.txt這個(gè)腳本的邏輯很簡(jiǎn)單用findstr檢查預(yù)處理輸出里是否包含“msinttypes\stdint.h”字符串找到了就返回成功否則輸出失敗并退出。批處理通過errorlevel判斷可以在構(gòu)建流水線里集成。對(duì)于使用GCC或Clang但也在Windows上交叉編譯的場(chǎng)景同樣可以用#error預(yù)處理指令做更硬的驗(yàn)證。例如在測(cè)試代碼里加入#ifndef INT32_MAX #error stdint.h does not define INT32_MAX #endif當(dāng)INT32_MAX沒有被定義時(shí)編譯直接終止并顯示錯(cuò)誤信息。這種編譯期驗(yàn)證方式比人工看日志更快適合自動(dòng)化集成。注意這里的宏定義名必須和實(shí)際頭文件一致msinttypes里確實(shí)定義了這些宏。5. 避坑指南C1083之外的五種常見翻車現(xiàn)場(chǎng)這一章是重頭戲我根據(jù)自己踩過的坑和幫同事排查的經(jīng)驗(yàn)整理了五條最容易遇到的錯(cuò)誤。5.1 坑1和其他庫(kù)自帶的stdint.h打架現(xiàn)象編譯時(shí)報(bào)一堆標(biāo)準(zhǔn)庫(kù)頭文件重復(fù)定義錯(cuò)誤錯(cuò)誤列表中全是“redefinition of typedef ‘int32_t’”、“missing type specifier”等字樣錯(cuò)誤數(shù)量能達(dá)到幾十條。原因項(xiàng)目里某個(gè)第三方庫(kù)自己攜帶了一個(gè)stdint.h或者系統(tǒng)SDK的新版本已經(jīng)包含了該頭文件。由于多個(gè)目錄都存在stdint.h編譯器按照搜索順序找了一個(gè)不期望的版本兩個(gè)標(biāo)準(zhǔn)庫(kù)的內(nèi)容彼此沖突。解決找到工程中所有名為stdint.h的文件確認(rèn)msinttypes的目錄是否在搜索列表最前面??梢栽趯傩皂?yè)中調(diào)整順序或者通過在C/C命令行中加入?yún)?shù)指定強(qiáng)制使用msinttypes的路徑。如果第三方庫(kù)的頭文件必須優(yōu)先那就只能手動(dòng)把庫(kù)自帶的stdint.h改名并把庫(kù)源碼里對(duì)應(yīng)的#include聲明一并修改。更穩(wěn)妥的辦法是用編譯開關(guān)查看預(yù)處理搜索路徑先確認(rèn)到底誰(shuí)被優(yōu)先找到了。5.2 坑2類型寬度和預(yù)期不一致現(xiàn)象代碼編譯通過但運(yùn)行時(shí)的結(jié)構(gòu)體大小、序列化字節(jié)數(shù)和Linux同事不一樣調(diào)試半天發(fā)現(xiàn)uint32_t變成了8字節(jié)。原因某些老的msinttypes版本在特定編譯器上沒有正確區(qū)分指針類型的寬度導(dǎo)致定義錯(cuò)誤。也可能是因?yàn)榫幾g器選項(xiàng)里啟用了/arch:IA32或強(qiáng)制4字節(jié)對(duì)齊等設(shè)置讓類型對(duì)齊發(fā)生變化。解決先用第4章的驗(yàn)證程序打印sizeof確認(rèn)寬度。如果確實(shí)是頭文件定義問題檢查是否使用了正確版本r26對(duì)64位和32位分別做了分支處理。再檢查編譯選項(xiàng)里是否定義了_WIN32或_WIN64宏msinttypes內(nèi)部依賴這些宏做位寬分支。在編譯命令行中手動(dòng)添加/D_WIN64可以強(qiáng)制走64位路徑。我遇到過最詭異的一次是工程屬性里的“平臺(tái)工具集”被誤設(shè)成了32位導(dǎo)致所有源文件都被當(dāng)作32位編譯加上/D_WIN64之后反而報(bào)宏重定義最后發(fā)現(xiàn)是工具集版本不匹配。5.3 坑3C代碼能編譯但C代碼報(bào)錯(cuò)現(xiàn)象同一個(gè)工程.c文件編譯正常.cpp文件卻報(bào)“stdint.h: No such file or directory”或語(yǔ)法錯(cuò)誤錯(cuò)誤行指向頭文件內(nèi)部的某個(gè)宏。原因老編譯器在編譯C時(shí)對(duì)C99頭文件的支持路徑不同。msinttypes的頭文件內(nèi)部包含了一些條件編譯比如“#ifndef __STDC_LIMIT_MACROS #error ...”這個(gè)錯(cuò)誤只在C中觸發(fā)因?yàn)镃標(biāo)準(zhǔn)規(guī)定這些宏默認(rèn)不暴露。解決在C源文件包含stdint.h之前先定義必要的宏。一般處理是在工程屬性中加上預(yù)處理器定義/D__STDC_LIMIT_MACROS /D__STDC_CONSTANT_MACROS這兩個(gè)宏是C標(biāo)準(zhǔn)里用來把C99的宏暴露給C的開關(guān)。msinttypes也遵守這個(gè)約定。加上之后C代碼中就能正常使用INT32_MAX和UINT64_C等宏了。如果仍報(bào)錯(cuò)檢查頭文件內(nèi)部是否還有__STDC_FORMAT_MACROS需要定義用于獲取printf格式化宏。這個(gè)宏在C里同樣默認(rèn)關(guān)閉忘記加的話PRId64會(huì)直接缺定義。5.4 坑4路徑配置正確但還是提示找不到stdint.h現(xiàn)象項(xiàng)目屬性里明明加了附加包含目錄編譯時(shí)依然報(bào)C1083。原因三種典型原因。一是配置的是“調(diào)試信息”而不是“包含路徑”二是配置的路徑下沒有對(duì)應(yīng)的文件比如解壓后把文件夾嵌套了一層三是IDE緩存問題清理不徹底。解決先用系統(tǒng)文件搜索確認(rèn)完整路徑比如D:\dev\include\msinttypes\stdint.h確實(shí)存在。然后檢查屬性頁(yè)的“附加包含目錄”填寫是否就是該目錄不要把“include\msinttypes”多寫或少寫。還要關(guān)注配置類型如果是同一個(gè)工程包含靜態(tài)庫(kù)和DLL項(xiàng)目每個(gè)子項(xiàng)目都要各自配置。最后嘗試關(guān)閉IDE重開或者清理中間文件。我甚至見過有人把路徑寫成了“D:\dev\include\stdint.h”少了msinttypes這一層自然找不到。這個(gè)坑看似低級(jí)但忙亂的時(shí)候很容易犯。5.5 坑5inttypes.h中的格式化宏導(dǎo)致printf警告現(xiàn)象用PRId64打印int64_t變量時(shí)編譯器報(bào)格式字符串參數(shù)類型不匹配的警告。警告級(jí)別開得越高報(bào)得越多甚至直接導(dǎo)致編譯失敗。原因老編譯器對(duì)printf系列的格式化檢查識(shí)別不了自定義宏或者宏展開后是“I64d”這種非標(biāo)準(zhǔn)格式編譯器的警告級(jí)別過高時(shí)會(huì)產(chǎn)生警告。這個(gè)警告不影響生成但會(huì)讓追求0警告的工程無(wú)法通過。解決把警告級(jí)別從/Wall降到/W3或者在包含inttypes.h之前定義__STDC_FORMAT_MACROS宏。在老編譯器上也可以直接使用系統(tǒng)擴(kuò)展格式符號(hào)比如printf(value%I64d\n, (long long)value);不過這樣就犧牲了可移植性。我的做法是在工程級(jí)統(tǒng)一加一個(gè)頭文件提前定義好宏并設(shè)置#pragma warning抑制掉特定的警告編號(hào)。記住一個(gè)原則寧可多花十分鐘把所有格式化宏檢查一遍也別等到集成測(cè)試時(shí)才發(fā)現(xiàn)日志亂碼。6. 進(jìn)階驗(yàn)證用靜態(tài)斷言確保類型寬度不再漂移6.1 在編譯期鎖死寬度當(dāng)你給老工程配上msinttypes之后編譯通過只是第一步我更建議在代碼里加上編譯期靜態(tài)斷言把類型寬度徹底鎖死。C語(yǔ)言中沒有標(biāo)準(zhǔn)靜態(tài)斷言可以用typedef數(shù)組技巧typedef char assert_int32_size[ (sizeof(int32_t) 4) ? 1 : -1 ];如果int32_t不是4字節(jié)這個(gè)typedef會(huì)定義一個(gè)長(zhǎng)度為-1的數(shù)組導(dǎo)致編譯錯(cuò)誤。C99后的編譯器可能支持_Static_assert但老編譯器不支持所以這個(gè)技巧更穩(wěn)。把它放在一個(gè)公共頭文件里所有模塊都會(huì)檢查到。6.2 用預(yù)處理宏輸出當(dāng)前環(huán)境在排錯(cuò)時(shí)經(jīng)常需要確認(rèn)當(dāng)前編譯器模式。以下代碼可以把關(guān)鍵宏打印在編譯輸出中#ifdef _MSC_VER #pragma message (MSVC version: STRINGIFY(_MSC_VER)) #endif需要配合一個(gè)字符串化宏#define STRINGIFY(x) #x這條pragma只在MSVC下生效GCC對(duì)應(yīng)的是#warning但也不妨礙體驗(yàn)。加上之后每次編譯都能看到版本號(hào)省去猜謎時(shí)間。6.3 我的自查流程從那以后我每次把一個(gè)新工程移植到Windows都會(huì)強(qiáng)制走一遍這個(gè)流程先跑一個(gè)最小的stdint驗(yàn)證程序確認(rèn)類型寬度再檢查INT32_MAX、PRId64這些宏是否可用最后用上面的靜態(tài)斷言把類型鎖死在公共頭文件里。等這一套做完我才敢繼續(xù)編譯主體代碼。經(jīng)歷過因?yàn)橐粋€(gè)類型寬度不一致導(dǎo)致整個(gè)協(xié)議棧解析錯(cuò)亂的事故就再也不想省這一步了。希望這些細(xì)節(jié)能幫到你讓你在Windows上遇到stdint.h問題時(shí)少走我當(dāng)初的彎路。本文還有配套的精品資源點(diǎn)擊獲取