Namespaces
Variants

Binary resource inclusion (since C23)

From cppreference.net

#embed 是一个用于在构建过程中包含(二进制)资源的预处理器指令,其中资源被定义为可从翻译环境访问的数据源。

目录

语法

#embed < h-字符序列 > 嵌入参数序列  (可选) 换行符 (1)
#embed " q-字符序列 " 嵌入参数序列  (可选) 换行符 (2)
#embed 预处理记号 换行符 (3)
__has_embed ( " q-字符序列 " 嵌入参数序列  (可选) )
__has_embed ( < h-字符序列 > 嵌入参数序列  (可选) )
(4)
__has_embed ( 字符串字面量 预处理平衡记号序列  (可选) )
__has_embed ( < h-预处理记号 > 预处理平衡记号序列  (可选) )
(5)
1) 搜索由 h-char-sequence 唯一标识的资源,并将该指令替换为对应资源数据的整型逗号分隔列表。
2) 搜索由 q-char-sequence 标识的资源,并将该指令替换为对应资源数据的整数列表。可能会回退到 (1)
3) 若既不符合 (1) 也不符合 (2) pp-tokens 将进行宏替换。替换后的指令会再次尝试与 (1) (2) 进行匹配。
4) 检查资源是否可用于嵌入、是否为空,以及传递的参数是否受实现支持。
5) (4) 不匹配,则 h-pp-tokens pp-balanced-token-sequence 将进行宏替换。替换后的指令将再次尝试与 (4) 进行匹配。
new-line - 换行符
h-char-sequence - 一个或多个 h-char 组成的序列,其中出现以下任意字符将导致未定义行为:
  • 字符 '
  • 字符 "
  • 字符 \
  • 字符序列 //
  • 字符序列 /*
h-char - 源字符集 中除换行符和 > 外的任意成员
q-char-sequence - 一个或多个 q-char 组成的序列,其中出现以下任意字符将导致未定义行为:
  • 字符 '
  • 字符 \
  • 字符序列 //
  • 字符序列 /*
q-char - 源字符集 中除换行符和 " 外的任意成员
pp-tokens - 一个或多个 预处理记号 组成的序列
string-literal - 字符串字面量
h-pp-tokens - 一个或多个 预处理记号 组成的序列(不包括 >
embed-parameter-sequence - 一个或多个 pp-parameter 组成的序列。注意与 attribute-list 不同,此序列不以逗号分隔。
pp-parameter - attribute-token (参见: attributes ),但由预处理记号而非记号构成
pp-balanced-token-sequence - balanced-token-sequence (参见: attributes ),但由预处理记号而非记号构成

说明

1) 以实现定义的方式搜索由 h-char-sequence 标识的资源。
2) 以实现定义的方式搜索由 q-char-sequence 标识的资源。对于 (1,2) ,实现通常使用与 源文件包含 所用的实现定义搜索路径类似但不同的机制。标准中的某个示例出现了 __has_embed ( __FILE__ ... 构造,这暗示至少对于情况 (2) ,期望搜索当前文件所在的目录。
3) 指令中 embed 后的预处理记号会像普通文本一样被处理(即每个当前定义为宏名的标识符会被其预处理记号的替换列表所替换)。所有替换后产生的指令必须符合前两种形式之一。在 < > 预处理记号对之间,或一对 " 字符之间的预处理记号序列如何组合成单个头文件名预处理记号,是由实现定义的。
4) h-char-sequence q-char-sequence 标识的资源,其搜索方式如同该预处理记号序列是语法 (3) 中的 pp-tokens ,但不再进行进一步的宏展开。若此类指令不满足 #embed 指令的语法要求,则程序非良构。 __has_embed 表达式在以下情况分别求值为:当资源搜索成功、资源非空且所有参数受支持时为 __STDC_EMBED_FOUND__ ;当资源为空且所有参数受支持时为 __STDC_EMBED_EMPTY__ ;当资源搜索失败或传入参数不被实现支持时为 __STDC_EMBED_NOT_FOUND__
5) 仅当语法 (4) 不匹配时才会考虑此形式,此时预处理标记的处理方式与普通文本完全相同。

当资源未找到或某个参数不被实现支持时,程序属于非良构。

__has_embed 可在 #if #elif 的表达式中展开。它会被 #ifdef #ifndef #elifdef #elifndef 以及 defined 视为已定义的宏,但不能在其他任何地方使用。

资源具有 实现资源宽度 ,即实现定义的定位资源的位大小。其 资源宽度 默认为实现资源宽度,除非被 limit 参数修改。若资源宽度为0,则该资源被视为空资源。 嵌入元素宽度 等于 CHAR_BIT ,除非被实现定义参数修改。资源宽度必须可被嵌入元素宽度整除。

#embed 指令的展开是一个由下述整数 常量表达式 列表构成的记号序列。列表中每个整数常量表达式对应的记号组,会通过逗号与前一整数常量表达式的记号组分隔。该序列既不以逗号开头也不以逗号结尾。若整数常量表达式列表为空,则记号序列为空。该指令会被其展开内容替换,并且在存在特定嵌入参数时,还会包含额外或替代的记号序列。

扩展序列中整型常量表达式的值由资源数据的实现定义映射所确定。每个整型常量表达式的值在区间 [ 0 , 2 embed element width ) 内。若满足:

  1. 整数常量表达式列表用于初始化与 unsigned char 兼容类型的数组,或者当 char 无法容纳负值时与 char 兼容类型的数组,且
  2. 嵌入元素的宽度等于 CHAR_BIT

那么数组已初始化元素的内容就如同资源的二进制数据在翻译时被 fread 读入数组一般。

鼓励实现同时考虑翻译时的位序和字节序以及执行时的位序和字节序,以更准确地表示指令中资源的二进制数据。这能最大化保证:若通过 #embed 指令在翻译时引用的资源与通过执行时方式访问的资源相同,则通过例如 fread 或其他方式读入连续存储的数据,将与由 #embed 指令扩展内容初始化的字符类型数组进行逐位比对时完全一致。

参数

标准定义了参数 limit prefix suffix if_empty 。指令中出现的任何其他参数必须是实现定义的,否则程序格式错误。实现定义的嵌入参数可能会改变指令的语义。

限制

limit( 常量表达式 ) (1)
__limit__( 常量表达式 ) (2)

limit 嵌入参数在嵌入参数序列中最多只能出现一次。它必须带有一个参数,该参数必须是求值为非负数的整型(预处理器) 常量表达式 ,且不能包含 defined 标记。资源宽度被设置为该整型常量表达式乘以嵌入元素宽度与实现资源宽度两者中的较小值。

suffix

suffix( 预处理平衡记号序列  (可选) ) (1)
__suffix__( 预处理平衡记号序列  (可选) ) (2)

suffix 嵌入参数在嵌入参数序列中最多只能出现一次。它必须包含一个(可能为空的)预处理器参数子句。若资源非空,该参数子句的内容会被直接放置在指令展开结果之后。否则,该参数不会产生任何效果。

prefix

prefix( pp-balanced-token-sequence  (可选) ) (1)
__prefix__( pp-balanced-token-sequence  (可选) ) (2)

prefix 嵌入参数在嵌入参数序列中最多只能出现一次。它必须包含一个(可能为空的)预处理器参数子句。如果资源非空,参数子句的内容会被直接放置在指令展开之前。否则,该参数不会产生任何效果。

if_empty

if_empty( pp-balanced-token-sequence  (可选) ) (1)
__if_empty__( pp-balanced-token-sequence  (可选) ) (2)

if_empty 嵌入参数在嵌入参数序列中最多只能出现一次。它必须包含一个(可为空的)预处理器参数子句。若资源为空,该参数子句的内容将替换指令;否则不产生任何效果。

示例

#include <stdint.h>
#include <stdio.h>
const uint8_t image_data[] =
{
#embed "image.png"
};
const char message[] =
{
#embed "message.txt" if_empty('M', 'i', 's', 's', 'i', 'n', 'g', '\n')
,'\0' // 空终止符
};
void dump(const uint8_t arr[], size_t size)
{
    for (size_t i = 0; i != size; ++i)
        printf("%02X%c", arr[i], (i + 1) % 16 ? ' ' : '\n');
    puts("");
}
int main()
{
    puts("image_data[]:");
    dump(image_data, sizeof image_data);
    puts("message[]:");
    dump((const uint8_t*)message, sizeof message);
}

可能的输出:

image_data[]:
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
00 00 00 01 00 00 00 01 01 03 00 00 00 25 DB 56
...
message[]:
4D 69 73 73 69 6E 67 0A 00

参考文献

  • C23 标准 (ISO/IEC 9899:2024):
  • 6.4.7 头文件名 (p: 69)
  • 6.10.1 条件包含 (p: 165-169)
  • 6.10.2 二进制资源包含 (p: 170-177)

参见

C++ documentation for 资源包含 (自 C++26 起)