2

I've come across this syntactic construct a few times, and I'm wondering:

  1. What does this do?
  2. What might the design reasoning be?

It tends to look something like this:

struct SubType : public SomeSuperType {

    SubType(int somthing) : SuperType(something), m_foo(*((FooType *)0))
    {}

    private:
    FooType m_foo;
}

To be clear, the code works. But what's the purpose? What would be the status of m_foo without that line?

nonot1
  • 2,788
  • 4
  • 25
  • 41
  • 8
    It gives the compiler freedom to behave as it wishes, including launching NetHack, or producing whatever executable it deems appropriate, without any regard for anything else in the program. Why anyone would want that to happen, I have no idea. – R. Martinho Fernandes Dec 07 '12 at 15:59
  • 2
    @R.MartinhoFernandes Curiosity. Plain old curiosity. Unless you're a cat, you should be safe. – Daniel Fischer Dec 07 '12 at 16:05
  • While the compiler is unconstrained by the C++ Standard, it still may be constrained by other entities. The compiler may have to perform according to its documentation, according to contracts between the programmer and the compiler's vendor, etc. So, in this case, there may be behavior that is defined, just not defined by C++. – Robᵩ Dec 07 '12 at 16:06
  • 3
    So it's casting a NULL pointer to (FooType *), and assigning the contents of that to m_foo. Off the top I can only think of two situations where that might be useful: in an embedded context where there is useful information at memory address 0, or to deliberately trigger an exception. In what context are you seeing this code? – Benny Smith Dec 07 '12 at 16:09
  • 2
    @BennySmith Even in the cases where you think it might be useful, you have to have some sort of guarantee from the compiler as well; a compiler could refuse to compile the code (if it could prove that it will be called), or generate some trap generating code, to cause it to crash at runtime. (g++ does this in certain cases of undefined behavior. I don't know if this is one of them.) – James Kanze Dec 07 '12 at 16:13
  • 2
    My guess is this is testing code, designed to dliberately generate a memory exception. – John Dibling Dec 07 '12 at 16:14
  • 1
    @BennySmith yet another guarantee you would need from the compiler was that a null pointer is the same as a pointer that points to address zero (the fact that a zero constant represents a null pointer has no bearing on that; the cast to the pointer can produce 0xFFFFFFFF). Like I said, what this does depends entirely upon the undisclosed compiler. – R. Martinho Fernandes Dec 07 '12 at 16:18
  • This code should compile. You might get a warning, but it's valid C/C++ code. And I don't believe the pointer cast would give you anything other than zero, unless you're doing a downcast or upcast, which is not happening here. – Benny Smith Dec 07 '12 at 16:21
  • The only occasion I could see the compiler having a problem with this code is if FooType does not have a copy constructor defined. – Benny Smith Dec 07 '12 at 16:25
  • 2
    @BennySmith The code is C++ code *without defined behaviour*, so no compiler is required to accept it. Regarding the null pointer, see http://c-faq.com/null/machexamp.html or http://stackoverflow.com/a/7547183/46642 – R. Martinho Fernandes Dec 07 '12 at 16:26
  • The articles about the NULL pointer were interesting, thank you. However, I think for this to be undefined behavior is to say that the compiler cannot cast an int to pointer; I'm not sure if this is forbidden or if it will simply generate a warning. – Benny Smith Dec 07 '12 at 16:32
  • 1
    @BennySmith It is not forbidden. The compiler is allowed to do anything, *including what you described*. But it is also allowed to do what I described, because anything here really means anything. Without knowing the compiler in question there is nothing else to be done but speculation. – R. Martinho Fernandes Dec 07 '12 at 16:35
  • @BennySmith A warning is a sign that you're doing something wrong. You should set your warnings as errors. – Etienne de Martel Dec 07 '12 at 16:45
  • I couldn't disagree more, but we're way off topic. – Benny Smith Dec 07 '12 at 16:47
  • 2
    @Benny Smith: Firstly, this is not about casing `int` to pointer. It is about casing constant (literal) `0` to pointer, which is a *very very veeeery* special case. It is explicitly guaranteed to produce null pointer. Secondly, the problem with this example is not the pointer itself, it is the fact that it is *dereferenced*. Dereferencing a null pointer is illegal. Thirdly, it all also depends on whether the expression is evaluated or not, as I stated in my answer. The example above does produce undefined behavior. The expression `*(T *) 0` in general does not have to. – AnT stands with Russia Dec 07 '12 at 17:50

3 Answers3

6

The purpose of this construct is to emulate a fake unnamed object of type SomeType in situations when you formally need an object, but don't want or can't declare a real one. It has its valid uses and does not necessarily cause undefined behavior.

A classic example would be determining the size of some class member

sizeof (*(SomeClass *) 0).some_member

or a similar application of decltype

decltype((*(SomeClass *) 0).some_member)

Neither of the above examples causes any undefined behavior. In non-evaluated context expressions like *(SomeClass *) 0 are perfectly legal and valid.

You can also see this technique used for illustrative purposes in the language standard itself, as in 8.3.5/12

A trailing-return-type is most useful for a type that would be more complicated to specify before the declarator-id:

template <class T, class U> auto add(T t, U u) -> decltype(t + u); 

rather than

template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);

