Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pointer-to-Pointer crashes in case where Pointer doesn't

Tags:

c++

windows

I'm porting some code to Windows and am stumped. There is some code that runs automatically on launch to copy a pointer to a pointer, and runs again on exit to delete the pointer to a pointer if it is not null.

I have created a sample program to reproduce the behavior

int main()
{
  // Pointer to a Pointer, current crash.
  InterfaceClass** ptrptr;
  ConcreteTwo* object = new ConcreteTwo();
  ptrptr = (InterfaceClass**)(&object); // cast is required for some reason.
  delete *ptrptr; // Crash here.

  // Single pointer, works fine.
  InterfaceClass* ptrptr;
  ConcreteTwo* object = new ConcreteTwo();
  ptrptr = object;
  delete ptrptr;

  // There are other cases where there are only 3 classes in the hierarchy.
  // This also works fine.
  InterfaceClass** ptrptr;
  ConcreteOne* object = new ConcreteOne();
  ptrptr = (InterfaceClass**)(&object);
  delete *ptrptr;

  return 0;
}

The Class Hierarchy looks like this. The base class is an interface with some pure virtual functions and is included by many classes all over the program in such a way that many objects potentially inherit it from more than one place. Because of this the concrete implementation must extend it with "public virtual InterfaceClass". In this example deleting the "virtual" resolves the crash.

class InterfaceClass {
public:
    virtual ~InterfaceClass() {};
    InterfaceClass() {}
};

class ConcreteClass : public virtual InterfaceClass {
public:
  ConcreteClass() { }
  virtual ~ConcreteClass() {}
};

class ConcreteOne : public ConcreteClass
{
public:
  ConcreteOne(void) {}
  virtual ~ConcreteOne(void) {}
};

class ConcreteTwo : public ConcreteOne
{
public:
  ConcreteTwo(void) {}
  virtual ~ConcreteTwo(void) {}
};
like image 640
Winder Avatar asked Dec 17 '10 20:12

Winder


4 Answers

So are you familiar with the fact that the type of a pointer hardly has anything to do with the type it points at? In other words, where you under the impression that if T1 inherits from T2 that T1* also inherits from T2*? That would be mistaken. Now, how does that apply to your current situation?

  InterfaceClass** ptrptr;
  ConcreteTwo* object = new ConcreteTwo();
  ptrptr = (InterfaceClass**)(&object); // cast is required for some reason.

Here's a major problem with C style casting. OK, so it saves some horizontal space but do you even know what kind of cast you just did? It's not what you think it is. You actually performed a reintpret_cast from type ConcreteTwo* to an unrelated type InterfaceClass*! Now the pointer address has nothing to do with the type you say it is.

Then you toss a reinterpreted pointer type into delete, which promptly caused you to violate your own sphincter.

like image 177
Edward Strange Avatar answered Nov 06 '22 20:11

Edward Strange


Well, the compiler warned you, you decided to do it your way...

You can't do this cast:

ptrptr = (InterfaceClass**)(&object);

because object points to ConcreteTwo, which is not the same as InterfaceClass. InterfaceClass sub-object of ConcreteTwo is located at a different address. *ptrptr is not a pointer to instance of InterfaceClass.

The pointer you pass to delete is a pointer to ConcreteTwo, however you said to the compiler that it is a pointer to InterfaceClass. delete assumes that it is indeed an InterfaceClass, hence the crash.

like image 37
Yakov Galka Avatar answered Nov 06 '22 20:11

Yakov Galka


"Converting Derived* → Base* works OK; why doesn't Derived** → Base** work?" in the C++ FAQ Lite.

like image 36
Karl Knechtel Avatar answered Nov 06 '22 22:11

Karl Knechtel


I think the problem are in the cast lines. BTW, if you remove the cast you inserted the compiler tells you precisely what's the problem.

If you really want to do this, way, which I strongly advise against, you should first create a temporary:

ConcreteTwo* object = new ConcreteTwo();
InterfaceClass* ptr = object;

then you can take its address and assign it to the ptrptr variable:

InterfaceClass** ptrptr = &ptr;

now you can safely delete it:

delete *ptrptr;

Take into account that if ptr goes out of scope before ptrptr, the delete will probably crash again.

As for the rest, Noah explains you why your code is not working.

like image 40
Simone Avatar answered Nov 06 '22 21:11

Simone