1

I have just created 2 pointers which has undefined behavior and try to invoke a class member function which has no object created ?

I don't understand this?

#include<iostream>

using namespace std;

class Animal
{
public:
  void talk()
  {
    cout<<"I am an animal"<<endl; 
  }
};

class Dog : public Animal
{  
public:
  void talk()
  {
    cout<<"bark"<<endl; 
  }
};

int main()
{
  Animal * a;
  Dog * d;

  d->talk();
  a->talk();  
} 
James McNellis
  • 348,265
  • 75
  • 913
  • 977
Eternal Learner
  • 3,800
  • 13
  • 48
  • 78
  • 10
    "I don't understand this?" <-- Yep, that pretty much summed up my thoughts after seeing this question. – Billy ONeal Apr 02 '10 at 04:06
  • What is your question? You state exactly what you have done: you have two pointers that don't point anywhere and you get undefined behavior when you dereference them. – James McNellis Apr 02 '10 at 04:08
  • 1
    I think the OP's question is why does it *work*, not why does it *not work*. This code prints out "bark" and "I am an animal" on my machine. As @James notes - undefined behaviour is the order of the day here. – Carl Norum Apr 02 '10 at 04:12
  • 3
    qudroplicate: 1. http://stackoverflow.com/questions/2505328/calling-class-member-through-uninitialized-class-pointer/ 2. http://stackoverflow.com/questions/1524312/why-i-am-able-to-make-function-call-using-invalid-class-pointer 3. http://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-behav – Alexander Malakhov Apr 02 '10 at 04:20

7 Answers7

10

A) It's undefined behavior. Any behavior may happen.

B) Since you're not calling a virtual method, it's pretty easy to explain why the undefined behavior actually does this (and I've tested this under just about every compiler I could find).

In C++, calling a member method is equivalent (in practice if not in definition) of calling a member with a hidden 'this' variable. If the method is virtual, it has to go through the vftable, but not for a non-virtual method.

So

Foo::Bar(){}

is the rough equivalent of

Foo_Bar(Foo *this){}

and in the calling code

Foo *foo = new Foo();
foo->bar();

the second line is roughly the moral equivalent of

Foo_Bar(foo);

Now, there's some simplification going on here, and as I said, some of this may be implementation detail rather than specification. However, the behavior holds true (though relying upon it is an error).

But, given the preceding, look at an implementation:

void Foo::Bar(){printf("Hello, world!\n");}

and calling code:

Foo *foo = 0;
foo->Bar();

As we've said, this is roughly equivalent (since we're non-virtual) to:

Foo *foo = 0;
Foo::Bar(foo);

Which means that we're calling the equivalent of the following method:

void Foo_Bar(Foo* this)
{ printf("Hello, world\n"); }

Now, given this method, we're not actually dereffing the this pointer! So, it's pretty clear why, in this case, the method will work and not fail.

The practical upshot of this is that calling non-virtual methods on a null pointer, where the method doesn't deref an a member, will generally result in this observed behavior. But, relying upon any undefined behavior is, basically, evil.

kyoryu
  • 12,848
  • 2
  • 29
  • 33
8

When you do something that has undefined behavior, anything can happen -- including it appearing to work. It looks like that's what's happening to you in this case.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • +1 to above comment and to answer. Of course, we cannot be sure that what appears is what is :P – Billy ONeal Apr 02 '10 at 04:16
  • Exactly. It is working in my compiler but I do not understand under what circumstances it can work? – Eternal Learner Apr 02 '10 at 04:17
  • 8
    @Srinivasa: Under **no** circumstances does undefined behavior work. **None**. Under _any_ circumstances it may _appear_ to work, but that's it. – James McNellis Apr 02 '10 at 04:19
  • @Srinivasa, you will have to look at a disassembly of the binary for your program to get more information about that. Maybe looking at the source code for the compiler will help you if that is available. My best guess is that this appears to work because the methods in question don't depend in any way on details about the particular instance of the object. The compiler might be changing those calls to simple function calls, and ignoring the fact that they're being used on uninitialized pointers. – Carl Norum Apr 02 '10 at 04:20
  • +1. While my answer goes into detail as to why the specific undefined behavior is observed, your answer (it's undefined, don't do it) is the *definitive* answer. – kyoryu Apr 02 '10 at 05:22
0

You need to use the new operator.

