March 28, 2021·5 min read·engineeringcstandardspointersprovenance

N2676 makes storage identity explicit in C pointers

The March provenance draft separates a pointer's address from the storage instance it may access. Its exposed-address model tries to reconcile compiler alias analysis with low-level pointer construction.

Two pointers can have the same representation without providing interchangeable access to an object. That is the problem behind the March draft of a provenance-aware memory object model for C. A numeric address does not describe the whole relationship between a pointer and the allocation from which it came. Optimizers already exploit that relationship, while programmers need a specification precise enough to explain which operations preserve it.

The WG14 document log lists N2676 on March 28. The draft itself carries a March 24 document date and the title "A Provenance-aware Memory Object Model for C". It is a draft technical specification, not a new edition of the C standard. Its authors are Jens Gustedt, Peter Sewell, Kayvan Memarian, Victor B. F. Gomes, and Martin Uecker.

The distinction between a draft model and an existing language requirement matters here. The paper proposes a way to reconcile existing compiler behavior with low-level C idioms. It also documents choices among competing models. Reading those choices as settled implementation guarantees would erase the uncertainty that the work is trying to resolve.

I find this work consistent with the part of C that requires the most care. The language is small, but staying within its defined behavior requires more than recognizing its syntax. A plausible machine-level explanation of a pointer operation can still omit assumptions the compiler uses when it transforms the surrounding program.

Equal bytes do not establish an allocation

The introductory example places two global integers in a situation where they may occupy adjacent addresses. One pointer is one past the first integer. Another points to the second. When the objects happen to be adjacent, comparing the pointer representations can find them identical. The example then attempts an access through the one-past pointer.

This is deliberately boundary-testing code. The paper does not recommend the idiom. Its purpose is to ask whether the representation comparison establishes that the pointer can access the neighboring object. A model in which every pointer is only an address makes that inference tempting. A provenance model keeps the first pointer associated with its original storage instance despite the address coincidence.

The distinction lets alias analysis reason about origin. If the pointer remains associated with the first integer's storage, an attempted access to the second integer through it falls outside that association. The compiler can then treat a program making that access as having undefined behavior. The paper reports different outcomes from existing compilers to demonstrate why a precise account is needed.

Importantly for anyone diagnosing this class of issue, the example does not depend on accessing an integer through an incompatible type. The draft explicitly distinguishes it from type-based alias analysis and notes that disabling strict aliasing does not change the reported GCC and ICC outcomes. A flag aimed at one class of alias assumption cannot be assumed to remove every other assumption about pointers.

Storage has identity as well as an address

The proposed abstract machine associates pointer values with a provenance and an address. A newly created storage instance receives an identifier unique across the execution. Its lifetime and identity remain distinct from its numeric location. Reusing an address after an allocation ends therefore does not recreate the earlier instance.

Pointer arithmetic preserves the association with the original instance. Access requires consistency between that association and the address, and the storage instance must still be live. This gives the model a way to express why moving a numeric address into another allocation does not automatically authorize access to that allocation. The association is semantic information used to explain program behavior.

Ordinary implementations need not store an extra identifier beside each machine pointer. The draft stresses that this is a description of the abstract machine. A compiler may reason about provenance during optimization without adding runtime metadata. That distinction prevents a common misunderstanding of the proposal as a requirement to enlarge every pointer representation.

The history predates this draft by many years. Its introduction points to WG14's 2004 discussion of defect report 260, which permitted implementations to distinguish pointers based on different origins even when their bit patterns matched. N2676 argues that this idea has not been integrated clearly and consistently into the standard. The draft attempts to supply the missing detail rather than introducing origin-based reasoning without precedent.

Integer round trips need their own account

Real C programs construct pointers through operations more varied than ordinary pointer arithmetic. The draft discusses conversions through integers and copying pointer representations. A model that only describes pointers copied as pointers would leave those practices unexplained. The hard part is allowing intended low-level operations without making every guessed address a valid route to every allocation.

N2676 follows the PNVI-ae-udi model. The PNVI family does not track provenance through integer values. Instead, conversion back to a pointer reconstructs provenance under conditions concerning a live storage instance. The address-exposed refinement restricts that reconstruction to storage whose address has already been exposed.

The draft identifies several exposing operations. Converting a pointer to an integer is one. Reading a pointer's representation through a non-pointer type and outputting it with %p are others. These rules give the model a record of which storage addresses the program has made available for subsequent reconstruction. The integer itself need not carry a hidden provenance tag through every arithmetic operation.

The user-disambiguation refinement addresses one-past pointers, including round trips through integers. Adjacent storage instances create ambiguity at their shared boundary, so an address check alone cannot always select the intended association. This is one reason the preferred model is more detailed than a general statement that integer casts preserve whatever the programmer meant.

I prefer an explicit account of those operations to relying on intuition about what a processor load will do. The practical question is whether the compiler must preserve the meaning intended by the source, and that question needs a language model. N2676 supplies examples and detailed semantics alongside proposed modifications to C17, so disagreement can be examined against particular rules and programs. Its separate technical specification format gives this work a defined document without claiming that every current C implementation already provides the proposed behavior.