0
class Parent
{};
class A_child : public Parent
{
  void A_method();
};
class B_child : public Parent
{
  void B_method();
};
void main()
{
  A_child a;
  Parent *p = &a;
  B_child *b = (B_child*)&p;
  b->B_method();
}

This piece of code is in C++. It is a logical error as we are trying to cast a "Cat" into a "Dog". But it works. Can anyone explain why and how?

  • 4
    This is undefined behavior. It doesn't _work_, it just doesn't crash. – tkausl Sep 07 '18 at 23:58
  • Add an int to `B_child`, set it to something it in `B_method`, and then see how well it works. – zzxyz Sep 08 '18 at 00:00
  • @tkausl - I dont understand. Do you mean to say that this piece of code will not work? – Kamalapriya Subramanian Sep 08 '18 at 00:09
  • `(B_child*)` is a C-style cast. It's a god hammer. With a C-style cast, you can force the round peg into the square hole. It will make A fit in B no matter how bad an idea that is. Avoid C-style casts. Use a `dynamic_cast` here and watch out for the returned null pointer. – user4581301 Sep 08 '18 at 00:10
  • @zzxyz - I gave just a print (cout) statement inside B_method and it gets invoked correctly when the line of code b->B_method() is encountered – Kamalapriya Subramanian Sep 08 '18 at 00:11
  • This is by pure dumb luck. You lied to the compiler, and now the compiler is allowed to do whatever it wants. It took the easy way out and gave you something that looked like "It works," but it could have launched a nuclear strike on Santa's Workshop. – user4581301 Sep 08 '18 at 00:13
  • @user4581301 - I gave just a print (cout) statement inside B_method and it gets invoked correctly when the line of code b->B_method() is encountered. It is not giving anything random as output. – Kamalapriya Subramanian Sep 08 '18 at 01:15
  • Unfortunately that means exactly nothing. My fellow numbered user is exactly right in his-or-her answer. Undefined behavior does not mean crash, odd result or even visibly wrong result. It means the standard doesn't say what will happen. This stuff is absolutely insidious. Sometimes a program looks like it works right up until [your boss gets back from Comdex and fires you.](https://www.youtube.com/watch?v=73wMnU7xbwE) – user4581301 Sep 08 '18 at 02:38
  • 1
    It is all about memory. `A_child` and `B_child` have the same structure and therefore fit in the same pattern in memory. By using a C-style class, you are merely pointing `b` to the memory address of `a` (via `p`). Because the memory patterns are the same, it works by serendipity. However, as others have pointed out, if the classes different in structure despite inheriting from the same base, they would be laid out differently in memory, and the C-style cast would be undefined behaviour. @zzxyz suggested adding an `int` to "disturb" the memory pattern of `b` and to cause a failure. – RWRkeSBZ Sep 08 '18 at 07:53
  • @EddyYoung - Thank you.. – Kamalapriya Subramanian Sep 08 '18 at 14:32

2 Answers2

5

Can anyone explain why and how?

Parent is a base of B_child and so the conversion from the type Parent *p to B_child* is well formed. However, the behaviour of accessing the pointed object through this converted pointer is only defined if p actually does point to a base sub object of a B_child instance.

The precondition does not hold, and so the behaviour of the program is undefined. Possible behaviours include, none of which are guaranteed:

 - working
 - not working
 - random output
 - non-random output
 - the expected output
 - unexpected output
 - no output
 - any output
 - crashing at random
 - crashing always
 - not crashing at all
 - corruption of data
 - different behaviour, when executed on another system
 -                    , when compiled with another compiler
 -                    , on tuesday
 -                    , only when you are not looking
 - same behaviour in any or all of the above cases
 - anything else within the power of the computer (hopefully limited by the OS)

Never static_cast, reinterpret_cast or C-style cast an expression to another type unless you can prove that the cast is correct. You can use dynamic_cast in a case where you're unsure.

eerorika
  • 232,697
  • 12
  • 197
  • 326
3

This is likely but not guaranteed to not cause an error because your B_method is effectively static.

  1. The method can be looked up and called into without dereferencing the class pointer
  2. The method itself doesn't need to dereference the class pointer.

As soon as the method becomes virtual (and now requires the class pointer for access to the vtable to look up the function address), accesses class data, or you sneeze or look at the compiler funny, you will be dealing unbound memory access.

And I should stress that while dereferencing the class pointer is not required by a hypothetical compiler, it is allowed, and could be required by any particular compiler implementation.

Further reading...check out the accepted answer Difference between Object and instance : C++ Your class pointer is unlikely to be looked at until you access instance data associated with a particular instance of the class.

Or...another way to put all this. If you can tack on static in front of your function declaration, calling it with an invalid pointer might work.

See also:

class MyClass
{
public:
  int doSomething(int x)
  {
    printf("%d", x);
    return x;
  }
};

int main()
{
  MyClass *pMyClass = nullptr;
  pMyClass->doSomething(42);
}
zzxyz
  • 2,953
  • 1
  • 16
  • 31