0

Is this code memory leeks free?

char *buffer = (char *) (::operator new(n));
delete buffer;

Does it work any different from "traditional bracket-form" option (when we talk about primitive types):

char *buffer = new char[n];
delete[] buffer;

I guess, there are 3 options:

a) will be freed only 1 byte (n - 1 leaked)

b) will be freed entire allocated block (n bytes)

c) undefined behaviour (implementation depends)

My thoughts: for primitive type char only free is triggered (no destructors are called) and free knows how many bytes should be freed (which is stored in allocator metainformation). So, this code should be correct.

Added:

Mixing of operator new/delete and expression new/delete will lead to undefined behavior. So that's a bad code.

Option using operator delete is also bad according to http://eel.is/c++draft/expr.delete#2:

char *buffer = (char *) (::operator new(n));
::operator delete(buffer);

So the only variant:

char *buffer = new char[n];
delete[] buffer;
A.King
  • 172
  • 2
  • 11
  • 3
    There is no good reason to use `(char *) (::operator new(n))` instead of `new char[n]`, why do you need this? – Remy Lebeau Oct 25 '19 at 23:44
  • Yes, no reason. Only theoretical interest. Also, it's possible to pass to `new` (without brackets) arbitrary size to allocate. How am I supposed to free memory after that call? – A.King Oct 25 '19 at 23:47
  • The abomination "_char *buffer = (char *) (::operator new(n));_" will never lead to anything good. – Ted Lyngmo Oct 25 '19 at 23:54
  • "_My thoughts:_" - it's good to figure out what happens when you do stuff wrong. It's even better to figure out how to do stuff right. – Ted Lyngmo Oct 25 '19 at 23:57
  • @A.King You cannot pass arbitrary size to `new`. It will always allocate as `sizeof(T)` bytes. – walnut Oct 26 '19 at 00:24
  • @uneven_mark To `new expression`, yes. What about `operator new`? Whatever you pass to `operator new` it will ignore it and somehow infer `sizeof(T)` instead? – A.King Oct 26 '19 at 00:41
  • 2
    @A.King You give `operator new` the size you want explicitly (this does not create an object with a type) and deallocate the memory it allocated with `operator delete`. A *new-expression* will infer the required size as `sizeof(T)` from the type given to the expression and call the appropriate `operator new` with the required size. – walnut Oct 26 '19 at 00:50
  • 1
    Regarding your edit: The first variant is also technically wrong (although it will probably work in practice), because it does not create any objects and so accessing the values in this "buffer" will cause undefined behavior. The only correct way to do it is the last one. (The `(char*)` cast is not needed and may hide bugs, so remove it.) – walnut Oct 26 '19 at 01:26
  • @uneven_mark Interesting, so even if I want only single char in a heap, I should also use `new char[1]` and `delete[]`, correct? – A.King Oct 26 '19 at 01:53
  • 1
    @A.King You can, but there are reasons not to do that. A single non-array object can be allocated and constructed with `new char` and destructed and deallocated with `delete`, i.e. the non-array versions of the *new-expression* and *delete-expression*. – walnut Oct 26 '19 at 01:58
  • @uneven_mark But `char` is not an object, is it? – A.King Oct 26 '19 at 02:02
  • 1
    `char` is a type, but `new char` creates an *object* of type *char* and returns a pointer to it. The word "object" is used here in a wider meaning than only instances of classes, it applies also to *objects of non-class type* as well. This is the definition of *object* that the C++ standard uses, not the definition used in general in object oriented programming. – walnut Oct 26 '19 at 02:14
  • Ok, thanks, get it. Still don't understand "accessing the values in this "buffer" will cause undefined behavior. ". Any links for that? So, there is no safe way of using `operator new/delete` at all? – A.King Oct 26 '19 at 02:29
  • @A.King As I said in practice this will work even though it is technically undefined behavior. At the moment I can only give you [cppreference.com](https://en.cppreference.com/w/cpp/language/lifetime) as a reference, look under the heading "Access outside of lifetime". Then also have a look at [this cppreference.com page](https://en.cppreference.com/w/cpp/language/object). This is rather information-dense material though. – walnut Oct 26 '19 at 02:49
  • One could use *placement new* to create valid objects in the buffer allocated that way... – Aconcagua Oct 26 '19 at 09:28

2 Answers2

2

As per http://eel.is/c++draft/expr.delete#2

In a single-object delete expression, the value of the operand of delete may be

  • a null pointer value
  • a pointer to a non-array object created by a previous new-expression,
  • or a pointer to a subobject representing a base class of such an object.

If not, the behavior is undefined.

As a call to operator new is not a new-expression, I would believe the behavior is undefined.

Also, "undefined behavior" does not mean that it is implementation dependent as you suggest (implementation dependent things are designated as “implementation-defined” in the standard). Undefined behavior means exactly what it means, the compiler is allowed to do whatever it likes. Check this question: Undefined, unspecified and implementation-defined behavior

spectras
  • 13,105
  • 2
  • 31
  • 53
  • It is not even a pointer to an object, because `operator new` does not create an object. – walnut Oct 26 '19 at 00:20
  • @uneven_mark indeed, I did read that from right to left so I went with the not-being-a-new-expression point, but it is not an object either. If we want to be truly thorough, we could argue the code is valid if `operator new` always returns a null pointer value. – spectras Oct 26 '19 at 00:23
  • 1
    Well, this is the throwing version, so it must either return a non-null pointer or throw an exception (https://timsong-cpp.github.io/cppwp/n4659/new.delete.single#3). – walnut Oct 26 '19 at 00:27
  • 1
    Good point, I did not know the requirement applied to replacement too. There is always something to learn :) – spectras Oct 26 '19 at 00:31
1
char* buffer = static_cast<char*>(::operator new(n));
::operator delete(buffer);

could be used for manual/custom memory management:

class Demo
{
    // some pretty complex class...
};

Demo* buffer = static_cast<Demo*>(::operator new(sizeof(Demo)));

new (buffer) Demo();
// placement new requires explicit destructor calls!
// (only occassion you ever need to do so)
buffer->~Demo();

::operator delete(buffer);

or similarly the array variant of. If you have specific alignment requirements that are not covered by default alignment, then there are overloads accepting alignment specification as second parameter.

Usually, you won't ever need such stuff, but it can be useful for performance critical code (to avoid subsequent allocations and deallocations), if you have limited memory resources (e. g. on micro controllers) or if you are in a safety critical environment (where you might be allowed to allocate only at startup, but not later on, to prevent failures due to std::bad_alloc).

Aconcagua
  • 24,880
  • 4
  • 34
  • 59