May 15, 2025·6 min read·engineeringcprovenancestandardsmemory

C pointer provenance gets its own technical specification

ISO/IEC TS 6010 formalizes pointer origins in the memory object model. Storage identity explains why matching addresses alone cannot establish a valid access.

Two pointers can have the same address while referring to different storage instances in C's abstract machine. That distinction helps compilers reason about which accesses can affect each other. It also explains why a numerical account of pointers is insufficient for some low-level programs. ISO/IEC TS 6010 now gives that problem a dedicated technical specification, with publication recorded on May 15.

WG14's project listing identifies the work as a provenance-aware memory object model. It is a separate specification that clarifies and constrains the memory model built on C17. Publication does not mean every C compiler has acquired a new runtime representation for pointers, or that the entire specification has become part of the core C standard.

C was my first language, and the distinction between its abstract semantics and machine instructions has become central to how I judge low-level code. A pointer printed as a familiar address can encourage reasoning that ignores object lifetime. Provenance gives a name and a model to the information that such reasoning leaves out.

The problem predates the specification

The public N3231 working draft, dated March 18, 2024, traces the issue to the committee's discussion of DR260 in 2004. Its introduction says implementations used provenance in alias analysis without a clear, consistently integrated definition in the language standard. The specification addresses a gap between optimization practice and the account programmers could derive from the text.

That history matters because provenance is sometimes discussed as though it were a newly added compiler restriction. The draft describes an effort to make existing assumptions precise. Formalizing them can still expose disagreements about particular programs, but the underlying optimization questions already existed. A program whose behavior depended on one compiler's treatment of those questions had not resolved the specification problem.

The draft credits earlier work by Jens Gustedt, Peter Sewell, Kayvan Memarian, Victor B. F. Gomes, and Martin Uecker. It distinguishes proposed changes to standard wording from examples and detailed semantics. Those are different kinds of evidence. Wording says what an implementation promises, while examples make it easier to see which familiar-looking operations depend on that promise.

A storage instance has an identity

In N3231, provenance is an abstract-machine entity associated with a pointer value. It is either empty or identifies a storage instance. The storage instance is the encompassing region of storage created by an object definition or an allocation. Its identity is unique across the execution, which makes identity more specific than a numerical address reused at different times.

The draft separates an object's lifetime from the bytes that may later occupy the same addresses. A live storage instance supplies the context for a valid object pointer. The pointer's address must also lie within that instance or one past its end. Both the identity and the address participate in the validity conditions described by the model.

This distinction provides a useful way to read code that retains a pointer after releasing storage. A later allocation may reuse an address. That reuse does not make the two allocations the same storage instance. Matching a logged address is consequently weaker evidence than establishing that a pointer refers to the correct live allocation.

The specification concerns abstract semantics. It does not require every ordinary pointer object to contain a separately stored provenance field. N3231 explicitly observes that pointer provenance and validity are generally not observable as additional representation bytes. Compiler reasoning can depend on abstract information without requiring that information to appear as another machine word in a structure.

One past an object is a distinct boundary

The earlier N2363 examples paper explores the adjacent-object case. A pointer derived as one past one object can happen to have the same representation as a pointer to a neighboring object. That coincidence creates a useful test of whether an implementation treats the pointers solely as numerical values.

The paper's example asks about writing through the one-past pointer when its representation matches the address of the neighbor. Its purpose is to examine the semantics, not to recommend that pattern as a programming idiom. The origin of the pointer remains relevant even where a representation comparison reports a match.

For application code, this makes the explanation more precise than saying that a pointer is simply an integer address. One-past values have a role in expressing boundaries. They do not automatically acquire the identity of a different object that happens to occupy the next address. The origin of an access cannot be established by a coincidence in placement alone.

The issue is distinct from whether a particular linker places two objects next to each other in one build. A test result observes one arrangement and one compiler's choices. It does not establish the complete set of behaviors a language specification permits. That is why the examples paper treats executable semantic models and ordinary compiler tests as different tools.

Exposing an address changes what analysis can assume

N3231 describes operations that expose a storage instance, including conversion of a pointer to an integer and certain reads of its representation. It also describes synthesized pointers, such as pointers constructed by conversion from an integer. These categories account for the low-level operations C programmers actually use instead of pretending that pointers never interact with bytes or integers.

Exposure matters to alias reasoning. A pointer obtained through an exposed address can reach storage in ways that a simple chain of pointer assignments does not describe. The draft marks exposure as irreversible within the execution. That makes it an event in the abstract machine, with implications for what the implementation can infer later.

The details also prevent an overly broad reading of provenance. The draft distinguishes reading a pointer through a compatible pointer representation from arbitrary byte manipulation or partial copying. Its rules do not treat every operation on a pointer object as the same event. The operation used to construct or inspect a value contributes to the semantic account.

That is a difficult requirement for systems code, but it reflects why the specification needs more than a sentence about pointer origins. C intentionally provides access to representations. A useful memory model must explain how those operations interact with object identity, including the cases where a value leaves an ordinary pointer-typed expression and later becomes a pointer again.

The model also distinguishes the address space within one execution from behavior across executions. N3231 describes addresses as constant over the relevant lifetime, not as a promise that a program will obtain the same addresses each time it runs. A stored diagnostic value and an active pointer consequently have different roles. The diagnostic can identify a location in an observed run. It cannot supply the lifetime relationship required to justify an access in another run. This is an implication of separating numerical location from the storage instance, and it is particularly relevant when reading logs that retain addresses longer than the objects themselves existed.

The specification has a defined scope

N3231 explicitly excludes subobject provenance. A structure can contain multiple member objects within the same encompassing storage instance, but the draft does not claim to settle every question about restricting pointers to individual members. That limit is material for code that reasons about enclosing allocations through pointers to parts of them.

The published specification builds on ISO/IEC 9899:2018 rather than replacing the complete language definition. Its status therefore needs to accompany technical discussion of its rules. A codebase cannot infer implementation support merely from the existence of the ISO publication, and a compiler's successful execution of an example does not establish conformance to the entire specification.

I welcome a specification that makes object identity explicit because it improves the questions that can be asked about an optimization. The relevant question becomes whether a transformation respects a stated memory model. A disagreement can then name a particular lifetime, conversion, or access instead of relying only on an intuition about what an address means.

For maintainers, the immediate value is explanatory. Pointer-heavy interfaces need to describe which allocation owns storage and how long that allocation remains live. TS 6010 supplies a formal context for those descriptions. The numerical address remains useful for examining a process, but the storage instance identifies the object to which the program's access rules apply.