yohhoyの日記

技術的メモをしていきたい日記

#embedディレクティブ

次期C2x(C23)言語仕様に追加される#embedディレクティブについて。外部ファイルをバイナリデータとしてプログラムに埋込む機能。

下記コードは、外部PNGファイル内容を生成プログラム中のuint8_t型配列として埋め込む例*1 *2。C17現在は外部ツール*3を用いたビルドステップで対応しているものが、C2x以降はC言語処理系(プリプロセッサ)のみで実現される。

// C2x
#include <stdint.h>  // uint8_t

#if __has_embed("resource/icon.png")
constexpr uint8_t icon[] = {
#embed "resource/icon.png"
};
static_assert(
  (icon[0]==137 && icon[1]=='P' && icon[2]=='N' && icon[3]=='G'
   && icon[4]==13 && icon[5]==10 && icon[6]==26 && icon[7]==10),
  "invalid PNG format");
// https://www.w3.org/TR/PNG-Structure.html
#else
#error "icon resource not found!"
#endif

// icon[] == PNG圧縮画像データ列

まとめ:

  • #emdedディレクティブ
    • 指定されたファイル(<path>"path"またはマクロ置換結果)の中身を読み取り、コンマ区切りの整数定数リストへと展開する。
    • C2x標準は下記パラメータ4種類の追加指定をサポートし、また処理系定義(implementation-defined)のパラメータ定義を許容する。
    • limit(N):展開されるバイナリデータをNバイト以下に制限する。
    • prefix(tokens):展開後リストの直前にtokensを配置する。バイナリデータ長=0の場合は何もしない。
    • suffix(tokens):展開後リストの直後にtokensを配置する。バイナリデータ長=0の場合は何もしない。
    • if_empty(tokens):バイナリデータ長=0の場合にtokensを代替配置する。それ以外は何もしない。
  • __has_embed
    • 指定ファイルの有無、空(empty)のバイナリデータを判定するプリプロセッサ式。
    • 0:指定ファイルが見つからない。
    • 1:指定ファイルが存在し、バイナリデータは空ではない。
    • 2:指定ファイルが存在し、バイナリデータは空(サイズ0)。
  • おまけ:C++標準に対してもP1967が提案されている。2022年9月現在の検討状況より、C++2c(C++26)以降での導入が想定される。

ホストプログラム中に外部ファイルに記述されたGLSLシェーダプログラムを埋め込む例(提案文書N3017より改変引用):

#define SHADER_TARGET "ches.glsl"
extern char* merp;

void init_data () {
  const char whl[] = {
#embed SHADER_TARGET \
    prefix(0xEF, 0xBB, 0xBF, ) /* UTF-8 BOM */ \
    suffix(,)
    0
  };
  // always null terminated,
  // contains BOM if not-empty
  int is_good = (sizeof(whl) == 1 && whl[0] == '\0')
    || (whl[0] == '\xEF' && whl[1] == '\xBB'
      && whl[2] == '\xBF' && whl[sizeof(whl) - 1] == '\0');
  assert(is_good);
  strcpy(merp, whl);
}
#define SHADER_TARGET "edith-impl.glsl"
extern char* null_term_shader_data;

void fill_in_data () {
  const char internal_data[] = {
#embed SHADER_TARGET  \
    suffix(, 0) \
    if_empty(0)
  };
  strcpy(null_term_shader_data, internal_data);
}

関連URL

*1:コンパイル時constexpr定数は提案文書 N3018 にてC2x導入予定。コンパイル時constexpr関数をもつC++とは異なり、C2x時点ではconstexpr定数のみが導入される。

*2:C2x仕様には(PDF)N2934が採択され、C++言語と同じ static_assert キーワードが導入される。一方で、C11からの _Static_assert は廃止予定の機能(obsolescent feature)となる。また例示コードでは利用していないが、(PDF)N2265の採択により理由文字列もC2xから省略可能となる。C++17以降の static_assert と同等。

*3:例えば https://linux.die.net/man/1/xxd などでバイナリファイルをC言語用の変数定義へと変換可能。