Audience: EWG, CWG, LWG
S. Davis Herring <herring@lanl.gov>
Los Alamos National Laboratory
January 10, 2025
The word “address” is used with several different meanings in the
standard, each given merely implicit definitions. [intro.memory]/1 tells
us “Every byte has a unique address.”, and pointers are said to
“represent the address” of bytes of memory ([basic.compound]/3);
[expr.delete]/5 and [class.mem.general]/22 (and some places in the
library) refer to an order on such addresses, and [basic.align]/1 even
talks about a number of bytes between addresses. There is also some sort
of order invoked for bytes in [basic.compound]/3 and [intro.object]/8.
However, no further use of those order and/or arithmetic structures is
made: in particular, the integers resulting from
reinterpret_cast
are not described as addresses, except in
the note in [expr.reinterpret.cast]/4 about the conversion being
“unsurprising”.
Furthermore, the word is sometimes used to just refer to any use of
the unary &
operator (e.g.,
[basic.lookup.argdep]/3 and [dcl.ptr]/4) or to any pointer
(e.g., [expr.delete]/11, [atomics.ref.pointer]/6, and
[util.smartptr.atomic.shared]/2). Finally, sometimes it refers to the
identity of an entity (e.g., [dcl.inline]/6,
[temp.constr.order]/2.2, or [temp.explicit]/12).
There is also one significant defect where the word should be used
but is not: [defns.order.ptr] refers to a total order over pointer
values that std::less
and company cannot possibly produce
with real implementations. In particular,
std::less<void*>
cannot distinguish the pointer
values of a pointer to an array and a pointer to its first element
(which are not even pointer-interconvertible). Neither can
std::less<int*>
distinguish a pointer past one
int
(&x+1
) with a pointer to another
int
(&y
) that happens to be stored
immediately after it.
This paper clarifies that addresses are totally ordered opaque labels
for bytes of memory, rewriting [expr.eq] to use that order (as does
[comparisons]) and [expr.rel] to use the appropriate subset of that
order. ([expr.rel] results must continue to be otherwise unspecified
because it is desirable that the result of <
be able to
differ between its usage directly and via std::less
, whose
total order must be unavailable during constant evaluation even if
<
provides it at runtime.) It changes the usages with
other meanings, continuing in the direction
already established there. It also clarifies the relationships between
addresses, bytes, and objects.
Associate an address with every pointer value, even pointers to
functions and null pointers. Establish a total order on these addresses,
retaining the possibility that objects are not contiguous in it to
support either interleaving or concatenating segments in the
std::less
order. Define equality operators on pointers and
all [comparisons] function objects in terms of this order. Introduce the
term “ordered” for pointers where <
is available during
constant evaluation.
Do not alter the general implementation-defined support for any
operation on pointers not valid in the context of that operation; note
that such an alteration has been separately
proposed. Nor alter the description of void*
values
used with operator new
and company strictly in terms of
their addresses (and suitable created objects).
Additionally, correct the separate defective claim in
[comparisons.general] that std::less<void>
produces
an ordered result for distinct arguments even though, if they have
different types, the conversion to their composite pointer
type ([expr.rel]/3) can cause them to become equal.
Use strictly “take the address” for the operation of
&
, but formally define “address of an overload set”
(whose evaluation is deferred until after overload resolution). Avoid
using “address of E” when “pointer to E” or
“E” is correct.
The only change of semantics (beyond the alignment of the library facilities with real implementations) is that comparisons involving one-past pointers are no longer unspecified (but still cannot be used in constant expressions). These changes to [expr.eq] merge cleanly with those from P2434R2.
Relative to N5001.
Remove subclause. Forward its stable name to [intro.memory].
Change paragraph 1:
[…] The memory available to a C++ program consists
of one or morea sequencesofcontiguousbytes. Every byte has a uniqueaddressaddress, which is a label drawn from a larger set equipped with an implementation-defined total order.[Note: The order is independent of the sequence order of the bytes except as specified ([intro.object], [basic.stc.dynamic.allocation]). — end note]
Change paragraph 8:
[…] Unless it is a bit-field ([class.bit]), an object with nonzero size shall occupy one or more bytes of storage, including every byte that is occupied in full or in part by any of its subobjects. An object of trivially copyable or standard-layout type ([basic.types.general]) shall occupy contiguous bytes of storage. The order of the addresses of the bytes occupied by any object is consistent with that of the bytes ([intro.memory]).
[Note: The address of a byte occupied by one complete object can intervene between those of the bytes occupied by another complete object. — end note]
Change paragraph 1:
Object types have alignment requirements ([basic.fundamental,basic.compound]) which place restrictions on the addresses at which an object of that type may be allocated. An alignment is an implementation-defined integer value representing the
numberspacing of successive bytesbetween successiveat whose addressesat whicha given object can be allocated. […]
Change paragraph 7:
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated[…] or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released,
anya pointerthat represents the address of the storage location whereto the objectwill be or was locatedmay be used but only in limited ways. […]
Change paragraph 9:
[…]
[Note: If these conditions are not met, a pointer to the new object can be obtained from a pointer
that represents thewith its addressof its storageby callingstd::launder
([ptr.launder]). — end note]
Insert before paragraph 2:
The address of a block of storage is the address of the first byte in it. The addresses of the bytes in any block of storage are in increasing order ([intro.memory]).
Change paragraph 2:
An allocation function attempts to allocate the requested amount of storage. If it is successful, it returns the address of
the start ofa block of storage whose length in bytes is at least as large as the requested size. The order, contiguity, and initial value of storage allocated by successive calls to an allocation function are unspecified. […] Furthermore, for the library allocation functions in [new.delete.single] and [new.delete.array], the address ofp0
represents the addressis that of a block of storage disjoint from the storage for any other object accessible to the caller.
Change paragraph 3:
For an allocation function other than a reserved placement allocation function ([new.delete.placement]), the pointer returned on a successful call shall
represent thehave as its address that of storage that is aligned as follows:
Change and split paragraph 3:
[…]what can be done with them ([basic.types.general]).
Every value of pointer type holds an address ([intro.memory]) that is not in general unique. A value of a pointer type that is a pointer to or past the end of an object
represents the addressholds the address ofthe first byte in memory ([intro.memory]) occupied bythe object[Footnote: For an object that is not within its lifetime, this is the address of the first byte in memory that it will occupy or used to occupy. — end footnote] or of the first byte in memory after the end of the storage occupied by the object, respectively.[Note: A pointer past the end of an object ([expr.add]) is not considered to point to an unrelated object of the object’s type, even if the unrelated object is located at that address. — end note]
Every pointer value that points to a function holds a unique address. All null pointer values hold a single unique address. Every invalid pointer value holds a unique address. These addresses are not associated with any byte.
For purposes of pointer arithmetic ([expr.add])
and comparison ([expr.rel,expr.eq]), a pointer past the end of the last element of an arrayx
of n elements […]
Change paragraph 13:
[…] If the original pointer value
represents thepoints to or past an object and holds an addressthat does not satisfy the alignment requirement ofA
of a byte in memory andA
T
, then the resulting pointer value ([basic.compound]) is unspecified. […]
Change paragraph 3:
The operand of the unary
&
operator shall be an lvalue of some typeT
. The unary-expression is said to take the address of its operand.[…]
Change paragraph 6:
An address of an overload set is a unary-expression that takes the address of an id-expression whose terminal name refers to an overload set. [Note: The address of an overload set
([over])can be taken only in a context that uniquely determines which function is referred to (see[over.match.call.general], [over.over]). Since the context can affect whether the operand is a static or non-static member function, the context can also affect whether the expression has type “pointer to function” or “pointer to member function”. — end note]
Change paragraph 17:
[…] For arrays of
char
,unsigned char
, andstd::byte
, the difference between the result of the new-expression and theaddressvalue returned by the allocation function (converted to the same type) shall be an integral multiple of the strictest fundamental alignment requirement ([basic.align]) of any object type whose size is no greater than the size of the array being created.
Change paragraph 10:
[…] When a delete-expression is executed, the selected deallocation function shall be called with
the address ofa pointer to the deleted object in a single-object delete expression, orthe address of the deleted objectsuch a pointer suitably adjusted for the array allocation overhead ([expr.new]) in an array delete expression, as its first argument.[Note: […] — end note]
Change paragraph 6:
[…]
In this case,
p <=> q
is of typestd::strong_ordering
and the result is defined by the following rules:
- If
two pointer operandsp
andq
compare equal ([expr.eq]),p <=> q
yieldsstd::strong_ordering::equal
;- otherwise, if
p
andq
compare unequalare ordered ([expr.rel]),p <=> q
yieldsstd::strong_ordering::less
ifq
compares greater thanp
p < q
istrue
andstd::strong_ordering::greater
ifotherwise;p
compares greater thanq
([expr.rel])- otherwise, the result is unspecified.
Replace paragraphs 4–6:
The result of comparing unequal pointers to objects[…] is defined in terms of a partial order consistent with the following rules:
[…]
If two operands
p
andq
compare equal ([expr.eq]), […]
If both operands (after conversions) are of arithmetic or enumeration type, […]
p > q
yieldsq < p
,p <= q
yields!(p > q)
, andp >= q
yields!(p < q)
.
If their operands
p
andq
are pointers:
p
andq
are ordered if an object O exists such thatp
andq
each point to or past an object that is or is nested within O. In that case,p < q
yieldstrue
if the address held byp
precedes that held byq
andfalse
otherwise.- Otherwise, if
p
andq
hold the same address,p < q
yieldsfalse
.- Otherwise, the result of
p < q
is unspecified.
Otherwise,
p < q
yieldstrue
if the (converted) value ofp
is less than that ofq
andfalse
otherwise.
Change paragraph 3:
If at least one of the converted operands is a pointer, pointer conversions ([conv.ptr]), function pointer conversions ([conv.fctptr]), and qualification conversions ([conv.qual]) are performed on both operands to bring them to their composite pointer type ([expr.type]). If the pointer values hold the same address, they compare equal; otherwise
Comparing pointers is defined as follows:
If one pointer represents the address of a complete object, and another pointer represents the address one past the last element of a different complete object,[…] the result of the comparison is unspecified.Otherwise, if the pointers are both null, both point to the same function, or both represent the same address ([basic.compound]), they compare equal.Otherwise,the pointersthey compare unequal.
Change and split bullet (10.25):
a three-way comparison ([expr.spaceship])
,or relational ([expr.rel]), oroperator whose (pointer) operands are unordered;
an equality ([expr.eq]) operator
where the result is unspecifiedwhose (pointer) operands hold the address of one complete object and the address held by a pointer past another complete object;
Change paragraph 6:
[Note: An inline function or variable with external or module linkage can be defined in multiple translation units ([basic.def.odr]), but is
onea single entitywith one address. A type orstatic
variable defined in the body of such a function is therefore a single entity. — end note]
Change paragraph 6:
An object of type “array of
N
U
” consists of acontiguously allocatednon-emptysetsequence ofN
subobjects of typeU
, known as the elements of the array, and numbered0
toN-1
. The first element has the same address as the array; each other element occupies the bytes immediately following the previous.[Note: Because the array is an object, the addresses of the elements are in the same order as the elements. — end note]
Change paragraph 12:
[…] The selected deallocation function shall be called with the
address of the block of storage to be reclaimedvalue returned by the allocation function as its first argument. If a deallocation function with a parameter of typestd::size_t
is used, the size of the block is passed as the corresponding argument.
Change paragraph 22:
[Note:AmongNnon-variant non-static data members of non-zero size ([intro.object])are allocated so that, later membershave higher addressesoccupy later bytes within a class object([expr.rel]).[Note: Implementation alignment requirements can cause two adjacent members not to be allocated immediately after each other; so can requirements for space for managing virtual functions ([class.virtual]) and virtual base classes ([class.mi]). — end note]
Change the example in paragraph 8:
&i; // error: taking the address of non-reference template-parameter
Change bullet (2.2):
the
address of anon-template function selected from an overload set ([over.over]),
Change paragraph 1:
If multiple function templates share a name, the use of that name can be ambiguous because template argument deduction ([temp.deduct]) may identify a specialization for more than one function template. Partial ordering of overloaded function template declarations is used in the following contexts to select the function template to which a function template specialization refers:
- during overload resolution for (a call to) a function template specialization ([over.match.best], [over.over]);
when the address of a function template specialization is taken;- when a placement operator delete that is a function template specialization is selected to match a placement operator new ([basic.stc.dynamic.deallocation,expr.new]);
- when a friend function declaration ([temp.friend]), an explicit instantiation ([temp.explicit]) or an explicit specialization ([temp.expl.spec]) refers to a function template specialization.
Change the section title.
Change paragraph 1:
Template arguments can be deduced from the type specified when
taking the address ofresolving an overload set ([over.over]). […]
Change paragraph 10:
[…] If
P
andA
are function types that originated from deduction whentaking the address ofnaming a function template ([temp.deduct.funcaddr]) or when deducing template arguments from a function declaration ([temp.deduct.decl]) andP
i andA
i_ are parameters of the top-level parameter-type-list ofP
andA
, respectively, […]
Change paragraph 1:
A null-terminated byte string, or NTBS, is a character sequence whose
highest-addressedlast element with defined content has the value zero (the terminating null character); no other element in the sequence has the value zero.[…]
Change bullet (1.2):
If a function argument is described as being an array, the pointer actually passed to the function shall have a value such that all
addresspointer computations and accesses to objects (that would be valid if the pointer did point to the first element of such an array) are in fact valid.
Change paragraphs 10 and 18 identically:
Preconditions:
ptr
is a null pointer orits value representsit holds the address of a block of memory allocated by an earlier call to a (possibly replaced)operator new(std::size_t)
oroperator new(std::size_t, std::align_val_t)
which has not been invalidated by an intervening call tooperator delete
.
Change paragraphs 9 and 15 identically:
Preconditions:
ptr
is a null pointer orits value representsit holds the address of a block of memory allocated by an earlier call to a (possibly replaced)operator new[](std::size_t)
oroperator new[](std::size_t, std::align_val_t)
which has not been invalidated by an intervening call tooperator delete[]
.
Change paragraph 2:
Preconditions:
An object X that is within its lifetime ([basic.life]) and whose type is similar ([conv.qual]) top
represents the address A of a byte in memory.T
is located athas the addressAheld byp
. All bytes of storage that would be reachable through ([basic.compound]) the result are reachable throughp
.
Change paragraph 1:
An object of type
coroutine_handle<T>
is called a coroutine handle and can be used to refer to a suspended or executing coroutine. Acoroutine_handle
object whose memberaddress()
returns a null pointer value does not refer to any coroutine. Twocoroutine_handle
objects refer to the same coroutine if and only if their memberaddress()
returnsthe samenon-null values that compare equal.
Change paragraph 2:
Returns:
compare_three_way()(this, &rhs)
.
[Note:
compare_three_way
([comparisons.three.way]) provides a
total ordering for pointersaddresses. — end
note]
Change bullet (1.2):
ptr
representsholds the address of contiguous storage of at leastspace
bytes
Change paragraph 2:
Effects: If it is possible to fit
size
bytes of storage aligned byalignment
into the buffer pointed to byptr
with lengthspace
, the functionupdatesassigns toptr
to representa pointer that holds the first possible address of such storage and decreasesspace
by the number of bytes used for alignment. Otherwise, the function does nothing.
Change paragraph 1:
Returns:
The actual address ofA pointer to the object or function referenced byr
, even in the presence of an overloadedoperator&
.
Change paragraph 5:
Returns: A
shared_ptr
instance that stores and ownsthe address ofa pointer to the newly constructed object.
Change paragraph 10:
Remarks: The
shared_ptr
constructors called by these functions enableshared_from_this
withthe address ofa pointer to the newly constructed object of typeT
.
Change paragraph 15:
Effects: Construct a
T
object in the storage whose address isrepresented bythat held byp
by uses-allocator construction with allocator*this
and constructor argumentsstd::forward<Args>(args)...
.
Change paragraph 1:
Preconditions:
upstream
is the address ofpoints to a valid memory resource.
Change paragraph 1:
Preconditions:
upstream
is the address ofpoints to a valid memory resource.initial_size
, if specified, is greater than zero.
Change paragraph 3:
Preconditions:
upstream
is the address ofpoints to a valid memory resource.buffer_size
is no larger than the number of bytes inbuffer
.
Change paragraph 2:
For templates
less
,greater
,less_equal
, andgreater_equal
, the specializations for any pointer type yield a resultconsistent withbased on the implementation-defined strict total order overpointersaddresses ([defns.order.ptrintro.memory]).[Note: If
pointersa < b
is well-defined fora
andb
of typeP
are ordered ([expr.rel]), then(a < b) == less<P>()(a, b)
,(a > b) == greater<P>()(a, b)
, and so forth. — end note]For template specializations
less<void>
,greater<void>
,less_equal<void>
, andgreater_equal<void>
, if the call operator calls a built-in operator comparing pointers, the call operator yields a resultconsistent withbased on the implementation-defined strict total order overpointersthe addresses held by the arguments after conversion to their composite pointer type.
Change bullet (3.1):
If the expression
std::forward<T>(t) <=> std::forward<U>(u)
results in a call to a built-in operator<=>
comparing pointersof type, returnsP
strong_ordering::less
if (the converted value of)t
precedesu
in the implementation-defined strict total order overpointersaddresses ([defns.order.ptrintro.memory]),strong_ordering::greater
ifu
precedest
, and otherwisestrong_ordering::equal
.
Change paragraph 1:
Constraints:
T
andU
satisfymodelequality_comparable_with
.
Remove paragraph 2:
Preconditions: If the expression
std::forward<T>(t) == std::forward<U>(u)
results in a call to a built-in operator==
comparing pointers of typeP
, the conversion sequences from bothT
andU
toP
are equality-preserving ([concepts.equality]); otherwise,T
andU
modelequality_comparable_with
.
Change paragraph 3:
Effects:
If the expressionstd::forward<T>(t) == std::forward<U>(u)
results in a call to a built-in operator==
comparing pointers: returnsfalse
if either (the converted value of)t
precedesu
oru
precedest
in the implementation-defined strict total order over pointers ([defns.order.ptr]) and otherwisetrue
.Otherwise, eEquivalent to:return std::forward<T>(t) == std::forward<U>(u);
Change bullet (10.1):
If the expression
std::forward<T>(t) < std::forward<U>(u)
results in a call to a built-in operator<
comparing pointers: returnstrue
if (the converted value of)t
precedesu
in the implementation-defined strict total order overpointersaddresses ([defns.order.ptrintro.memory]) and otherwisefalse
.
Change paragraph 4:
Effects: Initializes
bound-entity
withf
, andthunk-ptr
withthe address ofa pointer to a functionthunk
such thatthunk(bound-entity, call-args...)
is expression-equivalent ([defns.expression.equivalent]) toinvoke_r<R>(f, call-args...)
.
Change paragraph 7:
Effects: Initializes
bound-entity
withaddressof(f)
, andthunk-ptr
withthe address ofa pointer to a functionthunk
such thatthunk(bound-entity, call-args...)
is expression-equivalent ([defns.expression.equivalent]) toinvoke_r<R>(static_cast<cv T&>(f), call-args...)
.
Change paragraph 11:
Effects: Initializes
bound-entity
with a pointer to an unspecified object or null pointer value, andthunk-ptr
withthe address ofa pointer to a functionthunk
such thatthunk(bound-entity, call-args...)
is expression-equivalent ([defns.expression.equivalent]) toinvoke_r<R>(f, call-args...)
.
Change paragraph 15:
Effects: Initializes
bound-entity
withaddressof(obj)
, andthunk-ptr
withthe address ofa pointer to a functionthunk
such thatthunk(bound-entity, call-args...)
is expression-equivalent ([defns.expression.equivalent]) toinvoke_r<R>(f, static_cast<cv T&>(obj), call-args...)
.
Change paragraph 20:
Effects: Initializes
bound-entity
withobj
, andthunk-ptr
withthe address ofa pointer to a functionthunk
such thatthunk(bound-entity, call-args...)
is expression-equivalent ([defns.expression.equivalent]) toinvoke_r<R>(f, obj, call-args...)
.
Change bullet (2.2):
[…]
where
p
isholds the address of the uninitialized storage for the element allocated withinX
.
Change paragraph 15:
Remarks: It is unspecified whether multiple calls to
do_get()
withthe address ofthe sametm
object will update the current contents of the object or simply overwrite its members. Portable programs should zero out the object before invoking the function.
Change paragraph 4:
Each constructor then sets
N
to 0, andposition
toposition_iterator(a, b, re, m)
. Ifposition
is not an end-of-sequence iterator the constructor setsresult
tothe addressa pointer to the beginning of the current match. Otherwise if any of the values stored insubs
is equal to −1 the constructor sets*this
to a suffix iterator that points to the range [a
,b
), otherwise the constructor sets*this
to an end-of-sequence iterator.
Change paragraphs 3 and 4:
Otherwise, if
N + 1 < subs.size()
, incrementsN
and setsresult
tothe addressa pointer to the beginning of the current match.
Otherwise, sets
N
to 0 and incrementsposition
. Ifposition
is not an end-of-sequence iterator the operator setsresult
tothe addressa pointer to the beginning of the current match.
Change paragraph 4:
It is possible to have degenerate generalized slices in which an
addresselement is repeated.
Change paragraph 2:
[…] The three pointers are:
- the beginning pointer,
or lowestto the first elementaddressin the array (calledxbeg
here);- the next pointer,
orto the next elementaddressthat is a current candidate for reading or writing (calledxnext
here);- the end pointer,
or first element address beyondto the end of the array (calledxend
here).
[Drafting note: While there is of course no normative wording on the subject of memory mapping or multiple processes, the use of “address” in [atomics.lockfree]/5 is otherwise consistent with the definitions here. — end drafting note]
Change paragraph 9:
Remarks: The result may be an
undefined addressinvalid pointer value, but the operations otherwise have no undefined behavior.
Change paragraph 3:
Effects: Initializes the object with the value
desired
. Initialization is not an atomic operation ([intro.multithread]). [Note: It is possible to have an access to an atomic objectA
race with its construction, for example by communicatingthe address ofa pointer to the just-constructed objectA
to another thread viamemory_order::relaxed
operations on a suitable atomic pointer variable, and then immediately accessingA
in the receiving thread. This results in undefined behavior. — end note]
Change paragraph 8:
Remarks: The result may be an
undefined addressinvalid pointer value, but the operations otherwise have no undefined behavior.
Change paragraph 2:
Effects: Initializes the object with the value
desired
. Initialization is not an atomic operation ([intro.multithread]). [Note: It is possible to have an access to an atomic objectA
race with its construction, for example, by communicatingthe address ofa pointer to the just-constructed objectA
to another thread viamemory_order::relaxed
operations on a suitable atomic pointer variable, and then immediately accessingA
in the receiving thread. This results in undefined behavior. — end note]
Change paragraph 2:
Effects: Initializes the object with the value
desired
. Initialization is not an atomic operation ([intro.multithread]). [Note: It is possible to have an access to an atomic objectA
race with its construction, for example, by communicatingthe address ofa pointer to the just-constructed objectA
to another thread viamemory_order::relaxed
operations on a suitable atomic pointer variable, and then immediately accessingA
in the receiving thread. This results in undefined behavior. — end note]
Change paragraph 2:
Affected subclauses: [expr.rel] and [expr.eq]
Change: Comparing two objects of array type is no longer valid.
Rationale: The old behavior was confusing since it compared not the contents of the two arrays, but pointers to theiraddressesfirst elements.
Effect on original feature: A valid C++2023 program directly comparing two array objects is rejected as ill-formed in this document. [Example:int arr1[5]; int arr2[5]; bool same = arr1 == arr2; // ill-formed; previously well-formed bool idem = arr1 == +arr2; // compareaddressespointers bool less = arr1 < +arr2; // compareaddressespointers, unspecified result— end example]
Change paragraph 5:
Affected subclauses: [expr.rel] and [expr.eq]
Change: C allows directly comparing two objects of array type; C++ does not.
Rationale: The behavior is confusing because it compares not the contents of the two arrays, but pointers to theiraddressesfirst elements.
Effect on original feature: Deletion of semantically well-defined feature that had unspecified behavior in common use cases.
[…]