12

I'm dang certain that this code ought to be illegal, as it clearly won't work, but it seems to be allowed by the C++0x FCD.

class X { /* ... */};
void* raw = malloc(sizeof (X));
X* p = new (raw) X(); // according to the standard, the RHS is a placement-new expression
::operator delete(p); // definitely wrong, per litb's answer
delete p; // legal?  I hope not

Maybe one of you language lawyers can explain how the standard forbids this.

There's also an array form:

class X { /* ... */};
void* raw = malloc(sizeof (X));
X* p = new (raw) X[1]; // according to the standard, the RHS is a placement-new expression
::operator delete[](p); // definitely wrong, per litb's answer
delete [] p; // legal?  I hope not

This is the closest question I was able to find.

EDIT: I'm just not buying the argument that the standard's language restricting arguments to function void ::operator delete(void*) apply in any meaningful way to the operand of delete in a delete-expression. At best, the connection between the two is extremely tenuous, and a number of expressions are allowed as operands to delete which are not valid to pass to void ::operator delete(void*). For example:

struct A
{
  virtual ~A() {}
};

struct B1 : virtual A {};

struct B2 : virtual A {};

struct B3 : virtual A {};

struct D : virtual B1, virtual B2, virtual B3 {};

struct E : virtual B3, virtual D {};

int main( void )
{
  B3* p = new E();
  void* raw = malloc(sizeof (D));
  B3* p2 = new (raw) D();

  ::operator delete(p); // definitely UB
  delete p; // definitely legal

  ::operator delete(p2); // definitely UB
  delete p2; // ???

  return 0;
}

