Description
This question comes up a lot in C/C++ land. Here is the status quo:
- C rejects the program
NULL + 0
as ill-formed, because arithmetic must land inside of an "allocation". - C++ accepts
NULL + 0
, by special case, an defines it to beNULL
. - LLVM defines
getelementptr inbounds T, T* 0, i64 0
to beT* 0
, again by special case.- Incidentally, Clang lowers
p + 0
togetelementptr inbounds
in both language modes.
- Incidentally, Clang lowers
- Rust rejects the program
ptr::null::<T>().add(0)
, for the same reasons as C.- Miri correctly rejects it as UB (Miri seems to special-case null here? Am I reading this right?)
IME this has caused quite a bit of pain in C, where you might want to write:
void Foo(char* p, size_t len) {
char* end = p + len;
for(; p != end; p++) { ... }
}
This is UB for any sensible choice of an empty slice, including p = NULL
. While Rust gives us the tools to avoid writing such code, it is... somewhat questionable to follow C rather than C++ here. Like, you could use wrapping_add()
, but honestly that seems a bit silly given that LLVM goes out of its way to make this work, and to my knowledge Clang's frontend doesn't care except for -fsanitize=null
.
I get the impression this wasn't a conscious decision and more of an emergent property of "you can only do add()
on valid pointers", but it'd be nice to document it if it was. Although you could infer that p != NULL
if you write p + n
in a vaccum, I've never seen a situation where being able to deduce this is useful optimization fuel. That said, I would not mind being proven wrong for when I have equivalent conversations in C/C++ land. =)