-1

Possible Duplicate:
When does invoking a member function on a null instance result in undefined behavior?

Anything like this:

class Class {
public:
    void Method()
    {
       //empty;
    }
};

Class* object = 0;
object->Method();

is undefined behavior in C++ because calling non-static member functions via null pointers is formally illegal. See this answer for a detailed explanation full of quotes from the C++ Standard. I'm well aware of the theoretical part and this question is not about theory and so it's not a duplicate of that question.

In all implementations I'm aware of the code above or some equivalent thereof doesn't cause any observable problems - since the member function doesn't access the object the method will be called just fine.

May I have any real-life example in which the same setup causes practical observable problems?

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • What happens if you put some trivial codes that does not access object data members inside? Like a g_Num = rand(); where g_Num is a global variable. – ksming Feb 08 '12 at 09:53
  • @KennyTM: Not a dupe. That questions asks "when it is UB" and the answer is "it is UB at all times because the Standard says so". I know what the Standard says and want to know of an example when this leads to *observable problems*. – sharptooth Feb 08 '12 at 10:01
  • @ksming: That code will usually be executed. – sharptooth Feb 08 '12 at 10:01
  • 1
    Obviously, if the function is virtual, it *will* fail miserably. – Alexandre C. Feb 08 '12 at 10:19
  • If I cross the street with my eyes closed, will I always be run over by a bus? – Bo Persson Feb 08 '12 at 17:06
  • @Bo Persson: No, but "you might be run over by a bus" would be a real life example of what could go wrong while crossing a street with your eyes closed. – sharptooth Feb 09 '12 at 07:50
  • Here's a very good example that does not include "simply beware of UB" and "crazy optimization" plots: http://stackoverflow.com/a/3257755/57428 – sharptooth Dec 05 '13 at 09:28

3 Answers3

4

Simple:

struct Object { void foo(); std::string s; };

void print(Object* o) {
  o->foo();
  if (o) { std::cout << o->x << "\n"; }
}

Let us say that foo does not access any non-static attribute of Object (ie, x).

The problem is that because formally o->foo() is undefined behavior if o is null, then it is obvious that o is not null. Therefore the check is redundant.

The function is thus optimized:

void print(Object* o) {
  o->foo();
  std::cout << o->x << "\n";
}

Reversing the order does not change anything:

void print(Object* o) {
  if (o) { std::cout << o->x << "\n"; }
  o->foo();
}

is still optimized:

void print(Object* o) {
  std::cout << o->x << "\n";
  o->foo();
}

Sometimes referred to as the Time Travel Clause of Undefined Behavior by some SO members.

For more information, check out Chris Lattner's serie on Undefined Behavior:

Your specific concern is addressed in 2/3.

Whether this actually fails depend on the compiler you use, the optimization passes you specify and the order in which they run.

Do you really want to depend on all that :x ?

Of course, one would argue that's it is pointless to have a function member that does not access any state of the object... so the question itself is of little value in practice (but interesting for its theoretical aspects).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Btw what exactly is called "time travel clause"? May I have a reference to any such usage? – sharptooth Feb 08 '12 at 10:54
  • @sharptooth: there is no *reference* AFAIK, I have just seen some members here (GMan or jalf perhaps) using it a few times. If you look at the second example (where the test preceeds the `o->foo()` statement), you can see that the presence of `o->foo()` affects the test (which is removed) even though the test is (in normal circumstances) executed before. Therefore, the UB of `o->foo()` creates a crash *before* the `o->foo()` statement is executed, as if it travelled into the past :) – Matthieu M. Feb 08 '12 at 12:49
  • Just FYI: I've found a more real life example that does not include hardcore optimizations: http://stackoverflow.com/a/3257755/57428 Yours is great too. – sharptooth Dec 05 '13 at 09:29
  • @sharptooth: interesting indeed, though slightly different since `this` is actually accessed. What is amusing is that a compiler is allowed to consider that `this` is never `null` and thus the example could break even without multiple bases :) – Matthieu M. Dec 05 '13 at 10:04
  • Yes, you're right, but it is possible to craft some other example where `this` is not actually used by the method but the compiler needs to adjust it before passing into the method because it doesn't know what the method does inside. `this` will have insane value anyway, just this will be undetected. – sharptooth Dec 06 '13 at 08:38
1

This is UB, so what would constitute a "problem"? To be a problem, the code would have to do something other than what the standards say it should do or what we expect it to do, otherwise it's not a problem. The standards give us no idea what the code should or will do, so whatever it does isn't a problem.

You say it doesn't cause any "observable problems". Well, of course not. Whatever it did would be fine. It could fault, and that wouldn't be a "problem" because that's what the standard tells us can happen.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
0

It is unlikely that this code produces any problems on any platform I'm aware of. It is still undefined behavior, primarily because it is an irrelevant corner case: it could be defined that under certain conditions calling a member function on a null pointer would be OK. However, what is the point? If you don't access any of the members, why make the function a non-static member function? There is no loss in leaving this undefined as there is no conceivable need to define this case and it would thus unnecessarily complicate the language.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • You could have a `dump()` function in the class that checks if `this == 0`, and then either prints "Null" or some diagnostics. If you then have a bunch of pointers, some null and some valid, you can mindlessly call `dump()` on all of them. But then, for the reasons Matthieu M. pointed out, this is still a bad idea... – jdm Feb 08 '12 at 10:34