Created on 1999-08-29.00:00:00 last changed 171 months ago
[ Batavia: Robert voiced serious reservations about backwards compatibility for his customers. ]
[ Reopened due to a gcc conversation between Howard, Martin and Gaby. Forwarding or not is visible behavior to the client and it would be useful for the client to know which behavior it could depend on. ]
Rationale:
Yes, they may become unlinked, and that is by design. If a user replaces one, the user should also replace the other.
Proposed resolution:
Change 18.5.1.1 [new.delete.single]:
void* operator new(std::size_t size, const std::nothrow_t&) throw();-5- Effects: Same as above, except that it is called by a placement version of a new-expression when a C++ program prefers a null pointer result as an error indication, instead of a bad_alloc exception.
-6- Replaceable: a C++ program may define a function with this function signature that displaces the default version defined by the C++ Standard library.
-7- Required behavior: Return a non-null pointer to suitably aligned storage (3.7.4), or else return a null pointer. This nothrow version of operator new returns a pointer obtained as if acquired from the (possibly replaced) ordinary version. This requirement is binding on a replacement version of this function.
-8- Default behavior:
- Calls operator new(size).
- If the call to operator new(size) returns normally, returns the result of that call, else
- if the call to operator new(size) throws an exception, returns a null pointer.
Executes a loop: Within the loop, the function first attempts to allocate the requested storage. Whether the attempt involves a call to the Standard C library function malloc is unspecified.Returns a pointer to the allocated storage if the attempt is successful. Otherwise, if the last argument to set_new_handler() was a null pointer, return a null pointer.Otherwise, the function calls the current new_handler (18.5.2.2). If the called function returns, the loop repeats.The loop terminates when an attempt to allocate the requested storage is successful or when a called new_handler function does not return. If the called new_handler function terminates by throwing a bad_alloc exception, the function returns a null pointer.-9- [Example:
T* p1 = new T; // throws bad_alloc if it fails T* p2 = new(nothrow) T; // returns 0 if it fails--end example]
void operator delete(void* ptr) throw();void operator delete(void* ptr, const std::nothrow_t&) throw();-10- Effects: The deallocation function (3.7.4.2) called by a delete-expression to render the value of ptr invalid.
-11- Replaceable: a C++ program may define a function with this function signature that displaces the default version defined by the C++ Standard library.
-12- Requires: the value of ptr is null or the value returned by an earlier call to the
default(possibly replaced) operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&).-13- Default behavior:
- For a null value of ptr, do nothing.
- Any other value of ptr shall be a value returned earlier by a call to the default operator new, which was not invalidated by an intervening call to operator delete(void*) (17.4.3.7). For such a non-null value of ptr, reclaims storage allocated by the earlier call to the default operator new.
-14- Remarks: It is unspecified under what conditions part or all of such reclaimed storage is allocated by a subsequent call to operator new or any of calloc, malloc, or realloc, declared in <cstdlib>.
void operator delete(void* ptr, const std::nothrow_t&) throw();-15- Effects: Same as above, except that it is called by the implementation when an exception propagates from a nothrow placement version of the new-expression (i.e. when the constructor throws an exception).
-16- Replaceable: a C++ program may define a function with this function signature that displaces the default version defined by the C++ Standard library.
-17- Requires: the value of ptr is null or the value returned by an earlier call to the (possibly replaced) operator new(std::size_t) or operator new(std::size_t, const std::nothrow_t&).
-18- Default behavior: Calls operator delete(ptr).
Change 18.5.1.2 [new.delete.array]
void* operator new[](std::size_t size, const std::nothrow_t&) throw();-5- Effects: Same as above, except that it is called by a placement version of a new-expression when a C++ program prefers a null pointer result as an error indication, instead of a bad_alloc exception.
-6- Replaceable: a C++ program can define a function with this function signature that displaces the default version defined by the C++ Standard library.
-7- Required behavior:
Same as for operator new(std::size_t, const std::nothrow_t&). This nothrow version of operator new[] returns a pointer obtained as if acquired from the ordinary version.Return a non-null pointer to suitably aligned storage (3.7.4), or else return a null pointer. This nothrow version of operator new returns a pointer obtained as if acquired from the (possibly replaced) operator new[](std::size_t size). This requirement is binding on a replacement version of this function.-8- Default behavior:
Returns operator new(size, nothrow).
- Calls operator new[](size).
- If the call to operator new[](size) returns normally, returns the result of that call, else
- if the call to operator new[](size) throws an exception, returns a null pointer.
void operator delete[](void* ptr) throw(); void operator delete[](void* ptr, const std::nothrow_t&) throw();-9- Effects: The deallocation function (3.7.4.2) called by the array form of a delete-expression to render the value of ptr invalid.
-10- Replaceable: a C++ program can define a function with this function signature that displaces the default version defined by the C++ Standard library.
-11- Requires: the value of ptr is null or the value returned by an earlier call to operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&).
-12- Default behavior: Calls operator delete(ptr) or operator delete[](ptr
, std::nothrow) respectively.
As specified, the implementation of the nothrow version of operator new does not necessarily call the ordinary operator new, but may instead simply call the same underlying allocator and return a null pointer instead of throwing an exception in case of failure.
Such an implementation breaks code that replaces the ordinary version of new, but not the nothrow version. If the ordinary version of new/delete is replaced, and if the replaced delete is not compatible with pointers returned from the library versions of new, then when the replaced delete receives a pointer allocated by the library new(nothrow), crash follows.
The fix appears to be that the lib version of new(nothrow) must call the ordinary new. Thus when the ordinary new gets replaced, the lib version will call the replaced ordinary new and things will continue to work.
An alternative would be to have the ordinary new call new(nothrow). This seems sub-optimal to me as the ordinary version of new is the version most commonly replaced in practice. So one would still need to replace both ordinary and nothrow versions if one wanted to replace the ordinary version.
Another alternative is to put in clear text that if one version is replaced, then the other must also be replaced to maintain compatibility. Then the proposed resolution below would just be a quality of implementation issue. There is already such text in paragraph 7 (under the new(nothrow) version). But this nuance is easily missed if one reads only the paragraphs relating to the ordinary new.
N2158 has been written explaining the rationale for the proposed resolution below.
History | |||
---|---|---|---|
Date | User | Action | Args |
2010-10-21 18:28:33 | admin | set | messages: + msg1856 |
2010-10-21 18:28:33 | admin | set | messages: + msg1855 |
2010-10-21 18:28:33 | admin | set | messages: + msg1854 |
2010-10-21 18:28:33 | admin | set | messages: + msg1853 |
1999-08-29 00:00:00 | admin | create |