C语言如何在编译期导入二进制数据
本文最后更新于 2026年10月8日
在做某个 OS 实验的时候遇到了这个需求,实验指南建议使用 xxd 将二进制文件转换成 C/C++ 可以接受的常量数组
1 | |
但是这样会导致代码文件体积膨胀。
更优雅的办法是通过incbin直接将二进制数据放进ELF文件的只读区,NEMU和iPXE都使用了类似的方法。
1 | |
甚至可以用这个方法实现 quine
1 | |
需要注意的是这个方法在有些平台/编译器并不适用,比如MSVC x64 版不支持内联汇编。
这篇文章给出了更多的方式,比如 objcopy, Windows rc file
github上的incbin库考虑了多个平台多个编译器。
C23中引入了#embed宏,可以在编译时将指定内容设置为字符数组:
1 | |
还支持limit、prefix、suffix、if_empty等参数,例如:
1 | |
编译器支持方面,Clang 19和GCC
15起可用;C++26也采纳了#embed。
这个特性已经有了不少真实用户。GDAL用它内嵌各驱动的XML/CSV资源文件(有正式的RFC
102),PROJ用它内嵌proj.db数据库,pyodide用它内嵌初始化数据,ClassiCube用它内嵌Wii
U/PSVita/3DS平台的shader二进制。
需要注意的是,#embed本质上是把文件展开成逗号分隔的字节列表(等价于xxd
-i的预处理版),文件较大时编译时间和内存开销可观,大文件场景incbin这种直接塞进.rodata的方案仍然有优势。
另外值得一提的是,C++这边原本还有一个更强大的库版本提案std::embed(P1040):一个consteval函数,可以在constexpr求值时读文件,支持递归处理(嵌进去的文件还能再引用别的文件)。它没能进入C++26,主要卡在依赖追踪上——#embed是预处理指令,构建系统的依赖扫描器能直接看到;而std::embed的文件路径是编译期字符串,读取发生在constexpr求值阶段,扫描器无从得知,为此提案引入了#depend指令做补救,但递归glob、模块交互等问题一直没吵完。不过提案还在继续努力,2026年已投票转向C++29推进。