C语言如何在编译期导入二进制数据

本文最后更新于:2025年1月8日

在做某个 OS 实验的时候遇到了这个需求,实验指南建议使用 xxd 将二进制文件转换成 C/C++ 可以接受的常量数组

1
2
3
4
5
6
7
8
// Generated by: xxd -i /bin/ls
unsigned char _bin_ls[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
...
0x00, 0x00, 0x00, 0x00
};
unsigned int _bin_ls_len = 142144;

但是这样会导致代码文件体积膨胀。

更优雅的办法是通过incbin直接将二进制数据放进ELF文件的只读区,NEMUiPXE都使用了类似的方法。

1
2
3
4
5
6
7
#define EMBED_STR(name, path)                \
extern const char name[]; \
asm(".section .rodata, \"a\", @progbits\n" \
#name ":\n" \
".incbin \"" path "\"\n" \
".byte 0\n" \
".previous\n");

甚至可以用这个方法实现 quine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#define EMBED_STR(name, path) \
extern const char name[]; \
asm(".section .rodata, \"a\", @progbits\n" \
#name ":\n" \
".incbin \"" path "\"\n" \
".byte 0\n" \
".previous\n");

EMBED_STR(SourceFile, __FILE__);

int main() {
printf("%s", SourceFile);
return 0;
}

需要注意的是这个方法在有些平台/编译器并不适用,比如MSVC x64 版不支持内联汇编。

这篇文章给出了更多的方式,比如 objcopy, Windows rc file

github上的incbin库考虑了多个平台多个编译器。

C23中引入了#embed宏,可以在编译时将指定内容设置为字符数组:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
const unsigned char data[] = {
#embed "data.bin"
};
return 0;
}

c++也有std::embed#depend相关的提案。