Bogus cast from class to totally unrelated class works - why?

J

johnbrown105

Hello All,

I did try searching the archives, but the topics seemed to be mostly
about base
and derived classes.

I have reached Chapter 8 in Bruce Eckel's "Thinking in C++ 2nd Edition
Vol. 1".
Question 28 says:

"Create a class called bird that can fly( ) and a class rock that
can't. Create
a rock object, take its address, and assign that to a void*. Now take
the void*,
assign it to a bird* (you'll have to use a cast), and call fly( )
through that
pointer. Is it clear why C's permission to openly assign via a void*
(without a cast) is a "hole" in the language, which couldn't be
propagated
into C++?"

I came up with:

#include <iostream>
using namespace std;

class Bird{
public:
void fly(){ cout << "Flying..." << endl; }
};

class Rock{
double d;
};

int main()
{
Rock r;
void *v = &r;
Bird *pb = reinterpret_cast<Bird*>(v);
pb->fly();
return 0;
}

This compiled and linked without warnings, as I expected. I understand
why
you should not be allowed to convert a Rock to a Bird (unless you
really, really
want to very badly).

What I did *not* expect was that it would run, but it did. Why don't
I get
a run-time error when I call fly() in my Rock masquerading as a Bird?

I tried it with gcc 3.4.2 (MingW Windows port) and 4.1.1
(http://oss.netfarm.it/mplayer-win32.php) and it worked in both cases.
I am on Windows XP SP2.
 
G

Gavin Deane

Hello All,

I did try searching the archives, but the topics seemed to be mostly
about base
and derived classes.

I have reached Chapter 8 in Bruce Eckel's "Thinking in C++ 2nd Edition
Vol. 1".
Question 28 says:

"Create a class called bird that can fly( ) and a class rock that
can't. Create
a rock object, take its address, and assign that to a void*. Now take
the void*,
assign it to a bird* (you'll have to use a cast), and call fly( )
through that
pointer. Is it clear why C's permission to openly assign via a void*
(without a cast) is a "hole" in the language, which couldn't be
propagated
into C++?"

I came up with:

#include <iostream>
using namespace std;

class Bird{
public:
void fly(){ cout << "Flying..." << endl; }

};

class Rock{
double d;

};

int main()
{
Rock r;
void *v = &r;
Bird *pb = reinterpret_cast<Bird*>(v);
pb->fly();
return 0;

}

This compiled and linked without warnings, as I expected. I understand
why
you should not be allowed to convert a Rock to a Bird (unless you
really, really
want to very badly).

What I did *not* expect was that it would run, but it did. Why don't
I get
a run-time error when I call fly() in my Rock masquerading as a Bird?

I tried it with gcc 3.4.2 (MingW Windows port) and 4.1.1
(http://oss.netfarm.it/mplayer-win32.php) and it worked in both cases.
I am on Windows XP SP2.

Well the behaviour is undefined so the compiler is allowed to do
anything it wants and from a language definition point of view no
outcome is more or less valid than any other. But what is probably
happening is:

fly() is a member function of Bird and so your compiler creates code
exactly as it would for a normal function, except it inserts an extra
parameter of type Bird*. This extra parameter is the implied this
pointer that every (non-static) member function of any class has but
that does not appear explicitly in the parameter list. Now, in your
code, the Bird* this pointer inside fly() actually points to a Rock
and so any attempt to use or dereference the pointer isn't going to
work. But none of the code in your fly() function actually uses the
this pointer - no other member functions of Bird are called and no
member data is touched. Try changing the definition of Bird as below
and you might see something more obviously strange. Watch out for
nasal demons.

class Bird{
public:
Bird() : wbpm(42) {}
void fly()
{
cout << "Flying at " << this->wbpm << " wing beats per
minute" << endl;
}
private:
int wbpm;
};

Gavin Deane
 
S

Salt_Peter

Hello All,

I did try searching the archives, but the topics seemed to be mostly
about base
and derived classes.

I have reached Chapter 8 in Bruce Eckel's "Thinking in C++ 2nd Edition
Vol. 1".
Question 28 says:

"Create a class called bird that can fly( ) and a class rock that
can't. Create
a rock object, take its address, and assign that to a void*. Now take
the void*,
assign it to a bird* (you'll have to use a cast), and call fly( )
through that
pointer. Is it clear why C's permission to openly assign via a void*
(without a cast) is a "hole" in the language, which couldn't be
propagated
into C++?"

I came up with:

#include <iostream>
using namespace std;

class Bird{
public:
void fly(){ cout << "Flying..." << endl; }

};

class Rock{
double d;

};

int main()
{
Rock r;
void *v = &r;
Bird *pb = reinterpret_cast<Bird*>(v);
pb->fly();
return 0;

}

This compiled and linked without warnings, as I expected. I understand
why
you should not be allowed to convert a Rock to a Bird (unless you
really, really
want to very badly).

You aren't converting a Rock to a bird, you are 'reinterpreting' a
Rock* to a void* and that to a Bird*.
Converting a pointer converts the pointer, not its pointee.
Did you not read the warnings about using reinterpret_cast?
An old type cast might very well apply a static_cast, a dynamic_cast
or a reinterpret_cast.
You had no control over which was employed. Now you do.

int main()
{
Rock r;
Bird *pb = static_cast<Bird*>(&r); // invalid cast
pb->fly();
return 0;
}

Again, its the pointer being cast, not the object. That Rock is still
a Rock regardless of which type cast you apply on the ptr. I think you
are trying to bite more than you can chew here.
What I did *not* expect was that it would run, but it did. Why don't
I get
a run-time error when I call fly() in my Rock masquerading as a Bird?

Because its not a Rock* anymore. Unfortunately, the fly() member
function is not accessing the object in any way, which would generate
an error.
Classic example:

#include <iostream>

class Rock
{
double d;
public:
Rock( double d_ ) : d( d_ )
{ }
double get
() const
{
return d;
}
void foo()
{
std::cout << "Rock::foo()";
}
};

int main()
{
Rock * p;
p->foo(); // no error
p->get(); // seg fault
}

Why, cause there is no Rock object to access. All you have is an
unitialized pointer.
 
J

johnbrown105

... Now, in your
code, the Bird* this pointer inside fly() actually points to a Rock
and so any attempt to use or dereference the pointer isn't going to
work. But none of the code in your fly() function actually uses the
this pointer - no other member functions of Bird are called and no
member data is touched. Try changing the definition of Bird as below
and you might see something more obviously strange. Watch out for
nasal demons.
<cut>

I see. I now get:
Flying at 2009312941 wing beats per minute

I was still expecting something more dramatic than that, but it will
do. Thanks.
 
J

johnbrown105

You aren't converting a Rock to a bird, you are 'reinterpreting' a
Rock* to a void* and that to a Bird*.
Converting a pointer converts the pointer, not its pointee.

I understand. My language was imprecise.
Did you not read the warnings about using reinterpret_cast?

Most certainly.
Again, its the pointer being cast, not the object. That Rock is still
a Rock regardless of which type cast you apply on the ptr.

I may be a newbie, but I am not *that* new. I would like to take this
opportunity to remind you of my post's subject line, which is:
*BOGUS CAST ... WORKS - WHY?* I know that my Rock is still
a Rock. I just wanted to know why my Rock did not sink like a
stone, as a proper Rock should have.

I think you
are trying to bite more than you can chew here.

I am doing nothing of the kind. I am doing an exercise from a
textbook, the entire point of which is to show:

1) what can happen when you perform bogus casts, and
2) that C allows assignments to and/or from void *
without a cast, while C++ does not.

If you had bothered to read my post, you would have seen that.
Then again, you didn't even read the subject line. No matter
how anxious you are to attack hapless newbies, you should
at least read the post first.
Because its not a Rock* anymore. Unfortunately, the fly() member
function is not accessing the object in any way, which would generate
an error.

Which is the useful answer to my query.Thank you.
Classic example:
<Classic example cut>
It crashes at run-time. Now that's more like it.
 
O

Old Wolf

What I did *not* expect was that it would run, but it did. Why don't
I get a run-time error when I call fly() in my Rock masquerading as a Bird?

Your expectation is wrong. When you write code that
is compilable but has bogus effects, anything could
happen. Maybe you will get a runtime error but maybe
not. Often it *appears* to work normally, as in your
example, but then fails just when you have to do a
demonstration of your code.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,265
Latest member
TodLarocca

Latest Threads

Top