JRL
  • 76,767
  • 18
  • 98
  • 146
  • @Travis G: Actually I believe it is legal to hide an inherited name this way in C++ -- though admittedly it's bad practice and is not going to give someone used to Java what they expect ;) – Billy ONeal Apr 02 '10 at 04:10
  • @BillyONeal: You are right; using name hiding will not make the program run ridiculously slow. :-D (I kid! I kid! Sort of...) – James McNellis Apr 02 '10 at 04:11
  • @James McNellis: Kidding is perfectly fine so long as you understand there will be no Unicorn eating going on :P – Billy ONeal Apr 02 '10 at 04:13
  • Sorry for the terse question. When i ran this program expecting it to not work, it surprisingly works!!! I understand what I am trying to do is an undefined behavior but could anyone explain under what circumstances of "the undefined" behavior does this work ? – Eternal Learner Apr 02 '10 at 04:15
0

You need to change:

void talk()

To:

virtual void talk()

If you want the function to be polymorphic. Also, you need to instantiate your objects as in:

Animal* a = new Animal;
Dog* d = new Dog;

Don't forget to free them before you return:

delete a;
a = 0; 

delete d;
d = 0;

In practice, you will want to use a boost::shared_ptr or std::auto_ptr as in:

std::auto_ptr<Animal> a(new Animal);
std::auto_ptr<Dog> b(new Dog);

The above will save you the need to call delete. Alternatively, you can use automatic storage:

Animal a;
Dog d;

With the above, you would use a.talk() and d.talk() instead of a->talk() and d->talk().

Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
  • Fixed your mispelling of delete. Side note though -- you really should be using NULL rather than 0 to set pointers to NULL (or `nullptr` if you're lucky enough to have a compiler that supports that) – Billy ONeal Apr 02 '10 at 04:12
  • `auto_ptr`? Yuck. At least `scoped_ptr` or `shared_ptr` please :) – Billy ONeal Apr 02 '10 at 04:14
  • 3
    @BillyONeal, I disagree... with NULL you have to include ... using 0 doesn't require any includes. In C++0x, though nullptr would be ideal. – Michael Aaron Safyan Apr 02 '10 at 04:14
  • @Billy, I agree on auto_ptr... but until std::shared_ptr and std::scoped_ptr is standard, I will refer to Boost (boost::shared_ptr and boost::scoped_ptr) and to std::auto_ptr (for those who want to stick with just the standard library). – Michael Aaron Safyan Apr 02 '10 at 04:16
  • 1
    @Billy: I'm curious about your rational for that. – GManNickG Apr 02 '10 at 04:25
  • I should clarify, I mean NULL over 0. I think it's complete preference, but the way you worded it makes it sound like NULL has something which 0 does not. Maybe I'm reading too much into it. – GManNickG Apr 02 '10 at 04:42
  • @GMan: Two reasons. The first is that semantically, NULL is the NULL pointer constant. Second, because it makes it easier to update your code to use `nullptr` when you do eventually decide to do that, because it should be a simple find and replace operation at that point. – Billy ONeal Apr 02 '10 at 12:47
0

I believe the reason your code works is because the talk() methods are not actually accessing any member variables of the class. In other words, you are not actually accessing the implicit this, which happens to be invalid.

I actually experienced this same issue before. My code was calling a member function of a null pointer and it reliably worked. I finally discovered the problem when I modified the function so that it actually attempted to access a member variable. After that change it reliably crashed.

I'm not sure if this is standard behavior or compiler specific. In my case I was using Microsoft Visual Studio 2008.

flashk
  • 2,451
  • 2
  • 20
  • 31
  • It is not standard behaviour, it is undefined behaviour. If your compiler chooses to make it appear to work, that's up to its implementors. – Carl Norum Apr 02 '10 at 04:35
0

It's undefined behavior, so anything might happen.

It can be possible that it just prints the correct thing since the methods don't access any member variables of the objects they are called on (the memory where the objects supposedly live doesn't need to be accessed, so access violations don't necessarily occur).

Unless your compiler specifies this kind of behavior somewhere (which it most probably won't), you can of course not count on this to happen.

sth
  • 222,467
  • 53
  • 283
  • 367
0

you call this non-static member method, you should construct the object of class, so you need:

 Animal * a =  new Animal();
 Dog * d = new Animal();

 d->talk();
 a->talk(); 

 delete a;
 delete d;

And to use Polymorphism, you should use virtual keyword before talk()

public:
virtual void talk()
  {
    cout<<"I am an animal"<<endl; 
  }