C Mastery / Pointer Provenance and Pointer Validity
Part 2 — The Object Model and Undefined Behavior

Pointer Provenance and Pointer Validity

This chapter explains pointer provenance — the idea that a pointer is not just a numeric address but also carries information about which object it was derived from. This is an expert-level topic that explains why certain "clever" pointer tricks are undefined behavior even when the addresses "look right."

Why This Matters

Treating pointers as integers is the root of many subtle bugs and optimizer surprises. Provenance is the formal reason why two pointers with the same bit pattern can still be different, and why integer-to-pointer round-trips are dangerous.

Prerequisites

Core Concept

A pointer is more than an address

Informally, a pointer value consists of:

Pointer arithmetic is only defined *within* a single array object (plus one past the end). The provenance records which array that is. Two pointers with identical addresses but different provenance are not interchangeable for arithmetic or dereference.

Why provenance exists

Consider:

int a[1], b[1];
int *p = a + 1;       /* one-past-the-end of a */
int *q = b;           /* points to b[0] */

On many systems, p and q might happen to have the same address, but p cannot legally be dereferenced or used for further arithmetic, while q can. Provenance captures this distinction even though the address bits are equal.

Integer round-trips lose provenance

Converting a pointer to uintptr_t and back to a pointer is not guaranteed to recover a usable pointer in all cases; the round-trip preserves the *address* but provenance is implementation-defined and may be lost. In practice, round-tripping through uintptr_t is widely supported, but using the result for anything other than the original object is dangerous.

Syntax

#include <stdint.h>

uintptr_t i = (uintptr_t)p;   /* implementation-defined */
int *q = (int *)i;            /* implementation-defined; provenance lost */

Examples

One-past-the-end vs. valid pointer

int a[5];
int *end = a + 5;    /* valid to form, not to dereference */
int *bad = a + 6;    /* UB: outside array even to form */

Integer round-trip (implementation-defined)

#include <stdint.h>
#include <stdio.h>

int main(void)
{
    int x = 5;
    uintptr_t addr = (uintptr_t)&x;
    int *p = (int *)addr;      /* usually works, but not strictly portable */
    printf("%d\n", *p);
    return 0;
}

On mainstream hosted systems this "works," but the standard does not fully guarantee the round-trip's usability. IMPLEMENTATION-DEFINED

How It Works

The C standard describes pointers in terms of the objects they point into. The compiler's optimizer tracks provenance to prove things like "this loop does not access outside the array." When you manufacture a pointer from an integer, the compiler may not know which object it points to, so it must be conservative — but the standard still restricts what such a pointer can do.

Variations

Pointer tagging

Some code stores extra bits in the low bits of aligned pointers. This is platform-specific and, if not done with extreme care, undefined behavior.

CHERI and capability pointers

On capability architectures (e.g., CHERI), pointers are not just addresses but capabilities with bounds and permissions. Provenance is literally part of the pointer representation. HARDWARE

Common Mistakes

Undefined Behavior

VERIFIED

Portability

implementation has an integer type wide enough for a pointer). VERIFIED

Under the Hood

On conventional flat-memory machines, provenance is a compile-time concept (the hardware pointer is just an address). On capability machines, it is a run-time concept. Optimizers such as GCC/Clang increasingly model provenance (e.g., via the "pointer is not an integer" view) and can miscompile code that assumes otherwise.

Practical Usage

integer when the platform guarantees round-trip, and convert back to the *same* type.

embedded MMIO code.

Exercises

1. Explain why a + 6 for int a[5] is UB even if no dereference occurs. 2. Write a program that round-trips a pointer through uintptr_t and verify it on your platform; document why it is not strictly portable. 3. Explain the difference between two pointers with the same address but different provenance.

Deep Challenge

Research (or reason about) why pointer comparison between pointers from different arrays is unspecified in C17, and how provenance-based compiler models make such comparisons problematic. Write a short explanation with an example where an optimizer could exploit this.

References

operators).

Verification

verified.`

Progress

Concept checkboxes

Mastery levels

ConceptCurrent level (0–8)Target level
c.obj.provenance07