← BlogMISTAKES — 8 Jul 2026

The Pointer I Never Touched Was Still Undefined Behavior

I argued that a pointer nobody dereferences can't be a bug. The C standard doesn't care what you do with a pointer — only whether you were ever allowed to compute it in the first place.

CategoryMistakes
Published8 Jul 2026
Reading time5 min
cundefined-behaviorpointers

I was retaking a chapter test on pointers — the one where I'd missed something the first time and had to prove I actually got it now. Question two looked like the easy one. int *p = arr + 5;, where arr is int arr[3];. Nothing dereferences p anywhere, nothing prints it, nothing writes through it, it just sits there computed and unused for the rest of the function. I wrote that it was fine — deterministic pointer arithmetic, no memory access, so nothing wrong. I was wrong, and it took two rounds of arguing with myself before I actually believed why.

The setup

The question was testing one specific rule, and it did it by cutting out the part I assumed was the actual problem. Every undefined-behavior example I'd seen before this involved actually reading or writing through a bad pointer — a buffer overrun stomping on a neighboring variable, a dangling pointer read after free. So the shortcut in my head was "the danger is in the dereference." Take the dereference away, and I figured you take the danger away with it.

int arr[3] = {1, 2, 3};
int *p = arr + 5;   /* never read, never written, never compared */

That's the whole program, more or less. No crash. No output. No dereference anywhere in reach of p.

What went wrong

My reasoning leaned on how the hardware actually works: to a CPU, an address is just a number, and adding 5 to a number isn't an operation that touches memory. That part's true, I think, which is probably exactly why it was so easy to get the actual rule wrong. Apparently the C standard doesn't define pointer arithmetic in terms of what the CPU tolerates — it defines it in terms of what pointer values are even allowed to exist. Something like: a pointer produced by arithmetic is only valid if it lands inside the array, or exactly one element past the end, the "one past the last" position used for loop bounds. So arr + 3 is apparently fine even though you'd never dereference it, but arr + 5 on a 3-element array is already not a valid pointer the moment it's computed, whether or not anything ever reads through it.

I didn't buy this the first time I read through it. I went back and reread the explanation more skeptically, because it genuinely felt like being told a number is illegal to write down. The second pass landed better — something about the compiler being allowed to assume this never happens and building optimizations around that assumption, so the violation is in computing the bad pointer, not in whatever happens to it after. I'm not totally sure I could re-derive that reasoning myself from scratch yet, but the conclusion stuck: the undefined behavior happens at the computation, not at the use.

The fix

There's no real fix here in the usual sense — the "fix" was rewriting my answer from "fine, no dereference" to "UB at the point of computation," and trying to actually hold onto the idea that the rule attaches to the pointer's value, not to whatever happens to it afterward. What made me think it had actually landed, instead of just being memorized for this one question, was a later problem on the same test: a pointer subtraction that stayed within array bounds the whole time. I got that one right without having to stop and think about it, which felt like a decent sign. The general shape of the rule seemed to stick — it was specifically the "nothing ever touches it, so it can't matter" shortcut that hadn't.

The lesson

I'm going to try to check, on pointer arithmetic, not "does this get dereferenced" but "is this address, on its own, guaranteed to be inside the array or exactly one past it, regardless of what I do with it next." Not sure that's actually going to be the first question in my head every time yet — old habits and all that. I do think pushing back when an explanation sounds wrong is a fine instinct, maybe even a good one, since it's what actually made this stick better than just nodding along would have. But on this specific one, I think the standard just wins over what feels intuitive: the pointer's validity gets decided the instant it's computed, and apparently a bug can exist in code that never appears to touch anything at all.

← Why I'm Learning C Before Touching a FrameworkTwo Typos, Two Compiler Errors, One Variadic Function →

Notes from readers

Comments — via GitHub