Pointer provenance separates an address from a valid access
The C2x memory model proposal makes pointer origins explicit and examines how integer conversions can reconstruct valid pointers.
Two pointers can contain identical address bits while giving an optimizer different information about the objects they may access. That is the problem behind the memory object model study group's N2362 proposal, listed on April 2 in the WG14 document log. Its accompanying papers supply examples and detailed semantics. The work attempts to explain behavior already present in implementations while giving programmers a clearer account of which pointer manipulations remain valid.
The proposal concerns a part of C that I find especially demanding. Modern optimization makes the relationship between a source expression and a machine instruction indirect. Knowing an address is therefore insufficient when the compiler's argument also depends on the object from which a pointer originated.
A boundary address can have different origins
The examples paper N2363 explores cases where one object's end address coincides with another object's beginning. Consider the distinction between a pointer derived by advancing past an object and a pointer obtained directly from its neighbor. The numerical endpoint can coincide with the neighbor's starting address. That coincidence does not explain which object a subsequent dereference is allowed to access.
This distinction is independent of the types involved. Two objects can both have integer type, so an explanation based entirely on incompatible access types would miss the issue. An optimizer can still distinguish the storage from which the pointers arose. Disabling type-based alias analysis consequently does not supply a general solution to questions about pointer origins.
A simple array loop illustrates the useful role of an endpoint without relying on a coincidental neighbor. Under the existing pointer arithmetic rules in the C11 draft, an endpoint one past an array can control iteration:
int sum_four(const int values[4])
{
const int *end = values + 4;
int result = 0;
for (const int *p = values; p != end; ++p)
result += *p;
return result;
}
This example assumes the caller provides four accessible elements and that their sum fits in int. The loop forms its endpoint but never dereferences it. Its final increment advances from the last element to that endpoint, after which the comparison stops iteration. None of these operations requires deciding whether another object happens to occupy the next address.
Giving storage instances an identity
The proposal associates a pointer with a storage instance as well as an address. A fresh instance receives a fresh identity, even if its address matches an instance whose lifetime has ended. Pointer arithmetic preserves the original identity. An access must agree with that provenance and with the live storage it identifies.
This is a model of program meaning. It does not require normal implementations to enlarge every machine pointer with a runtime identity. The compiler can use provenance in its reasoning without placing that information into the pointer's object representation. A program that prints pointer bits is therefore examining a different thing from the complete abstract information used to justify an access.
The distinction also explains why allocator reuse cannot validate an obsolete pointer merely by returning the same address. A new allocation describes new storage. Equating its location with a previous allocation discards the lifetime distinction that the model is designed to preserve. Code that manages storage has to account for the allocation that currently exists.
I prefer an explicit model here because informal explanations tend to alternate between machine addresses and language objects without acknowledging the change. A documented identity rule makes the disagreement visible. It also gives an implementer a specific rule to defend when an optimization surprises a reader of the generated assembly.
Integers make the design harder
Casting a pointer to an integer raises a different problem. C programs use integer arithmetic to manipulate addresses, but tracking the origin of every integer would impose a much broader semantic burden. The study group examines models that preserve provenance through pointers without making ordinary integers carry it.
The detailed semantics paper N2364 distinguishes PNVI-plain, address-exposed variants and a provenance-via-integers model. These are competing descriptions of allowed behavior. Their differences concern how a later conversion back to a pointer obtains the necessary relationship with storage.
Exposure supplies a record that an allocation's address information has become available outside pointer operations. The preferred address-exposed design uses that fact when reconstructing provenance. An integer-to-pointer conversion cannot simply claim any convenient storage instance without regard to whether its address has become available in the required way.
This imposes a deliberate compromise. The model must support useful low-level conversions while allowing an optimizer to reason that an unexposed allocation cannot suddenly acquire an unrelated alias. Making all addresses reconstructible would weaken that argument. Tracking provenance through all integer computations would affect operations far removed from an actual memory access.
Why examples accompany the wording
The examples paper matters because a short rule can have unexpected consequences when pointers pass through representations. Copying a pointer value, copying its bytes and examining its bytes do not necessarily give the compiler the same information. A specification has to explain these cases instead of assuming that every operation is equivalent to copying a numerical address.
The endpoint case also creates an ambiguity for reconstruction. A single address may describe one allocation's one-past pointer and another allocation's first byte. A workable rule must say how the intended provenance is chosen and whether later uses remain consistent with that choice. Ordinary arithmetic on the integer address cannot by itself answer that question.
These are questions for systems code that implements allocators or interfaces with representations defined outside the immediate C expression. They also affect the claims a compiler can make when eliminating or reordering accesses. A model aimed only at ordinary array iteration would leave the difficult interoperation cases unexplained.
The April proposal remains proposed wording for C2x. Its practical contribution is a set of explicit distinctions against which pointer operations can be discussed. Address equality, storage lifetime and permission to access an object each require an explanation. The accompanying examples make those explanations inspectable at the exact operations where a conversion or dereference changes the question.