How is this possible?

Started by
5 comments, last by AsOne 16 years, 9 months ago
I wrote a simple "smart" pointer class just to get myself more familiar with overloading certain operators. However, I have encountered some strange behavior when calling the classes destructor explicitly. Although in practice this would not be an issue, but something weird seems to be happening. Consider the following code.

struct C
{
	void foo()
	{
		std::cout << "C::foo\n";
	}
};


int main()
{
	TSmart<C> x;
	x.~TSmart<C>();
	x->foo();
	return 0;
}



The relevant parts of TSmart are:

TSmart() {
		p = new T;
	}
~TSmart() {
		std::cout << "~TSmart()\n";
		delete p;
	}
T *const operator->() {
		return p;
	}



To my surprise the output of this program is: ~TSmart() C::foo ~TSmart() ... then an error indicating the same memory location has been freed twice. How is x->foo() being successfully called? The pointer to the C object is being deleted before this. What is going on here?
Advertisement
Quote:Original post by AsOne
How is x->foo() being successfully called? The pointer to the C object is being deleted before this. What is going on here?
It "just so happens" to work because you're not accessing any invalid memory. If foo() tried to read/write any member variables, then you'd have a problem (I.e. corrupting memory)
OK that makes sense, I should have realized before that when deleting a pointer the actual data isn't being erased. However, this runs without problem.
struct C{	C()	:		i(99)	{}	void foo()	{		i++;		std::cout << "C::foo\n";		std::cout << i << "\n";	}	int i;};int main(){	TSmart<C> x;	x->foo();	x.~TSmart<C>();	x->foo();	x->foo();	return 0;}

The output is:
C::foo
100
~TSmart()
C::foo
1
C::foo
2
~TSmart()

Would have been stranger if 101 and 102 were present. Is this just because the memory location where i is isn't being overwritten since the time of deletion?
Steve's right, here is what goes on under the hood.

When C::Foo is called it is passed the implicit 'this' as its first parameter. Then when the function reads or rights member variables, that pointer is dereferenced. So the following are equivalent:
struct C{	int a;	void foo( )	{		a = 5;		std::cout << "C::foo\n";	}};///////////////////////////////////////struct C{	int a;};void foo(C* this){	C->a = 5;	std::cout << "C::foo\n";};


Because your code never does anything with member variables, the invalid this is never dereferenced and thus never causes a problem. The following code will probably work too:

struct A{	void say( std::string msg )	{		std::cout << msg;	}};A* a = NULL;a->say("hello");


I'm pretty sure this behaviour is undefined, so I would try to avoid it.

<edit>I didn't see your last post, don't really understand why that's happening...
What compiler, and is it a debug or release build? I'd guess that the compiler is crapping up the memory occupied by the object when delete is called, to make sure you don't accidently use it again.

You could also have a look at the generated dissasembly and see if there's anything there.
Good to see good curiosity! This is the path to real knowledge and understanding.
Many people only tinker until it works, not until they understand.

It seems the compiler in is overriding some stuff, maybe to surface bugs.
It is better to know when you are using freed memory right away, rather than allow it to work for a while and crash later, which is why on debug builds extra writes may be inserted.

You can print the address of "i" and the value of "p", it will tell you if the pointer is overridden or the data is. Or better, you could step with a debugger and watch the variables without printing... though it will be harder to post questions [smile]
I'm using g++ to compile, the output is the same regardless of what optimization level used. The address of i remains the same even after the object is deleted. Thus, the data is just being overwritten.

What's interesting is if I change main to this:
int main(){	TSmart<C> x;	x->foo();	x.~TSmart<C>();	x->foo();	C *p = new C;	x->foo();	return 0;}


The output becomes:
C::foo
100
~TSmart()
C::foo
1
C::foo
100
~TSmart()

However, if I throw in some random allocations before the last x->foo() then I get a segmentation fault.

I guess the original code worked just because there were no allocations to erase the data from the C object.

This topic is closed to new replies.

Advertisement