10

Setup

Given this user-defined type:

struct T
{
    static int x;
    int y;

    T() : y(38);
};

and the requisite definition placed somewhere useful:

int T::x = 42;

the following is the canonical way to stream the int's value to stdout:

std::cout << T::x;

Control

Meanwhile, the following is (of course) invalid due to an instance of T not existing:

T* ptr = NULL; // same if left uninitialised
std::cout << ptr->y;

Question

Now consider the horrid and evil and bad following code:

T* ptr = NULL;
std::cout << ptr->x; // remember, x is static

Dereferencing ptr is invalid, as stated above. Even though no physical memory dereference takes place here, I believe that it still counts as one, making the above code UB. Or... does it?

14882:2003 5.2.5/3 states explicitly that a->b is converted to (*(a)).b, and that:

The postfix expression before the dot or arrow is evaluated; This evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.

But it's not clear whether "evaluation" here involves an actual dereference. In fact neither 14882:2003 nor n3035 seem to explicitly say either way whether the pointer-expression has to evaluate to a pointer to a valid instance when dealing with static members.

My question is, just how invalid is this? Is it really specifically prohibited by the standard (even though there's no physical dereference), or is it just a quirk of the language that we can probably get away with? And even if it is prohibited, to what extent might we expect GCC/MSVC/Clang to treat it safely anyway?

My g++ 4.4 appeared to produce code that never attempts to push the [invalid] this pointer onto the stack, with optimisations turned off.

BTW If T were polymorphic then that would not affect this, as static members cannot be virtual.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    There's some related discussion concerning this at [When does invoking a member function on a null instance result in undefined behavior?](http://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-behav) (Your question is about static data members; static member functions were discussed in the other question). The consensus was that it _should_ be allowable but is not; there's a closed DR that would make it allowable but that DR relies on the resolution to a still-open DR (whether that consensus was correct is open for debate :-D) – James McNellis Mar 09 '11 at 16:35
  • The issue is that the closed DR (CWG defect 315) says "`*p` is not an error when `p` is null unless the lvalue is converted to an rvalue." However, this relies on the concept of an "empty lvalue," which is part of the proposed resolution to the still-open [CWG Defect 232](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232). – James McNellis Mar 09 '11 at 16:40
  • @James: So we're saying that it's just another inconclusive quirk of the delicious mess that is C++? :) – Lightness Races in Orbit Mar 09 '11 at 16:47
  • Effectively, yes. I'm almost certain that in your quote, "evaluation" means "the operator does it's thing," and the thing that `*` does is "performs indirection," so you end up performing indirection via a null pointer, which yields undefined behavior. – James McNellis Mar 09 '11 at 16:48
  • @James: Oh joyous. I'd accept that as an answer, btw. – Lightness Races in Orbit Mar 09 '11 at 16:50

3 Answers3

11

it's not clear whether "evaluation" here involves an actual dereference.

I read "evaluation" here as "the subexpression is evaluated." That would mean that the unary * is evaluated and you perform indirection via a null pointer, yielding undefined behavior.

This issue (accessing a static member via a null pointer) is discussed in another question, When does invoking a member function on a null instance result in undefined behavior? While it discusses member functions specifically, I don't see any reason that data members are any different in this respect. There is some good discussion of the issue there.

There was a defect reported against the C++ Standard that asks "Is call of static member function through null pointer undefined?" (see CWG Defect 315) This defect is closed and its resolution states that it is valid to call a static member function via a null pointer:

p->f() is rewritten as (*p).f() according to 5.2.5 [expr.ref]. *p is not an error when p is null unless the lvalue is converted to an rvalue

However, this resolution is in fact wrong.

It presupposes the concept of an "empty lvalue," which is part of the proposed resolution for another defect, CWG defect 232, which asks the more general question, "Is indirection through a null pointer undefined behavior?"

The resolution to that defect would make certain forms of indirection through a null pointer (like calling a static member function) valid. However, that defect is still open and its resolution has not been adopted into the C++ Standard. Until that defect is closed and its resolution is incorporated into the C++ Standard, indirection via a null pointer (or dereferencing a null pointer, if one prefers that term) always yields undefined behavior.

