6

I have tried some interesting code(at least for me !). Here it is.

#include <iostream>

struct myStruct{
    int one;

    /*Destructor: Program crashes if the below code uncommented*/
    /*
    ~myStruct(){
        std::cout<<"des\n";
    } 
    */
};
struct finalStruct {
    int noOfChars;
    int noOfStructs;

    union { 
        myStruct *structPtr;
        char *charPtr;
    }U;

};
int main(){
    finalStruct obj;
    obj.noOfChars = 2;
    obj.noOfStructs = 1;
    int bytesToAllocate = sizeof(char)*obj.noOfChars 
                        + sizeof(myStruct)*obj.noOfStructs;

    obj.U.charPtr = new char[bytesToAllocate];
    /*Now both the pointers charPtr and structPtr points to same location*/

    delete []obj.U.structPtr;
}

I have allocated memory to charPtr and deleted with structPtr. It is crashing when I add a destructor to myStruct otherwise no issues.

What exactly happens here. As I know delete[] will call the destructor as many times as number given in new[]. Why it is not crashing when there is no destructor in myStruct?

Edward
  • 6,964
  • 2
  • 29
  • 55
Srikanth
  • 517
  • 3
  • 10
  • 29

4 Answers4

8

First off, storing one member of a union and then reading another in the way you're doing it is Undefined Behaviour, plain and simple. It's just wrong and anything could happen.

That aside, it's quite likely the type pun you're attempting with the union actually works (but remember it's not guaranteed). If that's the case, the following happens:

You allocate an array of bytesToAllocate objects of type char and store the address in the unionised pointer.

Then, you call delete[] on the unionised pointer typed as myStruct*. Which means that it assumes it's an array of myStruct objects, and it will invoke the destructor on each of these objects. However, the array does not contain any myStruct objects, it contains char objects. In fact, the size in bytes of the array is not even a multiple of the size of myStruct! The delete implementation must be thoroughly confused. It probably interprets the first sizeof(myStruct) bytes as one myStruct object and calls the destructor in those bytes. Then, there's less than sizeof(myStruct) bytes left, but there are still some left, so the destructor is called on those incomplete bytes, reaches beyond the array, and hilarity ensues.

Of course, since this is just UB, my guess at the behaviour above could be way off. Plain and simple, you've confused it, so it acts confused.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
2

delete makes two things, call destructor and deallocate memory.

You allocate data for one type, but delete if faking another type. You shouldn't do it. There are many things one could do in C/C++, take a look at IOCCC for more inspirations :-)

A struct in C++ without any function and having only plain old data is itself a POD. It never calls a constructor/destructor when created/deleted. Even not standard c-tors/d-tors. Just for performance reasons.

A Struct having (EDIT) user-defined copy-assignment operator, virtual function or d-tor is internally a little bit more complicated. It has a table of member function pointers.

If you allocate the memory block with chars, this table is not initialized. When you try to delete this memory block using a not POD-type, it first calls the destructor. And as the destructor function pointer is not initialized, it calls any memory block in your memory space, thinking it was the function. That's why it crashes.

Community
  • 1
  • 1
Valentin H
  • 7,240
  • 12
  • 61
  • 111
  • 1
    Are you sure about the table of member function pointers? (For monomorphic classes.) I bet you the size of a struct does not change if you add a non-virtual member function, and it's still binary compatible with its C equivalent using the same compiler family. – Peter - Reinstate Monica Apr 16 '15 at 10:52
  • @PeterSchneider You're absolutely right. A standard-layout struct can have non-virtual member functions to its heart's content, and its size/layout will not be affected. – Angew is no longer proud of SO Apr 16 '15 at 11:18
  • OK, right. I was not much specific. "A Plain Old Data Structure in C++ is an aggregate class that contains only PODS as members, has no user-defined destructor..." Static functions wont' change the size, non-virtual functions -probably. I'll check it. Thanks! – Valentin H Apr 16 '15 at 11:26
  • 1
    @ValentinHeinitz Non-virtual non-special member functions do *not* affect a structure's state of being POD. C++14 [class]/10. – Angew is no longer proud of SO Apr 16 '15 at 11:58
2

It works because myStruct does not have a destructor. [Edit: I now see that you tried that, and it does crash. I would find the question interesting why it crashes with that dtor, since the dtor does not access any memory of the object.]

As others said, the second function of free[] besides potentially calling the elements' dtors (which doesn't happen here, as described) is to free the memory.

That works perfectly in your implementation because typically free store implementations just allocate a block of memory for that purpose whose size is kept in a book keeping location in that very memory. The size (once allocated) is type independent, i.e. is not derived from the pointer type on free. Cf. How does delete[] "know" the size of the operand array?. The malloc like, type agnostic allocator returns the chunk of memory and is happy.

Note that, of course, what you do is bogous and don't do that at home and don't publish it and make ppl sign non liability disagreements and don't use it in nuclear facilities and always return int from main().

Community
  • 1
  • 1
Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
1

the problem is that obj.U.structPtr points to a struct, which can have a constructor and destructor.

delete also requires the correct type, otherwise it cannot call the destructor.

So it is illegal to create a char array with new and delete it as an struct pointer.

It would be okay if you use malloc and free. This won't call the constructor and destructor.

mch
  • 9,424
  • 2
  • 28
  • 42