I hope this shows that whether a pointer may be passed to void operator delete(void*) has no bearing on whether that same pointer may be used as the operand of delete.

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    FYI: The FCD (N3092) is no longer the latest draft. The latest draft is N3225. I've been keeping the [c++-0x tag wiki page](http://stackoverflow.com/tags/c%2b%2b0x/info) updated with a link to the latest draft PDF. – James McNellis Dec 11 '10 at 18:47
  • 1
    Note that 5.3.5/2, which covers this, has been modified in the latest draft. It now says that the pointer may be "a pointer to a non-array object created by a previous _new-expression_," and a _new-expression_ does indeed include placement new expressions. I don't think that is intended. – James McNellis Dec 11 '10 at 18:54
  • @James: Thanks VERY much for the new draft. And 5.3.5 is exactly the section I'm thinking should forbid this but doesn't. Could you please look at my answer (I'm getting ready to pull in any changed language from the new draft) and let me know if you think it has any bearing on this question? – Ben Voigt Dec 11 '10 at 18:54
  • @James: great page on C++0x and thanks for the latest draft, I don't have the right to edit it (no bronze C++0x badge :p), do you think you could add Clang C++0x status. Implementation is just really starting (they were focusing on C++03 compliance up until now) but there's already a couple features implemented. Here is the link: http://clang.llvm.org/cxx_status.html – Matthieu M. Dec 11 '10 at 20:12
  • @James, would you please put your comment into an answer so I can accept it? – Ben Voigt Dec 19 '10 at 17:04

4 Answers4

7

The Standard rules at [basic.stc.dynamic.deallocation]p3

Otherwise, the value supplied to operator delete(void*) in the standard library shall be one of the values returned by a previous invocation of either operator new(size_t) or operator new(size_t, const std::nothrow_t&) in the standard library, and the value supplied to operator delete[](void*) in the standard library shall be one of the values returned by a previous invocation of either operator new[](size_t) or operator new[](size_t, const std::nothrow_t&) in the standard library.

Your delete call will call the libraries' operator delete(void*), unless you have overwritten it. Since you haven't said anything about that, I will assume you haven't.

The "shall" above really should be something like "behavior is undefined if not" so it's not mistaken as being a diagnosable rule, which it isn't by [lib.res.on.arguments]p1. This was corrected by n3225 so it can't be mistaken anymore.

jww
  • 97,681
  • 90
  • 411
  • 885
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • That certainly applies to calling the deallocation functions directly using function-call syntax. But I don't think it's immediately applicable to invocations of `operator delete`, since the pointer I use as the operand of `operator delete` is not necessarily the one ultimately passed to the deallocation function. Well, reading 5.3.4 `[expr.new]` more closely, it is the same for scalar new. So think in terms of array placement new instead, followed by array delete. – Ben Voigt Dec 11 '10 at 19:11
  • @Ben I don't understand your comment. – Johannes Schaub - litb Dec 11 '10 at 19:16
  • The compiler is supposed to be responsible for following the rules for deallocation functions, so long as I follow the rules for *new-expression* and *delete-expression*. Which this code unfortunately does, according to the current wording quoted by James. – Ben Voigt Dec 11 '10 at 19:18
  • I added to the question code snippet the call that your quote clearly makes illegal, for comparison. – Ben Voigt Dec 11 '10 at 19:21
  • @Ben no it does not, because you pass a pointer to `delete` which in turn calls `operator delete` with that pointer (or with a pointer adjusted by a constant factor), and which in turn will cause undefined behavior. So you don't play with the rules. – Johannes Schaub - litb Dec 11 '10 at 19:21
  • Fine, I'll put in the array version, where that line of reasoning doesn't hold up. – Ben Voigt Dec 11 '10 at 19:22
  • @Ben same problem with the array version. `p` or `p` adjusted by a constant term wasn't assuredly returned by one of the listed `operator new[]` functions, so behavior is undefined. – Johannes Schaub - litb Dec 11 '10 at 19:28
  • @Ben the text you quoted doesn't concern the requirement that `operator delete(void*)` may not be passed a pointer not returned by one of the listed `operator new` versions. – Johannes Schaub - litb Dec 11 '10 at 19:35
  • I agree that the code is absolutely positively broken and can never work. I just think there's a defect because the standard promises to make it work, even though it's impossible (short of the compiler magically figuring out to call the placement version of `::operator delete(void*)` which is a no-op, or passing NULL to the deallocation function, again requiring magic to know to do so). – Ben Voigt Dec 11 '10 at 19:36
  • @Johannes: Where in the standard do you get "p or p adjusted by a constant term"? That may be what most practical implementations do during delete of an array or *in the absence of a virtual destructor*, but it isn't guaranteed by the standard that I can see, nor is it even correct in the general case. – Ben Voigt Dec 12 '10 at 23:00
3

The compiler doesn't really care that p comes from a placement new call, so it won't prevent you from issuing delete on the object. In that way, your example can be considered "legal".

That won't work though, since "placement delete" operators cannot be called explicitly. They're only called implicitly if the constructor throws, so the destructor can run.

Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
2

I suppose you might get away with it (on a particular compiler) if

  1. new/delete are implemented in terms of malloc/free and
  2. the placement new actually uses the same mechanism for keeping track of the destructor associated with allocations as the standard new, so that the call to delete could find the right destructor.

But there is no way it can be portable or safe.

dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
  • 1
    It's definitely not safe, because that delete expression is required to call a deallocation function, passing a pointer that didn't come from the matching allocation function. – Ben Voigt Dec 11 '10 at 18:52
2

I found this in the library section of the standard, which is about as counter-intuitive a location (IMO) as possible:

C++0x FCD (and n3225 draft) section 18.6.1.3, [new.delete.placement]:

These functions are reserved, a C ++ program may not define functions that displace the versions in the Standard C ++ library (17.6.3). The provisions of (3.7.4) do not apply to these reserved placement forms of operator new and operator delete.

void*  operator  new(std::size_t  size,  void*  ptr)  throw();
void*  operator  new[](std::size_t  size,  void*  ptr)  throw();
void  operator  delete(void*  ptr,  void*)  throw();
void  operator  delete[](void*  ptr,  void*)  throw();

Still, the section defining legal expressions to pass to delete is 5.3.5, not 3.7.4.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720