Community
  • 1
  • 1
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 1
    Can this be generalised to non-NULL invalid pointers with the restriction that the defect resolution would not fix this for them? – Lightness Races in Orbit Mar 09 '11 at 17:26
  • @Tomalak: Yes, because doing just about anything with an invalid pointer yields undefined behavior. I'd have to dig into the Standard to find specific citations for that, though. – James McNellis Mar 09 '11 at 17:36
  • Yea, IIRC even printing the contents of an invalid pointer (and, yes, I mean the contents of the pointer) is UB, because it's "use". Though that's off the top of my head. – Lightness Races in Orbit Mar 10 '11 at 00:40
  • **Update:** As of the FDIS this [appears to still be the case](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1102), so unfortunately it looks like we're stuck with this through C++11. :( – Lightness Races in Orbit Aug 20 '11 at 15:25
3

Concerning p->a, where p is a null pointer, and a a static data member: §9.4/2 says "A static member may be referred to using the class member access syntax, in which case the object-expression is evaluated." (The "object-expression" is the expression to the left of the . or the ->.)

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Sure, it's evaluated. The question is, is the evaluated expression (0) then explicitly dereferenced? – Lightness Races in Orbit Mar 10 '11 at 00:39
  • The expression is (*ptr).x; the -> operator is defined in terms of *. And evaluating (*ptr) dereferences the pointer. – James Kanze Mar 15 '11 at 19:17
  • Where does the standard say that explicitly for this scenario? (The question has already been answered: the standard does _not_ say that unambiguously.) – Lightness Races in Orbit Mar 15 '11 at 19:18
  • @Tomalak §5.2.5/2 unambiguously defines p->x as the equivalent of (*p).x. And §5.3.1/1 says that "the expression to which it [the unary * operator] is applied shall be a pointer to an object type, or a pointer to a function type". `0` points to neither an object nor a function (of any type), so `*0` is undefined behavior. – James Kanze Sep 05 '11 at 11:09
0

Looking from computer side onto OOP it is yet another way to calculate where data resides in memory. When data is not static - it is calculated from instance pointer, when data is static it is always calculated as fixed pointer in data segment. Template adds nothing, since resolved at compile time.

So it is rather popular technique to use NULL as start pointer (for example evaluate offset of filed in class for persisting purposes)

So code above is correct for static data.

Dewfy
  • 23,277
  • 13
  • 73
  • 121
  • I'd rather have some rationale about quotes from the standard than know what the popular technique is, though! Also, I'm not sure I follow you regarding people using an instance pointer to just access static data without an instance? I realise that I'm the one who posted the code, but it's fugly and should never be used in real life... – Lightness Races in Orbit Mar 09 '11 at 16:41
  • @Tomalak Geret'kal For example of usage of NULL-pointer look my answer at: http://stackoverflow.com/questions/1400660/what-is-a-common-c-c-macro-to-determine-the-size-of-a-structure-member/1400702#1400702 . Relative usage of pointer I've mean that compiler doesn't need your pointer, so it is not used at all and rejected at compile time. Even more there is WARNING (something like: usage of pointer with static data, ignoring) – Dewfy Mar 09 '11 at 16:46
  • @Dewfy: OK, that's fine, but I'm looking for proof as to whether or not it really is "safe" as you claim. I suspect that it's not. BTW, why aren't you using pointers-to-members there? – Lightness Races in Orbit Mar 09 '11 at 16:46
  • @Tomalak Geret'kal - sorry press enter not in time, so copying again: Relative usage of pointer I've mean that compiler doesn't need your pointer, so it is not used at all and rejected at compile time. Even more there is WARNING (something like: usage of pointer with static data, ignoring) – Dewfy Mar 09 '11 at 16:47
  • @Dewfy: OK, but to what extent does the _standard_ guarantee this? And which compilers will explicitly allow it? And where is this documented? – Lightness Races in Orbit Mar 09 '11 at 16:49
  • @Tomalak Geret'ka I hope that next answer shows relation with standard http://stackoverflow.com/questions/3498444/c-static-const-access-through-a-null-pointer. So you are right about POSSIBILITY of pointer evaluation. – Dewfy Mar 09 '11 at 16:52
  • @Dewfy: Ooh yes that looks like a dup. Good spot. – Lightness Races in Orbit Mar 09 '11 at 17:25