Observe how the (*(T*)0) + (*(U*)0) expression is used under decltype to perform compile-time prediction of the result type of binary + operator between types T and U.

Of course, again, such tricks are only valid when used in non-evaluated contexts, as shown above.

Sometimes it is used as an initializer for "null references" as in

SomeType &r = *(SomeType *) 0;

but this actually crosses the boundary of what's legal and produces undefined behavior.

What you have in your specific example is invalid, since it attempts to access an invalid "null lvalue" in evaluated context.

P.S. In C language there's also that peculiar part of specification that says that operators & and * cancel each other, meaning that &*(SomeType *) 0 is valid and guaranteed to evaluate to null pointer. But it does not extend to C++.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
5

What does this do? Undefined behaviour.

What might the design reasoning be? A desire to cause undefined behaviour. There's no other rationale.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 1
    Although it's undefined c++ behavour, it might have well defined behavour on a specific platform/impelementation of course and therefore do something useful for the author... – jcoder Dec 07 '12 at 17:49
  • It is only undefined in the provided example. And the way it is introduced, it appears to be a mere *example*. The question itself appears to be about `*(T *) 0` in general. In general `*(T *) 0` does not have to lead to undefined behavior. – AnT stands with Russia Dec 07 '12 at 17:52
  • There _is_ a possible use, actually: If `FooType` has multiple overloaded constructors, this would ensure that the correct one got called. Though for this specific example to work, `FooType`'s copy constructor would have to never use its argument, and that seems... sketchy, at best. – Xavier Holt Dec 07 '12 at 18:05
  • @Xavier Holt: Well, *formally* this is still a hack with undefined behavior. Dereferencing a null pointer in evaluated context is already UB by itself, regardless of whether you access the resultant lvalue or not. – AnT stands with Russia Dec 07 '12 at 18:47
1

I don't think the example is necessarily UB. It depends on the definition of FooType. Suppose Foo is an empty class with a constructor that does something:

class Foo {
  public:
    Foo() { std::cout << "Hey, world! A new Foo just arrived.\n"; }
    // I think the default copy and assign constructors do nothing
    // with an empty type, but just in case:
    Foo(const Foo&) {}
    Foo& operator=(const Foo&) { return *this; }
};

Now, suppose I need a Foo, for whatever reason, and I don't want to trigger the constructor. Doing this will not cause any actual dereferencing because operator* does not dereference and the copy constructor doesn't use its reference argument:

Foo(*static_cast<Foo*>(0));
rici
  • 234,347
  • 28
  • 237
  • 341