Doc. No.: | P0035R3 |
---|---|
Date: | 2016-05-26 |
Reply to: | Clark Nelson |
Phone: | +1-503-712-8433 |
Email: | clark.nelson@intel.com |
Audience: | Core, Library Evolution |
To codify widespread existing practice, C++11 added the ability to specify increased alignment (a.k.a. over-alignment) for class types. Unfortunately (but also consistently with existing practice), C++11 did not specify any mechanism by which over-aligned data can be dynamically allocated correctly (i.e. respecting the alignment of the data). For example:
class alignas(16) float4 { float f[4]; }; float4 *p = new float4[1000];
In this example, not only is an implementation of C++ not required to allocate properly-aligned memory for the array, for practical purposes it is very nearly required to do the allocation incorrectly. In any event, it is certainly required to perform the allocation by a process that does not take the specified alignment value into account.
This represents a hole in the support for alignment in the language, which really needs to be filled.
Significant changes in this revision relative to P0035R1, including all changes in the proposed wording, are enclosed in boxes.
Intel has released a compiler
that largely implements the language changes discussed herein,
except that, to guarantee backward compatibility,
the additional overloads are declared in a new header
(<aligned_new>
),
instead of being predeclared or
declared in <new>
.
To date, there has not yet been enough experience with the implementation to prove its viability. Nevertheless, it seems appropriate to get this issue back on the committee's radar, so that a decision can be made about it for the C++17 time frame.
Pablo Halpern has provided me with much valuable feedback and assistance.
One of the first questions that needs to be settled about the future direction is the degree to which backward compatibility with C++11/14 needs to be maintained. On the one hand, in an ideal world, for an example like the one above, it would be obvious that the specified alignment should be honored.
On the other hand,
there's no way to achieve that ideal
without at least potentially changing the behavior
of some programs conforming to an earlier standard.
For example, a program might asssume control of dynamic allocation
through the use of class-specific operator new
and operator delete
functions,
or by replacing the global functions.
These functions don't take any alignment argument.
If a different function is used instead,
which is somehow passed an alignment value,
some degree of backward compatibility is lost.
When backward compatibility and the ideal future direction are in conflict, which should take precedence, and to what degree?
If perfect backward compatibility were required,
one way to ensure that might be to require that a new header
– say <aligned_new>
–
be included in order to get new dynamic allocation
for over-aligned types.
But that would sacrifice convenience and/or correctness;
using alignas
by itself would presumably never be enough
to get correctly aligned dynamic allocation.
Another obvious position to take would be that backward compatibility with C++98, which had no alignment specifier, needs to be complete. This might suggest that dynamic allocation should differ between types involving alignment specifiers and types that don't – which some might consider to be an unfortunate complication.
In C++11/14, when an over-aligned class type has its own dynamic memory allocation functions, it would be reasonable to hope that those functions already do the right thing with respect to alignment, and dangerous to make any change. However, the only way over-alignment could be accommodated by global allocation and deallocation functions would be to replace them with functions that always provide the strictest alignment used by any type in the program. It may be reasonable to assume that very few programs go to that length, instead of using class-specific allocation/deallocation.
Therefore, it may be acceptable to abandon backward compatibility with C++14 with respect to calling a global allocation function for dynamic allocation of an over-aligned type. But if so, that may well be the only acceptable case.
To minimize the possibility of conflict with existing placement allocation functions, it might be advisable to invent a new standard enumeration type to use for alignment parameters; for example:
namespace std { enum class align_val_t: size_t; }; void *operator new(std::size_t, std::align_val_t); // new overload
It's not clear that this type would need
any named constants of its own;
it just needs to be able to represent alignment values,
which are associated with type size_t
.
It should perhaps nevertheless be a scoped enumeration,
to prevent the possibility that a value of that type
would inadvertently be converted to some integer type,
and match an existing placement allocation function.
If an allocation function that takes an alignment value is available, it should be used, for the sake of generality; but if no such function is available, a function that doesn't take one should be used, for backward compatibility. This suggests a new rule for new-expressions: attempting to find an allocation function in two phases, with two different sets of arguments.
It should be kept in mind that, under the current language rules, any class-specific allocation functions effectively hide all global allocation functions, including the ones in the standard library. For example, the following is invalid:
#include <new> class X { void *operator new(size_t); // no operator new(size_t, std::nothrow_t&) void operator delete(void *); }; X *p1 = new X; // uses X::operator new(size_t) X *p2 = new(nothrow) X; // error // ::operator new(size_t, std::nothrow_t&) is not considered
It is possible to imagine adjusting the rules to enable finding an alignment-aware allocation function more often, but that would also make it more likely that some programmers would write programs believing – incorrectly – that they have taken over complete control of the way that their class is dynamically allocated.
What implementation techniques should the standard allow for allocation and deallocation of aligned memory?
In POSIX, there is a function named posix_memalign
that can allocate over-aligned memory;
free
is used to free the blocks it allocates.
On Windows, on the other hand, of course malloc
,
realloc
and free
are supported
for default-aligned memory.
In addition, for over-aligned memory, there are functions named
_aligned_malloc
, _aligned_realloc
,
and _aligned_free
.
Memory that's allocated by _aligned_malloc
must be freed by _aligned_free
,
and memory that's allocated by malloc
must be freed by free
.
So logically, there are two disjoint, non-interoperable memory arenas;
the program has to know to which arena a block belongs
(i.e. how it was allocated)
in order to be able to free it.
This is almost certain to be true of any implementation where over-aligned memory allocation is layered on top of “plain old” default-aligned memory allocation. There are probably many such implementations, and they're not likely to go away soon.
In an environment where information about the method used
to allocate a block of memory can be lost,
having distinct arenas (i.e. distinct deallocation functions)
could be inconvenient.
A program whose operation depends on the assumption
that operator new
is equivalent to malloc
is effectively an environment where information about the method
used to allocate a block of memory is lost.
But in a well-written, portable C++ program, at the point where memory is deallocated, the type of the object being deleted – and therefore whether it is over-aligned – is known. This knowledge could, and probably should, be used to support layered implementations of over-aligned memory allocation.
This implies that, just as a new-expression for an over-aligned type should look for an alignment-aware allocation function, so should a delete-expression for a pointer to an over-aligned type look for an alignment-aware deallocation function. Presumably this would be done by selecting a deallocation function to which the alignment value can be passed, even though probably very few implementations will actually have any use for that value.
For exactly what classes should the allocation method change? Plausible answers include:
alignas
that actually specifies over-alignment.alignas
,
even if the alignment value is basic (i.e. small).The first answer seems to be right from a pragmatic perspective, but one consequence is that the behavior of a program might depend (in a new way) on an implementation-defined parameter. But if the only difference between alignment-aware and alignment-unaware allocation/deallocation functions is the actual allocation mechanism (i.e., in a well-designed program), this should not be a problem. It's rather like the implementation's license to elide certain copies, which implies that a copy constructor had really better just make a copy.
The below WD changes use the first answer, through use of “over-aligned”. The Intel implementation uses the first answer by default, but has a command-line option to select the second answer, for the sake of experimentation.
Also, it should be noted that the over-alignment threshold
used by the Intel implementation doesn't exactly match
the standard's definition of basic alignment.
The threshold used is actually the alignment
observed to be guaranteed
by the implementation of malloc
for the target environment.
(In all of the environments tested,
this turned out to be twice the size of a pointer.)
Assuming the existence of a variety of allocation functions, which one should be used for an over-aligned allocation? I believe the answer should be the first one from the following list that is known to exist:
It makes sense for a class-specific, alignment-unaware allocation function to be preferred over one that is global and alignment-aware, because there are many cases where a class-specific allocation function has enough information, even without an explicit parameter, to do the allocation with sufficient alignment. (Likely exceptions include a template class with a base or member of a type that is a template parameter, and a derived class that inherits its allocation function from a base class, and also adds a member or base of over-aligned type.)
If a global, alignment-aware allocation function is predeclared, then it will never be necessary to use a global, alignment-unaware allocation function for an over-aligned type; hence the brackets around item 4.
It should be noted that an alignment-aware allocation function
would be perfectly capable of performing an alignment
that would suffice for an alignment-unaware function.
In other words, from some perspective,
it would make sense to let operator new(size_t)
call operator new(size_t, align_val_t)
,
filling in the alignment value that it feels it needs to satisfy,
and move the allocation loop to the alignment-aware function.
But that would be pretty novel,
so I have chosen not to propose it.
At a joint EWG/CWG session in Jacksonville, it was agreed that the threshold above which to use the new allocation routines should be the maximum alignment provided by the existing allocation routines, and that this value should be made available to the programmer.
In this revision, I have chosen to use a predefined macro for this purpose.
Unquestionably, the compiler needs to know this value. It's possible to imagine some magical way it could be learned from some library header, but it needs to be known even if no header has been included. And given the existence of independent implementations of the standard C++ library, pragmatically sometimes it will be the programmer who needs to inform the compiler of this value at build time. To me, this set of requirements really suggests something an awful lot like a predefined macro.
There is no reason why this value couldn't also be exposed (with a “nicer” name) by something in a library header. But I'm afraid that in real-world implementations, the actual value from the library would be provided by some macro anyway.
So really, the only question is whether the predefined macro name appears in the standard, or is left for implementations to pick. And I think library implementers would be grateful if they didn't have to deal with different predefined macro names for different compilers.
LEWG will eventually consider whether an additional non-macro name for this is justified. The absence of any such proposal in this revision should be taken as a sign of caution (or laziness) on my part, and not of prejudice or opposition.
The following changes are relative to N4582 (post-Jacksonville).
There is one change of terminology worth noting.
Today, the phrase “placement new” is ambiguous.
In some contexts it means adding arguments
to a call to an allocation function,
with any types and unspecified purpose.
In other contexts,
it is used to refer specifically to cases
where there is a single additional argument
of type void *
,
in which case the allocation function
doesn't actually allocate anything.
I refer to the latter cases as “non-allocating”,
and refer to “allocating” cases
to distinguish them when necessary.
Change 3.7.4p1:
Objects can be created dynamically during program execution (1.9), using new-expressions (5.3.4), and destroyed using delete-expressions (5.3.5). A C++ implementation provides access to, and management of, dynamic storage via the global allocation functions
operator new
andoperator new[]
and the global deallocation functionsoperator delete
andoperator delete[]
. [ Note: The non-allocating forms described in [new.delete.placement] do not perform allocation or deallocation. —end note]
This is intended as a clarification of what seems already to be implied by [new.delete.placement]:
The provisions of (3.7.4) do not apply to these reserved placement forms of
operator new
andoperator delete
.
Change 3.7.4p2:
The library provides default definitions for the global allocation and deallocation functions. Some global allocation and deallocation functions are replaceable (18.6.1). A C++ program shall provide at most one definition of a replaceable allocation or deallocation function. Any such function definition replaces the default version provided in the library (17.6.4.6). The following allocation and deallocation functions (18.6) are implicitly declared in global scope in each translation unit of a program.
void* operator new(std::size_t); void* operator new(std::size_t, std::align_val_t); void* operator new[](std::size_t); void* operator new[](std::size_t, std::align_val_t); void operator delete(void*); void operator delete(void*, std::align_val_t); void operator delete[](void*); void operator delete[](void*, std::align_val_t); void operator delete(void*, std::size_t); void operator delete(void*, std::align_val_t, std::size_t); void operator delete[](void*, std::size_t); void operator delete[](void*, std::align_val_t, std::size_t);These implicit declarations introduce only the function names
operator new
,operator new[]
,operator delete
, andoperator delete[]
. [ Note: The implicit declarations do not introduce the namesstd
,std::size_t
,std::align_val_t
, or any other names that the library uses to declare these names. Thus, a new-expression, delete-expression or function call that refers to one of these functions without including the header<new>
is well-formed. However, referring tostd
orstd::size_t
orstd::align_val_t
is ill-formed unless the name has been declared by including the appropriate header. —end note] Allocation and/or deallocation functionscanmay also be declared and defined for any class (12.5).
The set of added functions is unchanged, but it was previously presented as a single cluster; the difference is editorial, but this ordering probably makes more sense. OTOH, a different order may make even more sense; see 18.6.1.
For deallocation, I originally chose to put the alignment before the size because the size argument is necessarily just a hint, whereas if separate arenas are used, at least the presence or absence of an alignment argument is significant for correctness. In a CWG teleconference, a mild consensus preference was expressed that when a deallocation function takes both an alignment and a size, the size should precede the alignment, mainly because that's the order when an allocation function takes both. LEWG needs to decide which order it prefers.
Change 3.7.4.1p2:
The allocation function attempts to allocate the requested amount of storage. If it is successful, it shall return the address of the start of a block of storage whose length in bytes shall be at least as large as the requested size. There are no constraints on the contents of the allocated storage on return from the allocation function. The order, contiguity, and initial value of storage allocated by successive calls to an allocation function are unspecified. The pointer returned shall be suitably aligned so that it can be converted to a pointer
ofto any suitable complete object type ([new.delete.single])with a fundamental alignment requirement (3.11)and then used to access the object or array in the storage allocated (until the storage is explicitly deallocated by a call to a corresponding deallocation function). Even if the size of the space requested is zero, the request can fail. If the request succeeds, the value returned shall be a non-null pointer value (4.10)p0
different from any previously returned valuep1
, unless that valuep1
was subsequently passed to anoperator delete
. Furthermore, for the library allocation functions in 18.6.2.1 and 18.6.2.2,p0
shall point to a block of storage disjoint from the storage for any other object accessible to the caller. The effect of indirecting through a pointer returned as a request for zero size is undefined.36
The text here is intentionally made less specific, with the details to be found elsewhere.
Change 3.7.4.2p2:
Each deallocation function shall return
void
and its first parameter shall bevoid*
. A deallocation functioncanmay have more than one parameter.The globalA usual deallocation function is a deallocation function that has:operator delete
with exactly one parameter is a usual (nonplacement) deallocation function. The globaloperator delete
with exactly two parameters, the second of which has typestd::size_t
, is a usual deallocation function. Similarly, the globaloperator delete[]
with exactly one parameter is a usual deallocation function. The globaloperator delete[]
with exactly two parameters, the second of which has typestd::size_t
, is a usual deallocation function.37 If a classT
has a member deallocation function namedoperator delete
with exactly one parameter, then that function is a usual deallocation function. If classT
does not declare such anoperator delete
but does declare a member deallocation function namedoperator delete
with exactly two parameters, the second of which has typestd::size_t
, then this function is a usual deallocation function. Similarly, if a classT
has a member deallocation function namedoperator delete[]
with exactly one parameter, then that function is a usual (non-placement) deallocation function. If classT
does not declare such anoperator delete[]
but does declare a member deallocation function namedoperator delete[]
with exactly two parameters, the second of which has typestd::size_t
, then this function is a usual deallocation function.
- exactly one parameter; or
- exactly two parameters, the type of the second being either
std::align_val_t
orstd::size_t
37); or- exactly three parameters, the type of the second being
std::align_val_t
and the type of the third beingstd::size_t
.Footnote: 37)
This deallocation functionThe globaloperator delete(void*, std::size_t)
precludes use of an allocation functionvoid operator new(std::size_t, std::size_t)
as a placement allocation function (C.3.2).A deallocation function
canmay be an instance of a function template. Neither the first parameter nor the return type shall depend on a template parameter. [ Note: That is, a deallocation function template shall have a first parameter of typevoid*
and a return type ofvoid
(as specified above). —end note ] A deallocation function template shall have two or more function parameters. A template instance is never a usual deallocation function, regardless of its signature.
Adding the new overloads to the set of "usual" deallocation functions in the same style as the previous formulation would have required outrageous verbosity. My new formulation is much more concise and (to my eyes) comprehensible, but there is a technical difference worth noting.
Previously, in class scope, if deallocation functions with and without a size parameter are both declared, the one with the size parameter was not "usual". My simpler formulation includes both overloads. But it is not difficult to tweak the selection algorithm (5.3.5p10) to produce the same result as previously.
Change 3.7.4.2p3:
If a deallocation function terminates by throwing an exception, the behavior is undefined. The value of the first argument supplied to a deallocation function may be a null pointer value; if so, and if the deallocation function is one supplied in the standard library, the call has no effect.
Otherwise, the behavior is undefined if the value supplied tooperator delete(void*)
in the standard library is not one of the values returned by a previous invocation of eitheroperator new(std::size_t)
oroperator new(std::size_t, const std::nothrow_t&)
in the standard library, and the behavior is undefined if the value supplied tooperator delete[](void*)
in the standard library is not one of the values returned by a previous invocation of eitheroperator new[](std::size_t)
oroperator new[](std::size_t, const std::nothrow_t&)
in the standard library.
These requirements apply only to the library implementations, and are already stated in 18.6.
Change 3.7.4.3p2:
A pointer value is a safely-derived pointer to a dynamic object only if it has an object pointer type and it is one of the following:
- the value returned by a call to the C++ standard library implementation of
::operator new(std::size_t)
or::operator new(std::size_t, std::align_val_t)
;39- ...
Change 3.9.2p3:
... [ Note: Pointers to over-aligned types (3.11) have no special representation, but their range of valid values is restricted by the extended alignment requirement.
This International Standard specifies only two ways of obtaining such a pointer: taking the address of a valid object with an over-aligned type, and using one of the runtime pointer alignment functions. An implementation may provide other means of obtaining a valid pointer value for an over-aligned type.—end note ]
Change 3.11p3:
An extended alignment is represented by an alignment greater than
alignof(std::max_align_t)
. It is implementation-defined whether any extended alignments are supported and the contexts in which they are supported (7.6.2). A type having an extended alignment requirement is an over-aligned type. A new-extended alignment is represented by an alignment greater than__STDCPP_DEFAULT_NEW_ALIGNMENT__
. [ Note: every over-aligned type is or contains a class type to which extended alignment applies (possibly through a non-static data member). —end note ]
Change 5.3.4p1:
The new-expression attempts to create an object of the type-id (8.1) or new-type-id to which it is applied. The type of that object is the allocated type. This type shall be a complete object type, but not an abstract class type or array thereof (1.8, 3.9, 10.4).
It is implementation-defined whether over-aligned types are supported (3.11).[ Note: ...
Change 5.3.4p8:
A new-expression may obtain storage for the object by calling an allocation function (3.7.4.1). If the new-expression terminates by throwing an exception, it may release storage by calling a deallocation function (3.7.4.2). If the allocated type is a non-array type, the allocation function's name is
operator new
and the deallocation function's name isoperator delete
. If the allocated type is an array type, the allocation function's name isoperator new[]
and the deallocation function's name isoperator delete[]
. [ Note: an implementation shall provide default definitions for the global allocation functions (3.7.4, 18.6.2.1, 18.6.2.2). A C++ program can provide alternative definitions of these functions (17.6.4.6) and/or class-specific versions (12.5). The set of allocation and deallocation functions that may be called by a new-expression may include functions that do not perform allocation or deallocation; for example, see [new.delete.placement]. —end note ]
As an editorial matter, note that the current WD inappropriately uses italics for the first occurrence of “allocation function” in this paragraph; it should be removed.
Change 5.3.4p13:
The new-placement syntax is used to supply additional arguments to an allocation function; such an expression is called a placement new-expression.
If used, overloadOverload resolution is performed on a function call created by assembling an argument list.consisting ofThe first argument is the amount of space requested(the first argument), and has typestd::size_t
. If the type of the allocated object has new-extended alignment, the next argument is the type's alignment, and has typestd::align_val_t
.and theIf the new-placement syntax is used, its expressionsin the new-placement part of the new-expression (are thesecond andsucceeding arguments).The first of these arguments has typeIf no matching function is found and the allocated object type has new-extended alignment, the alignment argument is removed from the argument list, and overload resolution is performed again.std::size_t
and the remaining arguments have the corresponding types of the expressions in the new-placement; such an expression is called a placement new-expression.
Change 5.3.4p14:
[ Example:
new T
results in a call of eitheroperator new(sizeof(T))
oroperator new(sizeof(T), std::align_val_t(alignof(T)))
,new(2,f) T
results in a call of eitheroperator new(sizeof(T),2,f)
oroperator new(sizeof(T), std::align_val_t(alignof(T)),2,f)
,new T[5]
results in a call of eitheroperator new[](sizeof(T)*5+x)
oroperator new[](sizeof(T)*5+x, std::align_val_t(alignof(T)))
, andnew(2,f) T[5]
results in a call of eitheroperator new[](sizeof(T)*5+
oryx,2,f)operator new[](sizeof(T)*5+x, std::align_val_t(alignof(T)),2,f)
.Here, each instance of
x
and y areis a non-negative unspecifiedvaluesvalue representing array allocation overhead; ... — end example ]
Change 5.3.4p15:
[ Note: unless an allocation function has a non-throwing exception specification (15.4), it indicates failure to allocate storage by throwing a
std::bad_alloc
exception (3.7.4.1, Clause 15, 18.6.3.1); it returns a non-null pointer otherwise. If the allocation function has a non-throwing exception specification, it returns null to indicate failure to allocate storage and a non-null pointer otherwise. —end note ] If the allocation function is areserved placement allocation functionnon-allocating form (18.6.2.3) that returns null, the behavior is undefined. Otherwise, if the allocation function returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.
Change 5.3.4p22:
A declaration of a placement deallocation function matches the declaration of a placement allocation function if it has the same number of parameters and, after parameter transformations (8.3.5), all parameter types except the first are identical. If the lookup finds a single matching deallocation function, that function will be called; otherwise, no deallocation function will be called. If the lookup finds
the two-parameter form ofa usual deallocation function with a size parameter (3.7.4.2) and that function, considered as a placement deallocation function, would have been selected as a match for the allocation function, the program is ill-formed. For a non-placement allocation function, the normal deallocation function lookup is used to find the matching deallocation function (5.3.5) [ Example:
Change 5.3.5p5:
If the object being deleted has incomplete class type at the point of deletion, and the complete class has new-extended alignment, has a non-trivial destructor, or has a deallocation function, the behavior is undefined.
Change 5.3.5p10:
If deallocation function lookup finds
both amore than one usual deallocation functionwith only a pointer parameter and a usual deallocation function with both a pointer parameter and a size parameter, the function to be called is selected as follows:
- If the type has new-extended alignment, a function with an alignment parameter is preferred; otherwise a function with no alignment parameter is preferred. If exactly one preferred function is found, that function is selected and the selection process terminates. If more than one preferred function is found, all non-preferred functions are eliminated from further consideration.
- If the deallocation functions have class scope, the one without a size parameter is selected.
- If the type is complete and if, for the second alternative (delete array) only, the operand is a pointer to a class type with a non-trivial destructor or a (possibly multi-dimensional) array thereof, the function with
two parametersa size parameter is selected.- Otherwise, it is unspecified
which of the two deallocation functionswhether a deallocation function with a size parameter is selected.
Change 5.3.5p11:
When a delete-expression is executed, the selected deallocation function shall be called with the address of the block of storage to be reclaimed as its first argument. If a deallocation function with an alignment parameter is used, the alignment of the type of the object to be deleted is passed as the corresponding argument.
and (if the two-parameterIf a deallocation function with a size parameter is used), the size of the block is passed asits secondthe corresponding argument.82
Add a new predefined macro to the list in 16.8p1:
__STDCPP_DEFAULT_NEW_ALIGNMENT__
- An integer literal whose value is the threshold for an alignment value above which
operator new(std::size_t)
is not guaranteed to allocate appropriately-aligned memory.
Change 17.6.4.6p2:
A C++ program may provide the definition for any of the following dynamic memory allocation function signatures declared in header
<new>
(3.7.4, 18.6):
operator new(std::size_t)
operator new(std::size_t, const std::nothrow_t&)
operator new(std::size_t, std::align_val_t)
operator new(std::size_t, std::align_val_t, const std::nothrow_t&)
operator new[](std::size_t)
operator new[](std::size_t, const std::nothrow_t&)
operator new[](std::size_t, std::align_val_t)
operator new[](std::size_t, std::align_val_t, const std::nothrow_t&)
operator delete(void*)
operator delete(void*, const std::nothrow_t&)
operator delete(void*, std::size_t)
operator delete(void*, std::align_val_t)
operator delete(void*, std::align_val_t, const std::nothrow_t&)
operator delete(void*, std::align_val_t, std::size_t)
operator delete[](void*)
operator delete[](void*, const std::nothrow_t&)
operator delete[](void*, std::size_t)
operator delete[](void*, std::align_val_t)
operator delete[](void*, std::align_val_t, const std::nothrow_t&)
operator delete[](void*, std::align_val_t, std::size_t)
Change 18.6.1:
namespace std { class bad_alloc; class bad_array_new_length; enum class align_val_t: size_t {}; struct nothrow_t {}; extern const nothrow_t nothrow; typedef void (*new_handler)(); new_handler get_new_handler() noexcept; new_handler set_new_handler(new_handler new_p) noexcept; // 18.6.4, hardware interference size static constexpr size_t hardware_destructive_interference_size = implementation-defined ; static constexpr size_t hardware_constructive_interference_size = implementation-defined ; };
void* operator new(std::size_t size); void* operator new(std::size_t size, std::align_val_t alignment); void* operator new(std::size_t size, const std::nothrow_t&) noexcept; void* operator new(std::size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept; void operator delete(void* ptr) noexcept; void operator delete(void* ptr, std::size_t size) noexcept; void operator delete(void* ptr, std::align_val_t alignment) noexcept; void operator delete(void* ptr, std::align_val_t alignment, std::size_t size) noexcept; void operator delete(void* ptr, const std::nothrow_t&) noexcept; void operator delete(void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept; void* operator new[](std::size_t size); void* operator new[](std::size_t size, std::align_val_t alignment); void* operator new[](std::size_t size, const std::nothrow_t&) noexcept; void* operator new[](std::size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept; void operator delete[](void* ptr) noexcept; void operator delete[](void* ptr, std::size_t size) noexcept; void operator delete[](void* ptr, std::align_val_t alignment) noexcept; void operator delete[](void* ptr, std::align_val_t alignment, std::size_t size) noexcept; void operator delete[](void* ptr, const std::nothrow_t&) noexcept; void operator delete[](void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept;
void* operator new (std::size_t size, void* ptr) noexcept; void* operator new[](std::size_t size, void* ptr) noexcept; void operator delete (void* ptr, void*) noexcept; void operator delete[](void* ptr, void*) noexcept;
I have carefully added the new overloads
in the synopsis to appear in the same order
that they are described below.
Unfortunately, that required moving the declarations
of nothrow
deallocation functions;
that's an editorial change that I didn't explicitly mark above.
I also noticed that the lists in 3.7.4 and 17.6.4.6
are ordered differently than this one,
and for that matter that the list in 17.6.4.6 has bullets;
editorially, it may be desirable for these lists
to be more consistent with one another.
Change 18.6.2p1:
Except where otherwise specified, the provisions of (3.7.4) apply to the library versions of
operator new
andoperator delete
. If the value of an alignment argument passed to any of these functions is not a valid alignment value, or for an allocation function if the size argument is not an integral multiple of the alignment argument, the behavior is undefined.
The provision about the size being a multiple of the alignment was added in response to a comment made at the Jacksonville meeting. However, Martin Sebor, who has also been working on aligned allocation for C, points out that it unnecessarily complicates the following technique:
typedef struct S { _Alignas(64) int a[16]; char x[]; } S; S *s = aligned_alloc(_Alignof(S), sizeof(S) + 20);
It basically assumes that the only way a block of allocated memory can be used is by filling it with an array (of elements all having the same type). But raw memory is (or at least ought to be) more flexible than that. So I think it would make sense to remove that provision, or change it such that the size must be at least as great as the alignment.
Change section 18.6.2.1 as follows. (Some technically irrelevant editorial improvements are included; in CWG, these would be called “en passant” or “drive-by” changes. Also, there are some inappropriate uses of italics in these two sections which should be fixed, for phrases including “allocation function”, “deallocation function”, and “ptr”.)
18.6.2.1 Single-object forms
void* operator new(std::size_t size);
void* operator new(std::size_t size, std::align_val_t alignment);Effects: The allocation
functionfunctions ([basic.stc.dynamic.allocation]) called by a new-expression ([expr.new]) to allocatesize
bytes of storage. The second form is called for a type with new-extended alignment, and allocates storage with the specified alignment. The first form is called otherwise, and allocates storage suitably aligned to represent any object of that size or smaller, provided the object's type does not have new-extended alignment.Replaceable: a C++ program may define
a function with this function signature that displacesfunctions with either of these function signatures, and thereby displace the defaultversionversions defined by the C++ standard library.Required behavior: Return a non-null pointer to suitably aligned storage ([basic.stc.dynamic]), or else throw a
bad_alloc
exception. This requirement is binding onaany replacementversionversions ofthis functionthese functions.Default behavior:
- 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
functionfunctionsmalloc
oraligned_alloc
is unspecified.- Returns a pointer to the allocated storage if the attempt is successful. Otherwise, if the current
new_handler
([get.new.handler]) is a null pointer value, throwsbad_alloc
.- Otherwise, the function calls the current
new_handler
function ([new.handler]). 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.
void* operator new(std::size_t size, const std::nothrow_t&) noexcept;
void* operator new(std::size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept;Effects: Same as above, except that
it isthese are called by a placement version of a new-expression when a C++ program prefers a null pointer result as an error indication, instead of abad_alloc
exception.Replaceable: a C++ program may define a function with this function signature that displaces the default version defined by the C++ standard library. This should be changed similarly to paragraph 2.
Required behavior: Return a non-null pointer to suitably aligned storage ([basic.stc.dynamic]), or else return a null pointer.
ThisEach of these nothrowversionversions ofoperator new
returns a pointer obtained as if acquired from the (possibly replaced)ordinary versioncorresponding non-placement function. This requirement is binding on a replacement version of this function. The last sentence should be changed similarly to paragraph 3.Default behavior: Calls
operator new(size)
, oroperator new(size, alignment)
, respectively. If the call returns normally, returns the result of that call. Otherwise, returns a null pointer.[ Example:
T* p1 = new T; // throws bad_alloc if it fails T* p2 = new(nothrow) T; // returns nullptr if it fails—end example ]
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, std::size_t size) noexcept;
void operator delete(void* ptr, std::align_val_t alignment) noexcept;
void operator delete(void* ptr, std::align_val_t alignment, std::size_t size) noexcept;Effects: The deallocation
functionfunctions ([basic.stc.dynamic.deallocation]) called by a delete-expression to render the value ofptr
invalid.Replaceable: a C++ program may define
a function with signaturefunctions with any of these signatures, and thereby displace the default version(s) defined by the C++ standard library. Ifvoid operator delete(void* ptr) noexcept
that displacesthisa function(without asize
parameter)is defined, the program should also definethe corresponding function with avoid operator delete(void* ptr, td::size_t size) noexcept
size
parameter. Ifthisa function with asize
parameter is defined, the program shall also define the corresponding version without thesize
parameter. [ Note: The default behavior below may change in the future, which will require replacing both deallocation functions when replacing the allocation function. —end note ]Requires:
ptr
shall be a null pointer or its value shallbe a value returnedpoint to a block of memory allocated by an earlier call tothea (possibly replaced)operator new(std::size_t)
oroperator new(std::size_t,
which has not been invalidated by an intervening call toconst std::nothrow_t&std::align_val_t)operator delete
(void*)or.operator delete(void*, std::size_t)
Requires: If an implementation has strict pointer safety ([basic.stc.dynamic.safety]) then
ptr
shall be a safely-derived pointer.Requires: If the
alignment
parameter is not present,ptr
shall have been returned by an allocation function not taking an alignment argument. If present, thealignment
argument shall equal the alignment argument passed to the allocation function that returnedptr
. If present, theargument shall equal the size argument passed to the allocation function that returned
std::size_tsizeptr
.Required behavior:
CallsA call to anoperator delete
taking a size may be changed to(void* ptr, std::size_t size)callsa call to the correspondingoperator delete
not taking a size, without affecting memory allocation. [ Note: A conforming implementation is for(void* ptr)operator delete(void* ptr, std::size_t size)
to simply calloperator delete(ptr)
. —end note ]Default behavior: the function
operator delete(void* ptr, std::size_t size)
callsoperator delete(ptr)
. The functionoperator delete(void* ptr, std::align_val_t alignment, std::size_t)
callsoperator delete(ptr, alignment)
. [ Note: See the note in the above Replaceable paragraph. —end note ]Default behavior: If
ptr
is null, does nothing. Otherwise, reclaims the storage allocated by the earlier call tooperator new
.The previous revision suggested changing “Default behavior” to “Required behavior”. I am walking back from that suggestion, not because I think it was wrong, but simply because it has nothing to do with alignment. If that should be done, let it be done as a separate effort.
Remarks: It is unspecified under what conditions part or all of such reclaimed storage will be allocated by subsequent calls to
operator new
or any ofaligned_alloc
,calloc
,malloc
, orrealloc
, declared in<cstdlib>
.
void operator delete(void* ptr, const std::nothrow_t&) noexcept;
void operator delete(void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept;Effects: The deallocation
functionfunctions ([basic.stc.dynamic.deallocation]) called by the implementation to render the value ofptr
invalid when the constructor invoked from a nothrow placement version of the new-expression throws an exception.Replaceable: a C++ program may define
a functionfunctions withsignatureany of these signatures, and thereby displace the default version(s) defined by the C++ standard library.void operator delete(void* ptr, const std::nothrow_t&) noexcept
that displacesRequires: If an implementation has strict pointer safety ([basic.stc.dynamic.safety]) then
ptr
shall be a safely-derived pointer.Default behavior: The function
operator delete(void* ptr, const std::nothrow_t&)
callsoperator delete(ptr)
. The functionoperator delete(void* ptr, std::align_val_t alignment, const std::nothrow_t&)
callsoperator delete(ptr, alignment)
.
Change section 18.6.2.2:
18.6.2.2 Array forms
void* operator new[](std::size_t size);
void* operator new[](std::size_t size, std::align_val_t alignment);Effects: The allocation
functionfunctions ([basic.stc.dynamic.allocation]) called by the array form of a new-expression ([expr.new]) to allocatesize
bytes of storage. The second form is called for a type with new-extended alignment, and allocates storage with the specifed alignment. The first form is called otherwise, and allocates storage suitably aligned to represent any array object of that size or smaller, provided the array's element type does not have new-extended alignment.224Footnote 224: It is not the direct responsibility of
operator new[](std::size_t)
oroperator delete[](void*)
to note the repetition count or element size of the array. Those operations are performed elsewhere in the arraynew
anddelete
expressions. The arraynew
expression, may, however, increase thesize
argument tooperator new[](std::size_t)
to obtain space to store supplemental information.Replaceable: a C++ program
canmay define a function with this function signature that displaces the default version defined by the C++ standard library. This should be changed similarly to paragraph 2 above.Required behavior: Same as for
the corresponding single-object forms. This requirement is binding on a replacement version of this function. The last sentence should be changed similarly to paragraph 3 above.operator new(std::size_t)
Default behavior: Returns
operator new(size)
, oroperator new(size, alignment)
, respectively.
void* operator new[](std::size_t size, const std::nothrow_t&) noexcept;
void* operator new[](std::size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept;Effects: Same as above, except that
it isthese are called by a placement version of a new-expression when a C++ program prefers a null pointer result as an error indication, instead of abad_alloc
exception.Replaceable: a C++ program
canmay define a function with this function signature that displaces the default version defined by the C++ standard library. This should be changed similarly to paragraph 2.Required behavior: Return a non-null pointer to suitably aligned storage ([basic.stc.dynamic]), or else return a null pointer.
ThisEach of these nothrowversionversions ofoperator new[]
returns a pointer obtained as if acquired from the (possibly replaced)corresponding non-placement function. This requirement is binding on a replacement version of this function. The last sentence should be changed similarly to paragraph 3.operator new[](std::size_t)
functionDefault behavior: Calls
operator new[](size)
, oroperator new[](size, alignment)
, respectively. If the call returns normally, returns the result of that call. Otherwise, returns a null pointer.
void operator delete[](void* ptr) noexcept;
void operator delete[](void* ptr, std::size_t size) noexcept;
void operator delete[](void* ptr, std::align_val_t alignment) noexcept;
void operator delete[](void* ptr, std::align_val_t alignment, std::size_t size) noexcept;Effects: The deallocation
functionfunctions ([basic.stc.dynamic.deallocation]) called by the array form of a delete-expression to render the value ofptr
invalid.Replaceable: a C++ program
canmay definea function with signaturefunctions with any of these signatures, and thereby displace the default version(s) defined by the C++ standard library. Ifvoid operator delete[](void* ptr) noexcept
that displacesthisa function(without asize
parameter)is defined, the program should also definethe corresponding function with avoid operator delete[](void* ptr, std::size_t size) noexcept
size
parameter. Ifthisa function with asize
parameter is defined, the program shall also define the corresponding version without thesize
parameter. [ Note: The default behavior below may change in the future, which will require replacing both deallocation functions when replacing the allocation function. —end note ]Requires:
ptr
shall be a null pointer or its value shallbe the value returnedpoint to a block of memory allocated by an earlier call to a (possibly replaced)operator new[](std::size_t)
oroperator new[](std::size_t,
which has not been invalidated by an intervening call toconst std::nothrow_t&std::align_val_t)operator delete[]
(void*)or.operator delete[](void*, std::size_t)
Requires: If the
alignment
parameter is not present,ptr
shall have been returned by an allocation function not taking an alignment argument. If present, thealignment
argument shall equal the alignment argument passed to the allocation function that returnedptr
. If present, theargument
std::size_tsizemustshall equal the size argument passed to the allocation function that returnedptr
.Required behavior:
CallsA call tooperator delete[]
taking a size may be changed to(void* ptr, std::size_t size)callsa call tooperator delete[]
not taking a size, without affecting memory allocation. [ Note: A conforming implementation is for(void* ptr)operator delete[](void* ptr, std::size_t size)
to simply calloperator delete[](void* ptr)
. —end note ]Requires: If an implementation has strict pointer safety ([basic.stc.dynamic.safety]) then
ptr
shall be a safely-derived pointer.Default behavior:
The functions that have aoperator delete[](void* ptr, std::size_t size)
callsoperator delete[](ptr)
, andoperator delete[](void* ptr)
callsoperator delete(ptr)
.size
parameter forward their other parameters to the corresponding function without asize
parameter. The functions that do not have asize
parameter forward their parameters to the correspondingoperator delete
(single-object) function.This is the one place in the standard where the default behavior needs to be specified for four replaceable functions. My senses of esthetics and propriety rebelled at the prospect of enumerating them all, especially in a single flowing paragraph.
void operator delete[](void* ptr, const std::nothrow_t&) noexcept;
void operator delete[](void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept;Effects: The deallocation
functionfunctions ([basic.stc.dynamic.deallocation]) called by the implementation to render the value ofptr
invalid when the constructor invoked from a nothrow placement version of the array new-expression throws an exception.Replaceable: a C++ program may define
a functionfunctions withsignatureany of these signatures, and thereby displace the default version(s) defined by the C++ standard library.void operator delete[](void* ptr, const std::nothrow_t&) noexcept
that displacesRequires: If an implementation has strict pointer safety ([basic.stc.dynamic.safety]) then
ptr
shall be a safely-derived pointer.Default behavior: The function
operator delete[](void* ptr, const std::nothrow_t&)
callsoperator delete[](ptr)
. The functionoperator delete[](void* ptr, std::align_val_t alignment, const std::nothrow_t&)
callsoperator delete[](ptr, alignment)
.
Change the title of section 18.6.2.3:
18.6.2.3
PlacementNon-allocating forms [new.delete.placement]
Change 20.9.9.1p5:
Returns: A pointer to the initial element of an array of storage of size
n * sizeof(T)
, aligned appropriately for objects of typeT
.It is implementation-defined whether over-aligned types are supported (3.11).
Change 20.9.9.1p6:
Remark: the storage is obtained by calling
::operator new
(18.6.2), but it is unspecified when or how often this function is called. The use of(std::size_t)hint
is unspecified, but intended as an aid to locality if an implementation so desires.
Change 20.9.9.1p10:
Remarks: Uses
::operator delete
(18.6.2), but it is unspecified when this function is called.(void*, std::size_t)
Change 20.9.11p1:
Effects: Obtains a pointer to uninitialized, contiguous storage for N adjacent objects of type
T
, for some non-negative number N.It is implementation-defined whether over-aligned types are supported (3.11).