10

I've always known for a fact that the Standard mandates dereferencing null is UB. However,

(Link 1) says

p = 0; *p; is not inherently an error.

and provides a link to

(Link 2) says

*p is not an error when p is null unless the lvalue is converted to an lvalue

(I believe it's a typo and probably should read lvalue is converted to an rvalue)

Link 1 also says

char* p = 0; char *q = &*(p)

is "not undefined", which I could only read as well-defined or at least implementation-defined

Can a language lawyer provide an authoritative explanation of what's going on?

Community
  • 1
  • 1
PoweredByRice
  • 2,479
  • 1
  • 20
  • 26
  • I am not a language lawyer but my reading of the links is that dereferencing a null pointer in not in itself UB, only using the value is. And yes, it seems to be a typo, should be lvalue to rvalue, as that's how it's quoted in link1. I think it makes sense in the static member case, as only the static type of `operator*` is needed, and link1 is, as far as I can see, consistent with that. – drRobertz Apr 21 '17 at 10:01
  • If implementations make it segfault, is that REALLY UB? I can't think of any that don't. – Donnie Apr 21 '17 at 18:24
  • I was interested in a similar question that dereferencing a pointer to class instance is UB. In trying to assign a NUll pointer to a reference. std::string &s = *nullPointer; Trying to dereference a char* I think should also mean getting the char value at that location. dereferencing a class instance means get me the object at that location, but you dont actually fetch any value since you aren't using the member access operator. I wanted to know if this was UB. Note that dereferencing a char* should mean fetch the cpu char at that location. Which should cause a hardware exception. – user13947194 Oct 16 '22 at 03:20

1 Answers1

5

I explored the topic of indirection through null pointers in this answer. In short, it is indeed well-defined per se, just as elaborated in your cited Core issues. The committee used the notion of an empty lvalue as proposed many years ago (but never adopted); *p is supposed to be such an empty lvalue, and unless we attempt to access the (non-existent) memory location behind that lvalue (for example, by performing an lvalue-to-rvalue conversion), all other operations behave as expected. E.g. &*p is equivalent to p, unless p is invalid. (This also works for pointers past-the-end of an array, which is necessary for the common idiom &arr[n]).

I also started drafting a paper for empty lvalues (this is WIP, and the rebase against N4640 isn't complete yet), so there's a chance we'll see more of this at a later stage.

Community
  • 1
  • 1
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • IMHO, a number of issues in the Standards for both C and C++ could be cleaned up if they recognize the concept of zero-size lvalues, and treated `null` as such an entity. If one recognizes that pointers can point to the start and/or end of an object [so given `int foo[10]`, pointer `foo+1` would point to the end of the first item and start of the second, while `foo+10` would point only to the end of the tenth item], and that pointers to different objects must be distinct *except* that a pointer to the start of one object may point to the end of another... – supercat Jul 14 '18 at 17:01
  • ...then the "last element plus one" concept would fit in naturally with everything else without needing special treatment, and empty lvalues would fit likewise (the start address could equal the end address of some other object, and the end address could equal the start of another object). Requiring that code which needs objects with unique addresses must either give them non-zero size or else use a special means of creating a unique object would have been much cleaner than requiring that all objects have non-zero size. – supercat Jul 14 '18 at 17:05