I am mostly foggy on some semantics of -Wnull-pointer-subtraction and looking
for a bit of clarity.
Having just tried 13.0.1 on a project, I noticed the following expression
triggering a null pointer subtraction warning:
where A is different from void. Being a bit naive to this particular UB, a
little digging led me to the C11 standard (draft), which clearly lays out the
behaviour of pointer subtraction. However, it also defines a “null pointer” as
An integer constant expression with the value 0, or such an expression cast
to type void *, is called a null pointer constant.
This, in isolation, would seem to imply that “null pointer” doesn’t include
integer constant expressions cast to some non-void pointer. Am I simply missing
something, or is this a legitimate reading?
Trying to grok this a little more, I poked around the clang source for a bit of
enlightenment. It appears the diagnostic is generated in
Sema::CheckSubtractionOperands, where the
relevant block looks like:
bool LHSIsNullPtr = LHS.get()->IgnoreParenCasts()->isNullPointerConstant( Context, Expr::NPC_ValueDependentIsNotNull); bool RHSIsNullPtr = RHS.get()->IgnoreParenCasts()->isNullPointerConstant( Context, Expr::NPC_ValueDependentIsNotNull); // Subtracting nullptr or from nullptr is suspect if (LHSIsNullPtr) diagnoseSubtractionOnNullPointer(*this, Loc, LHS.get(), RHSIsNullPtr); if (RHSIsNullPtr) diagnoseSubtractionOnNullPointer(*this, Loc, RHS.get(), LHSIsNullPtr);
which git blame attributes to commit 9cb00b9ecbe74d19389a5818d61ddee328afe031
by @jamie. So it looks like the code is explicitly ignoring casts of zero
constant expressions. This seems like a stronger interpretation of the
standard. Would you mind shedding some light on the motivations? I must be
As for why null pointer subtraction is UB, is this because “pointer” and
“memory location” are not the same thing?
Since pointer subtraction is defined 1 to be mostly the “difference of array
indices”, it begs the question whether
Q-P is even meaningful in the case
P are not both part of the same array (or one past the end).
However, on a system with a flat memory model, it seems like we could naively
generalize the calculation to
((intptr_t)Q - (intptr_t)P)/sizeof(Q) or
similar. Granted, on segmented architectures or systems where “memory offset”
and “memory location” have different sizes, this seems fishy.
Furthermore, I am aware that some old, cranky architectures actually represent
“null pointer” by some memory location different from
0, so it seems like
(void *)0 might have the leeway to resolve into some, potentially
instruction-dependent, non-zero value in the instruction operand.
Are the above thoughts vaguely in the right direction?