views:

136

answers:

5
class Foo {
public:
 static const int kType = 42;
};

void Func() {
 Foo *bar = NULL;
 int x = bar->kType;
 putc(x, stderr);
}

Is this defined behavior? I read through the C++ standard but couldn't find anything about accessing a static const value like this... I've examined the assembly produced by GCC 4.2, Clang++, and Visual Studio 2010 and none of them perform a dereference of the NULL pointer, but I'd like to be sure.

+4  A: 

I believe that the actual value of the type is not used at all when calling

bar->kType

since kType is static, and bar is of type Foo it is the same as calling

Foo::kType

which you should really be doing anyway for clarity.

Calling bar->kType gives a compiler warning on most platforms for this reason.

Akusete
+1 "which you should really be doing anyway for clarity"
dave
Correct, static members are stored independently of object instances, so the object itself is never dereferenced.
casablanca
+6  A: 

You can use a pointer (or other expression) to access a static member; however, doing so through a NULL pointer unfortunately is officially undefined behavior. From 9.4/2 "Static members":

A static member s of class X may be referred to using the qualified-id expression X::s; it is not necessary to use the class member access syntax (5.2.5) to refer to a static member. A static member may be referred to using the class member access syntax, in which case the object-expression is evaluated.

Based on the example that follows:

class process {
public:
    static void reschedule();
};

process& g();

void f()
{
    process::reschedule();   // OK: no object necessary
    g().reschedule();        // g() is called
}

The intent is to allow you to ensure that functions will be called in this scenario.

Michael Burr
I was going to split a hair and point out that "evaluating the object expression" doesn't necessarily imply *dereferencing the pointer* -- it could just mean finding the pointer value -- but looking at §5.2.5, it appears that it *does*, because the "object-expression" in this case is defined to be "`*bar`", not just "`bar`". So yer right.
Zack
Hasn't this been a subject of debate? The notion of evaluating an object, as in simply nominating it, is rather zen. On the other hand, the example under that text clearly shows a function call. Clearly they mean that the expression before the "." or "->" is evaluated.
Potatoswatter
@Potatoswatter: I believe this my interpretation is correct, but I certainly don't consider myself an authority. While I think that the likelihood of calling a static member function through an appropriately typed NULL pointer is likely to work as you might expect, it's easy enough to have the call be made using the type name rather than a pointer (though I think for templates that might not always be true), so you might be better off simply not using pointers for this.
Michael Burr
Also, note that there might be a style issue that makes it so you should be reluctant to call static member functions through object-expressions: http://stackoverflow.com/questions/840522/given-a-pointer-to-a-c-object-what-is-the-preferred-way-to-call-a-static-membe/840766#840766
Michael Burr
@Michael: Yeah. I'm trying to find where the standard says that dereferencing NULL is bad. The specification of `*` at 5.3.1 only says that it turns a pointer into an lvalue reference, and that "the expression to which it is applied shall be a pointer to an object type". I'm not sure what "type" means. Does it mean the expression must be a valid pointer else UB, or it only needs pointer *type*? Hmm. Anyway, of course you're right. Obtaining an invalid/NULL pointer generally implies having the type name on hand. I guess if you had a function returning a NULL pointer to template type…
Potatoswatter
Hey that answer is not fair - the guy clealry asked about a pointer and yoy are giving him a reference which changes the semantics of the whole thing.
ZXX
+1  A: 

Apart from the issue about accessing through the NULL pointer, there is another subtle issue in the code

$9.4.2/2 - "The declaration of a static data member in its class definition is not a definition and may be of an incomplete type other than cv-qualified void. The definition for a static data member shall appear in a namespace scope enclosing the member’s class definition."

$9.4.2/4- "If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer."

class Foo { 
public: 
 static const int kType = 42; 
}; 

int const Foo::kType;

void Func() { 
 Foo *bar = NULL; 
 int x = bar->kType; 
 putc(x, stderr); 
}

So, yet one more reason for UB in the OP code.

Chubsdad
A: 

Even if it worked it is awful code.

In serious programming you code not only for you, but for others who will maintain your code. Playing tricks like this must be avoided, because you respect your colleague.

One consequence of this code: whether the pointer is NULL or not is even not at question, but it let think that this member kType may not be static, but a plain common member data of the class. Sometimes class are big (this is evil too) and one cannot always think to recheck each and every definition of each variable.

Be rigourous. And call all you static member only this way:

Foo::kType

Another possibility is to follow a coding convention that let know that the member is static, for exemple a s_ prefix for all classes static members:

Foo::s_kType

Stephane Rolland
A: 

There is a higher rule so to speak which basically says - don't even think about compiling things that are provably not used. Advanced template programming depends on this a lot, so even if it might be a bit gray-zonish when a compiler clearly sees that the result of a construct is not used it's just going to eliminate it. Especially when it's provably safe like in this case.

You may want to try a few variants if you want - like making pointer a param of a function, result of a function, leaving a pointer uninitialized (best chance for triggering compiler complaint), doing a straight cast of 0 (best chance of being conplaint-free).

ZXX