June 27, 2022·5 min read·engineeringcembedstandards

N3017 makes binary resources visible to the C preprocessor

The revised embed proposal addresses the cost of generated initializer lists without hiding resource dependencies in semantic analysis. Its June revision also clarifies how unsupported parameters and empty resources should behave.

Embedding a binary resource in a C program often starts by converting it into source code. That introduces work unrelated to the resource's meaning. A compiler must tokenize and parse a long list of integer literals simply to reconstruct bytes that already existed in a file. JeanHeyd Meneide's N3017, published on June 27, proposes a preprocessing directive that makes the binary resource itself an input to translation.

The proposal's placement in the preprocessor is central to its design. Earlier approaches explored treating embedding as an expression, but that could delay discovery of file dependencies until semantic analysis. A build system needs to know which inputs affect a compilation. N3017 keeps the resource name in a position that scanning tools can find without evaluating arbitrary program expressions.

The WG14 document log lists this as revision 7. It remains a proposal, with some parts of its wording explicitly optional for separate committee decisions. Describing the whole document as a settled language facility would obscure questions the paper still asks the committee to resolve.

An initializer with a resource dependency

The basic proposed use places #embed inside an initializer. The directive produces values as though the resource had been written as a comma-separated sequence of integer constants suitable for an unsigned character array. This example assumes a nonempty resource and an implementation of the proposal.

static const unsigned char startup_data[] = {
#embed "startup.bin"
};

The surrounding declaration controls the array's storage and qualification. Embedding does not introduce a separate kind of runtime object with its own ownership rules. The program receives an ordinary initialized array. The resource is needed during translation, so the program does not need to open that file merely to access these compiled-in values at runtime.

The paper's performance argument concerns translation. Generated lists create many tokens and corresponding compiler representations. A compiler that recognizes the resource directly can avoid much of that intermediate work. N3017 includes experimental results comparing its implementation with generated initializer lists, and qualifies its memory measurements as approximate operating-system observations. Those results support the motivation without establishing a universal compilation-time ratio.

I find this a persuasive addition to C because the abstraction is narrow. It names an external input and supplies initialization data. The surrounding language still controls what object receives the values and how the program uses that object.

Dependency discovery constrains the syntax

The resource name appears first, delimited by quotes or angle brackets. Parameters follow it. The paper explains that this order lets simple scanning tools locate the dependency before dealing with extensions or parameter arguments. Search paths remain implementation-defined, following the broad approach already familiar from header inclusion.

That decision reflects problems with an earlier expression-based design. If file names could be computed through general compile-time programming, one embedded resource could determine the name of another. Discovering the full dependency graph would then require executing more of the compiler's semantic work. The proposal describes tool vendors' objections to that cost.

Preprocessing therefore limits a form of expressiveness in exchange for earlier discoverability. This is relevant to distributed compilation as well as incremental builds. A system that sends preprocessed input elsewhere needs a way to represent all the resources the compilation consumes. Keeping embedding in the preprocessing model gives implementations a place to integrate it with existing dependency output.

Parameters cannot disappear silently

N3017 uses named parameters and permits vendor-qualified names. This resembles some existing attribute syntax, but the June revision deliberately stops calling these parameters attributes. An unknown parameter must produce a constraint violation instead of being silently ignored.

That distinction protects the meaning of the initializer. A parameter can change how a resource is read or what values it produces. Ignoring such a parameter could build an executable with different data from the data the source requested. The paper therefore treats recognition of the parameter as part of accepting the directive.

The proposed __has_embed facility supplies a way to ask about support. Its discussion distinguishes a missing resource or unsupported parameter from a resource that is available with supported parameters. The proposal also explores a separate return value for an empty resource, with that part of the C wording marked optional. Capability detection and empty-resource handling remain related but distinct design questions.

Limits and empty inputs have different meanings

The limit parameter bounds how many elements embedding may produce. N3017 motivates it partly through devices and pseudo-files with no natural finite end. A translation that attempts to read such a resource completely could otherwise exhaust memory. The limit is an upper bound, so a shorter resource need not produce the requested number of elements.

The paper explicitly says this is not a timeout. Limiting the number of values does not bound how long a resource takes to locate or read. An implementation supporting slow remote resources would need a separate extension for inactivity or elapsed-time limits. That is a precise separation between data quantity and availability.

Empty input causes another problem because punctuation inside an initializer depends on whether embedding produces values. N3017 includes optional prefix and suffix parameters that contribute tokens only for nonempty input. An if_empty parameter supplies replacement tokens when the resource is empty. The June revision renames the earlier is_empty spelling to if_empty in response to committee feedback.

These parameters do more than save a conditional. They let punctuation follow the existence of actual initializer elements. A suffix containing a comma can separate embedded values from an unconditional terminator without leaving a leading comma when the resource is empty. The alternative discussed in the paper repeats the resource inquiry through __has_embed and branches explicitly.

My preference is for the proposal to specify these cases before applications depend on them. Empty files are ordinary inputs, and a source-level resource mechanism needs a defined way to represent them. The paper's optional wording makes the remaining committee choice visible instead of presenting one branch of that choice as already decided.

Binary inclusion does not decode a file format

The proposed directive exposes resource values during translation. It does not automatically interpret a file's multibyte fields as native integers. N3017 discusses host and target differences, including endianness and differing character widths, and compares the intended result with reading the same resource as binary data.

For a program embedding a serialized format, field decoding therefore remains part of that format's contract. An unsigned character array is useful precisely because it preserves a byte-oriented representation without pretending the resource was a native structure. The proposal allows ordinary declaration mechanisms, such as an implementation's alignment attributes, to apply to that array. Embedding supplies the initialization values while the declaration continues to describe the object that contains them.