Repository navigation
CWG3232 [expr.add] Undefined behavior in pointer subtraction has been removed accidentally #922
Description
Activity
I would call this one expr.sub.pointers.representable. Avoid range, because "range" when talking about pointers is a very overloaded term.
The actual change here seems fine, but it is on the border of editorial --- it's correcting a normative mistake in a paper that was merged, but it is definitely a normative and not completely editorial change.
Unless i'm missing something obvious, the example doesn't seem to have UB --- the value of that difference is PTRDIFF_MAX, which seems like a value that should be representable as a std::ptrdiff_t. I haven't been able to come up with a meaningful example that would work on anything like a real platform and exhibit this particular UB.
The actual change here seems fine, but it is on the border of editorial --- it's correcting a normative mistake in a paper that was merged, but it is definitely a normative and not completely editorial change.
It is borderline editorial, but I think this would be one of those editorial PRs that CWG should look at anyway, so we don't lose much by going through issue handling. It's also valuable to get some feedback on whether we want to cover this using some [expr.pre] blanket wording again (though that has always been surprising, which is why we have this note in the first place), or whether we want to make the note normative.
Unless I'm missing something obvious, the example doesn't seem to have UB --- the value of that difference is
PTRDIFF_MAX, which seems like a value that should be representable as astd::ptrdiff_t.Hmm yeah, it needs to be
PTRDIFF_MAX + 1for the example to work. Fixed.The unfortunate part is there's no way to get there in a real example --- no common platforms yet you get an object that big in the first place.
You're not going to get this behavior on a 64-bit platform, but maybe on a 16-bit platform or 32-bit platform, you might be allowed to
mallocmore than half your address space. Or maybe you could get there with freestanding, or memory mapping, or anything like that.I don't think we should make such edits, since this reference in the form of a note already refers to the relevant provisions of the standard ([expr.pre#4.5]) - which is quite sufficient, since the type of operation is already clearly indicated here.
5 The result of subtracting two pointer expressions P and Q is a prvalue of type std::ptrdiff_t ([support.types.layout]).
The behavior of evaluating an arithmetic expression is undefined ([ub:expr.expr.eval]) if the mathematical result is neither
4.5 in the range of representable values for its type nor 4.6 a negative infinity, positive infinity, or NaN that is among the values of the type[.](https://eel.is/c++draft/expr.pre#4.sentence-3)Pointers are not of arithmetic type, so the difference of two pointers is not an arithmetic expression. That''s the hole that was opened up by P3899R3.
Pointers are not of arithmetic type, so the difference of two pointers is not an arithmetic expression. That''s the hole that was opened up by P3899R3.
Yes, perhaps this is exactly what should be changed?
(Above, I wanted to suggest that the existing rule could be reused for pointers as well — it seems like it wouldn't introduce any negative aspects.)Perhaps it's worth adding operations on pointers to the "arithmetic operations"?
4 An [arithmetic expression](https://eel.is/c++draft/expr.pre#def:expression,arithmetic) is 4.1 a unary plus or minus ([[expr.unary.op]](https://eel.is/c++draft/expr.unary.op)), 4.2 an addition ([[expr.add]](https://eel.is/c++draft/expr.add)), 4.3 a subtraction ([[expr.sub]](https://eel.is/c++draft/expr.sub)), or 4.4 a multiplication, division, or remainder ([[expr.mul]](https://eel.is/c++draft/expr.mul)) expression where every (possibly converted) operand is of arithmetic + or pointer type[.](https://eel.is/c++draft/expr.pre#4.sentence-1)although this will lead to some absurd actions for pointers (like multiplication)
P3899 makes some points about this, and i think it's right. There's no such thing as the "mathematical result" of any operation with pointers --- pointers aren't numbers. We define elsewhere a number of operations that can be done with pointers, but none of those things are arithmetic operations on the pointer --- they are defined by the language, and the abstract machine, not by math. Mixing the stuff in expr.pre t include operations that are not defined by math just seems to be a category error (that was fixed by P3899, that adding pointer back to that list would just reintroduce).
Reacted by Safocl StollmannovicI think the example correction in the original post is simple to implement, but perhaps it's worth considering a more general change that would produce the same results without affecting other situations?
That said, I would still replace "If the value i−j" in the original post with "if the result" (similar to [expr.pre#4]).
The result of subtracting two pointer expressions P and Q is a prvalue of type std::ptrdiff_t ([support.types.layout]). (5.1) If P and Q both evaluate to null pointer values, the value is 0. (5.2) Otherwise, if P and Q point to, respectively, array elements i and j of the same array object x, the expression P - Q has the value i−j. - [Note 2: If the - value i−j + result is not in the range of representable values of type std::ptrdiff_t, the behavior is undefined - ([expr.pre]) . - — end note] (5.3) Otherwise, the behavior is undefined.That would seem fine to me. I will leave it up to @eisenwave and @jensmaurer how we want to deal with this one though (land as part of P4284 with the annex entry, land as a separate CWG issue and do the annex entry later, land as an editorial issue and keep the annex entry in P4284 to land after the editorial issue, etc.).
I think we should always put any UB annex changes into the CWG issues that affect UB, so CWG can easily review these as a holistic package. The "we'll do the annex entry later" approach is just asking for the UB annex to get out of sync. "We'll do it later" has a way of happening years later or never in WG21.
CWG hasn't yet decided how to deal with this pointer subtraction UB wording, so it's also unclear what the annex entry looks like, and whatever you put into P4284 might be subject to major changes through this CWG issue.
Reacted by A. Jiang- changed the title
[-][expr.add] Undefined behavior in pointer subtraction has been removed accidentally[/-][+]CWG3232 [expr.add] Undefined behavior in pointer subtraction has been removed accidentally[/+]on Sep 20, 2026 CWG3232
@jensmaurer it looks like you flipped std::end and std::begin in the UB annex proposed wording ... but I guess we do allow that? Nvm, the result doesn't need to be positive, but then isn't the example off by one because ptrdiff_t can represent one more negative value than positive value?
Yup, fixed.
Subclause: [expr.add]
Issue description
P3899R3 accidentally removed undefined behavior in the case where the result of subtracting pointers is not representable as
std::ptrdiff_t. This was caused by limiting undefined behavior in subtraction to arithmetic types, which pointers are not.Proposed resolution
Make the note in [expr.add] paragraph 5 normative text:
The result of subtracting two pointer expressions P and Q is a prvalue of type std::ptrdiff_t ([support.types.layout]). (5.1) If P and Q both evaluate to null pointer values, the value is 0. (5.2) Otherwise, if P and Q point to, respectively, array elements i and j of the same array object x, the expression P - Q has the value i−j. - [Note 2: If the value i−j is not in the range of representable values of type std::ptrdiff_t, the behavior is undefined - ([expr.pre]) . - — end note] (5.3) Otherwise, the behavior is undefined.Rename [ub:expr.add.sub.diff.pointers] to [ub:expr.add.sub.pointers.array], and add a corresponding UB annex entry [ub:expr.add.sub.pointers.range]: