C++26 加入 #embed,內嵌二進位檔不必再靠 xxd 產生標頭
P1967R14 · 2025-02-14(Sandor Dargo 於 2026-08-05 撰文介紹)
C++26 新增 #embed 前置處理指令,讓編譯器直接把二進位檔展開成整數陣列初始值,取代過去用 xxd -i、objcopy 或 CMake 腳本先產生 .h 再 #include 的做法。提案是 P1967R14,作者為 JeanHeyd Meneide,Sandor Dargo 的文章整理了用法與編譯器支援現況。
原本的問題
C++ 沒有內建方法把檔案內容放進程式。常見繞路是用 外部工具把檔案轉成 C 陣列原始碼,例如 xxd -i,或用 objcopy、資源編譯器連結成目的檔。這些方法要額外維護 build 規則,來源檔改了卻忘了重新產生時,產物就會過期。
另一個代價是編譯成本。xxd 產生的標頭是一大串逐字元的字面值,編譯器得把每個元素當成一般 token 解析。P1967R14 實測 40 MB 檔案:#embed 在 GCC 約 1.069 秒、記憶體約 341.72 MB;xxd 產生的標頭在 GCC 要 225.290 秒、約 12,622 MB,在 Clang 要 83.250 秒、約 7,116 MB。論文註明記憶體數字是 OS 定期取樣,只是近似值。
核心改動
#embed 會展開成以逗號分隔的整數常數運算式清單,所以放在陣列初始值的大括號內即可。雙引號形式先搜尋原始檔所在目錄,角括號形式搜尋系統路徑。
| 舊做法 | C++26 | |
|---|---|---|
| 流程 | build 時 xxd -i icon.png > icon.h,再 #include "icon.h" | 原始碼內直接寫 #embed |
| 相依追蹤 | 靠 build 規則自行維護 | 編譯器看得到檔案 |
const unsigned char icon[] = {
#embed "icon.png"
};指令還有幾個參數:limit(n) 限制最多取幾個元素,適合只讀檔頭;prefix(...) 與 suffix(...) 在資源非空時於資料前後補 token;if_empty(...) 則在檔案存在但是零位元組時提供替代內容,此時 prefix 與 suffix 都不會生效。prefix 結尾與 suffix 開頭的逗號要自己寫,否則 token 會黏在一起。
const unsigned char data[] = {
#embed "payload.bin" prefix(0xAA, 0xBB,) suffix(, 0xCC, 0xDD)
};另有 __has_embed 可在前置處理階段判斷資源狀態,回傳 __STDC_EMBED_NOT_FOUND__、__STDC_EMBED_FOUND__ 或 __STDC_EMBED_EMPTY__,可用來寫「有檔案就內嵌,沒有就用預設值」的分支。
影響範圍
編譯器支援還不齊。依 Dargo 文章,GCC 15 在 -std=c++26 下完整支援;Clang 19 以上可用,但在 C++ 模式下被視為 C23 擴充,會有警告,可用 -Wno-c23-extensions 關掉;MSVC 尚未支援。需要跨編譯器的專案要保留舊流程當後備,或用 __has_embed 與巨集分流。
- 內嵌憑證、小圖示、預設設定檔、韌體檔頭的專案:可移除 xxd 或自製腳本步驟,少一個 build 目標。
- CMake 專案:產生標頭的
add_custom_command可以拿掉,但路徑要確認 include 目錄設定能讓編譯器找到資源檔。 - 目前仍以 MSVC 為主要編譯器的團隊:暫時不能採用。
要注意的是,Dargo 文章提到 P1967R14 以 100 MB 檔案比較,但我實際查閱論文,只看到 40 KB 到 40 MB 的數據,沒有 100 MB,所以本文採用論文原有的數字。