c++copy-constructordefault-copy-constructor

Code involving default copy constructor should segfault, but works just fine


I was running a little experiment to better understand when constructors/destructors are implicitly called, and I discovered something really strange.

The following code invokes the default copy constructor when myFunction() is called. Before exiting the scope of myFunction(), my user-defined ~myClass() is invoked, which should be calling free() on dangerData. However, this doesn't seem to be freeing dangerData!

#include <cstdio>
#include <cstdlib>

class myClass {
    static int nextid;
    int myID;
    char *dangerData;

    public:

    myClass() {
        myID = nextid++;
        printf("Constructing myClass number %d\n", myID);
        dangerData = (char *)malloc(1024);
        dangerData[12] = 0;
    }

    ~myClass() {
        printf("Destructing myClass number %d. dangerData = %p\n", myID, (void *) dangerData);
        dangerData[12] = 'a'; //Mark the array
        free(dangerData); //This call chould free the array... but it doesn't!
    }

    void msg() {
        printf("Message from myClass number %d. dangerData[12] = %d\n", myID, dangerData[12]);
    }
};

int myClass::nextid = 1;

void myFunction(myClass param) {
    param.msg();
}

int main() {
    myClass m;
    myFunction(m); //Calls default copy constructor
    m.msg();
    return 0;
}

The output from this code is:

Constructing myClass number 1
Message from myClass number 1. dangerData[12] = 0
Destructing myClass number 1. dangerData = 02f71458. dangerData[12] = 0
Message from myClass number 1. dangerData[12] = 97
Destructing myClass number 1. dangerData = 02f71458. dangerData[12] = 97

The first call to the desctructor is definitely calling free() on the malloc'd array, however the call to m.msg() in main() can still access it without segfaulting!

Is this supposed to be happening?


Solution

  • Is this supposed to be happening?

    Nothing particular is "supposed" or rather "expected" to happen here - you are invoking undefined behavior, so anything could happen, including "nothing".

    the call to m.msg() in main() can still access it without segfaulting!

    This is not terribly surprising (but again, nothing you should ever count on). Freeing the memory more or less tells the OS that that memory can be reused/overwritten freely. The more focus there is on speed over safety between your compiler, OS and their settings, the less work they will invest into marking this memory as inaccessible, and since you just used that exact memory location you would have to run under very stringent OS checks to even get a segfault there. It is also exceedingly unlikely for that 13th character to be overwritten in the time between the two msg() calls, so you are likely to read back the same thing again.

    But all this just pertains to this small example and is, again, nothing to rely on. Undefined behavior (somewhat unfortunately) does not automatically mean "your program will segfault now". It may do, or it may segfault two minutes later in a completely different part, or silently output wrong results, or format your hard drive. Or it may run as you intended, this one time.