Saturday, 29 October 2016

In C++, when reassigning an object, why does the constructor fire before the destructor?

I'm a beginner in C++. I'm compiling my code with GCC 9.2.0. Recently I've stumbled upon some weird behaviour which is segfaulting me.



I have a certain class wrapper for Hyperscan. I've made it so it basically has the same functionality as Python's re, but many times faster and multithreaded. I have a class regex which I usually allocate like



auto some_regex = regex();



Now, the what the constructor does, for those of you unfamiliar with Hyperscan is 2 (actually 3) things -> first it allocated memory for the regular expression database. This is done by calling their API, which is in C. Then, if it succeeds, you allocate scratch space. Now, because my implementation supports multithreading, my class instance holds a member which is a vector of scratch spaces. Nevertheless, I allocated a scratch space for each thread I use, also with their API. What I then have are a pointer to the compiled regex database and a vector of scratch space pointers.



When deconstructing the object, I have to free the database and scratch spaces (I also use the provided functions from the API) if they're allocated. So far so good. The problem arises when I do something like this:



auto some_regex = regex();
some_regex = regex(R"(\s+)");



Do you see a problem here? Maybe I'm too much of a noob to see it, but it looks good to me. But nope. It segfaults in the destructor while trying to free the database memory. Debugging it, I have come across these actions:



allocate memory for some_regex
initialize some_regex
allocated new memory for some_regex
initialize new some_regex
run destructor for the old some_regex
run destructor for the new some_regex



Basically, the destructor for the first instance is run only after the constructor for the second one fires. What this does is essentially making it so that the first destructor frees up the data allocated and initialized by the second constructor instead of just cleaning itself.



Now, things get weirder. I looked into the addresses at hand, because I started setting the freed up pointers to nullptr - it turns out that this happens:



allocate memory for some_regex
initialize some_regex
allocated new memory for some_regex
initialize new some_regex
run destructor for the old some_regex
database pointer is not 0, free it

set database pointer to nullptr
run destructor for the new some_regex
database pointer is not 0, free it (BUT THIS IS ALREADY FREED MEMORY???)
SIGSEGV


I'm utterly confused. Even after setting the database pointer to null explicitly, the new one, after the constructor did its job, assigns it a new address.



This reproduces the behaviour:




#include 
#include

mock::mock()
{
std::cout << "Starting constructor..." << std::endl;
this->test = (int*)malloc(sizeof(int));
*this->test = 0;
std::cout << "Ending constructor..." << std::endl;
}


mock::mock(int x)
{
std::cout << "Starting full constructor..." << std::endl;
this->test = (int*)malloc(sizeof(int));
*this->test = x;
std::cout << "Ending full constructor..." << std::endl;
}

mock::~mock()

{
std::cout << "Starting destructor...";
free(this->test);
this->test = nullptr;
std::cout << "Address of test is " << this->test << std::endl;
std::cout << "Ending destructor..." << std::endl;
}


run it in main like this:




auto some_mock = mock();
some_mock = mock();
some_mock = mock();
some_mock = mock(3);



Expected result:




Starting constructor...
Ending constructor...
Starting destructor...Address of test is 0
Ending destructor...
Starting constructor...
Ending constructor...
Starting destructor...Address of test is 0
Ending destructor...
Starting constructor...
Ending constructor...

Starting destructor...Address of test is 0
Ending destructor...
Starting full constructor...
Ending full constructor...
Starting destructor...Address of test is 0
Ending destructor...
Process returned 0


Actual result:




Starting constructor...
Ending constructor...
Starting constructor...
Ending constructor...
Starting destructor...Address of test is 0
Ending destructor...
Starting constructor...
Ending constructor...
Starting destructor...Address of test is 0

Ending destructor...
Starting full constructor...
Ending full constructor...
Starting destructor...Address of test is 0
Ending destructor...
*** Error in `/deploy/cmake-build-local-debug/CPP': double free or corruption (fasttop): 0x0000000002027c40 ***
.
.
.
Starting destructor...

Process finished with exit code 134 (interrupted by signal 6: SIGABRT)


Is this intended behaviour? If so, why??? And how can I get around it (aside from using new and deleting before reassigning)?

No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...