Please help me about reloading operator new and delete


#1

Please notice the pic; Why the object destructed twicely !?!?!
If Do not use the pointer convertion (" ::delete p; "), the complier will apear a warning, but the object only shows a one destructor. Looking like normal running.
Why?!?! Im chaos.



#2

Because void pointer p is converted to pointer type X*

First delete is for xp, next is for p


#3

Thanks Jonne
But how can i close this warning ?!


#4

You allocated a char*, so you have to delete a char*, not a void*.


#5

Thanks FManga
it has correct!

void* operator new(size_t sz){
    cout << "reloading new" << endl;
    return ::new char[sz];
}
void operator delete(void* p){
    cout << "reloading delete" << endl;
    ::delete [](char*)p;
}

#6

Hi! Jonne,
Could you tell me this detail ?
I don’t know why yet :joy:


#7

Ok I am not an expert on C++

but if you cast void pointer p into type X pointer, that temporary instance must be deleted automatically when the function returns. Therefore destructor of class X is called a second time.

Someone who knows more can correct me if I am wrong


#8

I noticed when only cout in operator delete, the void* p destructor still running.
what means first delete for p , next the complier found a global new for xp, so it do ~X() again ???


#9

A C++ question and nobody notified me!? :P


@79859899

(I’ve left out the std::cout code to simplify the explanation.)

In the case with the cast, what happens is:

  1. new X is evaluated
    1. X::new is called
    2. An array of char (char[sz]) is allocated with ::new[]
    3. A pointer to that array is returned as a void *
  2. The resulting void * is initialised with X::X(), then converted to X * and assigned to the variable xp
  3. delete xp; is evaluated
    1. The object at xp is deconstructed with X::~X()
    2. X::delete is called
    3. p (a void *) is converted to X *
    4. The object at p is deconstructed with X::~X()
    5. The memory at p is deallocated by ::delete

In the case without the cast what happens is:

  1. new X is evaluated
    1. X::new is called
    2. An array of char (char[sz]) is allocated with ::new[]
    3. A pointer to that array is returned as a void *
  2. The resulting void * is initialised with X::X(), then converted to X * and assigned to the variable xp
  3. delete xp; is evaluated
    1. The object at xp is deconstructed with X::~X()
    2. X::delete is called
    3. The memory at p (a void *) is deallocated by ::delete

The reason for this is because of how ::new and ::delete work.
Functionally new and delete are much like C’s malloc and free*,
but with the added caveat that the compiler will insert the correct constructor and destructor calls after new and before delete (respectively).

In fact new and delete are often implemented with std::malloc and std::free,
because that’s how they are intended to be implemented.
(Note though they technically don’t have to be implemented that way, but I won’t get into the specifics of that now.)

Technically you allocated char[sz] with ::new[],
so it must be deallocated with ::delete[] called on char *.
(Also to convert the void * to char * you should prefer static_cast.)

So the ideal X should look something like this:

class X
{
public:
	X()
	{
		std::cout << "ctor\n";
	}

	~X()
	{
		std::cout << "dtor\n";
	}

	void * operator new(std::size_t size)
	{
		std::cout << "new\n";
		return ::new char[size];
	}

	void operator delete(void * pointer)
	{
		std::cout << "delete\n";
		::delete[] static_cast<char *>(pointer);
	}
};

While I’m at it, I’d like to mention that using namespace std; is bad practice,
and that you should use \n rather than std::endl;
std::endl doesn’t just print a new line, it also forces the stream to be flushed,
which is an expensive and often unnecessary operation.
(See https://en.cppreference.com/w/cpp/io/manip/endl for more info.)

Also you should use std::size_t because technically <cstddef> is required to declare std::size_t but there’s no requirement for it to declare ::size_t.
(See https://en.cppreference.com/w/cpp/header#C_compatibility_headers for more info.)


Almost right, but not quite.

Technically it’s called a second time simply because of the compiler inserting calls to the constructor.

When delete xp is called, it gets translated to:

xp->~X();
X::delete(xp);

Then when delete (X*)p; is called, it gets translated to:

(static_cast<X *>(p))->~X();
::delete(static_cast<X *>(p));

Techincally speaking, even when the correct code is used, there’s still a second destructor being called.
In the solution I gave earlier in my comment, ::delete[] static_cast<char *>(pointer);, the code is translated to:

for(std::size_t index = 0; index < /*number of chars*/; ++index)
    (static_cast<char *>(pointer)[index]).~char();
::delete[](static_cast<char *>(pointer));

The exact details of how the destructor is called for each char object are (I believe) implementation defined, hence I’ve left a comment instead of a proper expression.

But what’s notable about this is that ~char is technically a nop, so that entire loop will be optimised away by the compiler.
(I think that optimisation might actually be required by the standard, though I’m not completely sure, I’d have to check.)


Lastly, @79859899, why are you creating custom new and delete operators in the first place?

If it’s just for learning purposes then fair enough, but if it’s for something specific then you’re probably trying to do the wrong thing.

I’m not saying you won’t ever need to know how to do it,
but it’s not something you should ever need to do in a normal program.

You should only ever need to do it if you’re implementing C++ infrastructure for an environment that doesn’t already have it.
E.g. Arduino didn’t use to have a placement new operator (and then I came along…).


#10

thanks Pharap!!!
it’s just a exercise in <thinking in C++> new and delete chapter.


#11

Fair enough.

Like I say though, you probably won’t ever need to write a custom new or delete,
so it’s probably not a very practical excercise.