C++26: #embed

Lobsters Hottest 新闻

摘要

C++26 introduces the #embed preprocessor directive, allowing binary files to be embedded directly at compile time without external tools, simplifying resource inclusion in C++ programs.

<p><a href="https://lobste.rs/s/0j5jx5/c_26_embed">Comments</a></p>
查看原文
查看缓存全文

缓存时间: 2026/08/05 18:00

# C++26: #embed Source: [https://www.sandordargo.com/blog/2026/08/05/cpp26-embed](https://www.sandordargo.com/blog/2026/08/05/cpp26-embed) If you’ve ever needed to ship a binary file — a certificate, a small image, a default configuration — inside a C\+\+ program, you know the ritual\. You find or write a tool that converts the file into a C array, you wire it into your build system, and you pray that nobody forgets to re\-run the conversion after updating the original file\. C\+\+26 ends this with`\#embed`\([P1967R14](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p1967r14.html)by[JeanHeyd Meneide](https://thephd.dev/about/)\)\. Think of it as`\#include`for binary data — a preprocessor directive that turns a file into a comma\-separated sequence of integer constant expressions, directly at compile time, with no external tools\. Before`\#embed`, every project rolled its own approach —`xxd \-i`,`objcopy`, linker tricks, Python scripts, CMake`file\(READ\)`— each fragile, platform\-specific, and one forgotten regeneration away from being stale\. `\#embed`is also a perfect example of how long the standardization process can take\. The first revision of P1967 was submitted in 2020, and it took fourteen revisions over five years before the committee voted it in\. Along the way the syntax changed substantially as the design searched for consensus\. And P1967 itself was a restart — earlier proposals like[P1040](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1040r4.html)\(`std::embed`\) pushed for a non\-preprocessor approach, embedding resources through a`constexpr`function rather than a directive\. That path didn’t find enough support, and JeanHeyd eventually pivoted to the preprocessor\-based design that made it through\. ## The syntax `\#embed`is a preprocessor directive\. At its simplest: `1 2 3 const unsigned char icon\[\] = \{ \#embed "icon\.png" \};` The directive reads`icon\.png`and expands to a comma\-separated list of integer constant expressions, one per byte\. Each value is in the range \[0, 255\] \(assuming`CHAR\_BIT == 8`, which it is on every platform you care about\)\. The result is exactly what`xxd \-i`would have produced — but without the extra tool, the build step, or the generated file\. The resource identifier follows the same rules as`\#include`: double quotes search implementation\-defined paths \(typically starting with the source file’s directory\), and angle brackets search the system include paths: `1 2 \#embed <default\_config\.json\> // system resource path \#embed "local\_asset\.bin" // local path first` ## Embed parameters What makes`\#embed`more than a built\-in`xxd`are its four standard parameters\. They are specified in parentheses after the resource identifier, using a syntax borrowed from attributes\. ### `limit` Restricts how many elements are produced: `1 2 3 const unsigned char header\[\] = \{ \#embed "firmware\.bin" limit\(64\) \};` This embeds only the first 64 bytes\. Useful for pulling in just a file header, a magic number, or a fixed\-size prefix without embedding the entire resource\. ### `prefix`and`suffix` Prepend or append token sequences — but only when the resource is non\-empty: `1 2 3 const unsigned char data\[\] = \{ \#embed "payload\.bin" prefix\(0xAA, 0xBB,\) suffix\(, 0xCC, 0xDD\) \};` If`payload\.bin`contains bytes`\{0x01, 0x02\}`, this expands to`\{0xAA, 0xBB, 0x01, 0x02, 0xCC, 0xDD\}`\. If the file is empty, the prefix and suffix are silently omitted — you get an empty initializer, not a stray comma\. Note the trailing comma in`prefix\(0xAA, 0xBB,\)`and the leading comma in`suffix\(, 0xCC, 0xDD\)`\. These aren’t typos — they’re necessary because`\#embed`expands to a token sequence that sits between the prefix and suffix\. Without the trailing comma in the prefix, the last prefix token and the first embedded byte would be concatenated incorrectly\. ### `if\_empty` Provides fallback content when the resource exists but has zero bytes: `1 2 3 const unsigned char config\[\] = \{ \#embed "user\_overrides\.cfg" if\_empty\('\{', '\}'\) \};` If`user\_overrides\.cfg`is empty, you get`\{'\{', '\}'\}`— a minimal valid JSON object as raw bytes\. If the file has content,`if\_empty`is ignored\. Note that when`if\_empty`applies,`prefix`and`suffix`are also suppressed — you get exactly the`if\_empty`tokens and nothing else\. ## `\_\_has\_embed` You might not have seen this pattern before, but`\#include`actually has a companion preprocessor test too —`\_\_has\_include`, available since C\+\+17\. Most of us never needed it because we control our own includes\.`\#embed`gets the same treatment with`\_\_has\_embed`, and here it’s more likely to be useful: the resource you want to embed might genuinely not exist in all build environments\. `\_\_has\_embed`lets you check whether a resource exists and whether it has content — before trying to embed it: `1 2 3 4 5 6 7 8 \#if \_\_has\_embed\("branding\.png"\) const unsigned char branding\[\] = \{ \#embed "branding\.png" \}; \#else // fall back to a compiled\-in default const unsigned char branding\[\] = \{ /\* \.\.\. \*/ \}; \#endif` `\_\_has\_embed`returns one of three values: MacroValueMeaning`\_\_STDC\_EMBED\_NOT\_FOUND\_\_`0Resource not found`\_\_STDC\_EMBED\_FOUND\_\_`1Found, non\-empty`\_\_STDC\_EMBED\_EMPTY\_\_`2Found, but empty Since`\_\_STDC\_EMBED\_NOT\_FOUND\_\_`is 0 and the other two are truthy, a plain`\#if \_\_has\_embed\(\.\.\.\)`covers the common case of “embed if available\.” If you need to distinguish between found\-empty and found\-with\-content, compare against the specific macros\. `\_\_has\_embed`also accepts the same parameters as`\#embed`\. This matters because some parameters can affect whether the result is considered “empty\.” For example,`\_\_has\_embed\("data\.bin" limit\(0\)\)`returns`\_\_STDC\_EMBED\_EMPTY\_\_`regardless of the file’s actual size — you asked for zero bytes\. ## A realistic example Here’s a pattern I like: embedding a default configuration that the program can use if no external config file is found at runtime\. `1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // https://godbolt\.org/z/Wb339WTzj \#include <cstdint\> \#include <fstream\> \#include <iostream\> \#include <iterator\> \#include <vector\> constexpr unsigned char default\_config\[\] = \{ \#embed "defaults\.json" \}; std::vector<uint8\_t\> load\_config\(const char\* path\) \{ std::ifstream file\(path, std::ios::binary\); if \(\!file\) \{ return \{std::begin\(default\_config\), std::end\(default\_config\)\}; \} return \{std::istreambuf\_iterator<char\>\(file\), std::istreambuf\_iterator<char\>\(\)\}; \} int main\(\) \{ auto config = load\_config\("config\.json"\); std::cout << "Loaded " << config\.size\(\) << " bytes of configuration\\n"; \}` No build system plumbing, no code generation step, no out\-of\-sync risk\. The default configuration is baked into the binary at compile time, available as a`constexpr`array\. ## Compiler support and compile times GCC 15 has full support \(`\-std=c\+\+26`\)\. Clang 19\+ supports`\#embed`but treats it as a C23 extension in C\+\+ mode — add`\-Wno\-c23\-extensions`to suppress the warning\. MSVC does not support it yet\. On Compiler Explorer, you can try it with GCC 15\+ or look for “x86\-64 clang \(thephd\.dev\)” in the compiler list — JeanHeyd Meneide’s custom Clang build has had`\#embed`support for years\. A natural concern with large files: does the compiler have to pretend it’s parsing ten million integer literals? In principle, yes —`\#embed`is defined as expanding to a comma\-separated list\. But in practice, both GCC and Clang fast\-path it internally\. According to the benchmarks shared in[P1967R14](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p1967r14.html), GCC embeds a 100 MB file in about 1\.3 seconds using 117 MiB of RAM; the equivalent`xxd`\-generated header takes 139 seconds and 13 GiB\. ## Conclusion `\#embed`is one of those features that makes you wonder why it took so long\. Embedding binary resources into a C or C\+\+ program was a solved problem for decades — just not solved*in the language*\. Every project had its own incantation of`xxd`,`objcopy`, or custom scripts, each with its own portability and staleness issues\. With`\#embed`, the resource is part of the source\. The compiler reads it, the build system doesn’t need to know about it, and there’s no generated file to keep in sync\. The parameters —`limit`,`prefix`,`suffix`,`if\_empty`— handle the framing and fallback patterns that always required wrapper scripts before\. It’s a small addition syntactically, but it removes an entire category of build system complexity\. ## Connect deeper If you liked this article, please - hit on the like button, - [subscribe to my newsletter](https://sandor-dargo.kit.com/e19f29b0a1) - and let's connect on[Twitter](https://twitter.com/SandorDargo)\! - if you're preparing for a C\+\+ quant/trading interview, check out[GetCracked](https://www.getcracked.io/?via=sandor)

相似文章

意图原型:嵌入API

Lobsters Hottest

Chromium团队为Web平台提出了一种新的嵌入API,允许开发者利用Chrome的AI基础设施在设备端生成向量嵌入,从而实现保护隐私的语义搜索、检索增强生成和内容聚类,同时降低延迟和成本。

Embedist

Product Hunt

Embedist是一款开源的AI原生嵌入式开发环境工具,在Product Hunt上发布,专为从事嵌入式系统开发的工程师设计。

C++26:新的调试头文件

Hacker News Top

C++26 引入了新的 <debugging> 头文件,提供 std::breakpoint()、std::is_debugger_present() 和 std::breakpoint_if_debugging(),通过提案 P2546R5 和 P2810R4 实现标准化,以取代平台特定的调试宏。