You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Example in [allocator.requirements.general] incorrectly uses launder? #4553
When reusing storage denoted by some pointer value p, launder(reinterpret_cast<T*>(new (p) byte[n * sizeof(T)])) can be used to implicitly create a suitable array object and obtain a pointer to it.
If T is not an implicit-lifetime type, then, even if an object of type T[m ≤ n] is created, the lifetimes of the array elements are not started, which means launder's preconditions are violated:
Preconditions: p represents the address A of a byte in memory. An object Xthat is within its lifetime and whose type is similar to T is located at the address A.
The usage is indeed incorrect. The valid cast is launder(reinterpret_cast<T(*)[n]>(new (p) byte[n * sizeof(T)])) (May be n is not necessary) I believe. The problem is that value of the array (as a pointer) is valid pointer for purposes of arithmetic and casting to it and laundering it should be allowed as long as I do not de-reference it, (without constructing an object first).
These operations select one of the implicitly-created objects whose address is the address of the start of the region of storage, and produce a pointer value that points to that object, if that value would result in the program having defined behavior.
Incidentally, the standard never specifies which implicitly created objects whose addresses are the address of the start of the region of storage.
That's intentional. The right one is selected, by magic, if that would make the program correct.
So, we cannot plainly determine whether a program is well-formed. For instance
structX{
int x;
};
auto ptr = (X*)malloc(sizeof(X));
ptr->x = 0;// is well-formed?
No one can say the return value of malloc can be the address of object X. Although X is implicitly-lifetime type, however, we don't know whether that object's address is the address of the start of the region of storage. I.E., only the implementor of the function malloc knows that.
No, that's not what the wording means. There is no "implementor of the function malloc," because it's part of the implementation and so behaves as the standard says (not as some developer says). The standard says that if beginning the lifetime of an object of type X at that location would make the program have defined behaviour, then that's what happens. So the answer to the "is well-formed?" question is yes. By definition. If creating an X there makes it well-formed, then that's what happens, and so it's well-formed.
Yes, and ptr->x = 0 is always well-formed when ptr has type X*. It's also well-defined because malloc implicitly creates objects of the type needed to make it have defined behaviour.
@jwakely However, the standard/implementation never says that the object of type X would be located at the start address of that region. This is the confusion here. Actually, [intro.object] p11 imposes two requirements:
these objects shall have an implicitly-lifetime type.
these objects' addresses shall be the address of the start of the region of storage.
we can say the object of type X satisfies the first bullet since the standard defines the "implicitly-lifetime type", hence malloc implicitly creates the object with type X. However, the standard never says these objects' addresses are what(i.e., whether their addresses satisfy the second bullet). Based on this logic, we cannot determine whether ptr->x = 0; is well-defined or not.
The malloc function allocates space for an object whose size is specified by size and
whose value is indeterminate.
Returns
The malloc function returns either a null pointer or a pointer to the allocated space.
structT{
int i;
char c;
};
auto tptr = (T*)malloc(sizeof(T));
tptr-> a = 0;
auto iptr = (int*)malloc(sizeof(T));
*iptr = 0;
The standard only specifies that an object of type T and the member subobject T::i is pointer-interconvertible, hence they have the same address. However, neither in C++ nor C standard, they ever specify, through any provision, that the addresses of a type T object and its subobject T::i are identical to the address of the start of the region of the storage that is allocated by the malloc function. when we say the above example is well-defined is just based on the premise that these objects whose addresses are the address of the start of the allocated space. However, this premise has no formal definition to prove in the current standard.
Yes, because that's a situation where we're not intending to re-use the existing bytes for the to-be object representation. As-is, the optimizer can apply dead-store elimination for any store to the affected region.
start_lifetime_as_array<T>(new (p) byte[n * sizeof(T)], n) seemingly works as intended. Although there's an open issue CWG1997 indicating that it may be unclear whether the new-expression renders the contents in the storage indeterminate.
That core issue far predates std::start_lifetime_as. I think the goal of CWG1997 is (and continues to be) to clarify that placement-new without initialization also yields an object with indeterminate value.
The goal of CWG1997, in the first place, should be to clarify that the lifetime of objects created by new-expression using non-allocating operator new starts, because «obtaining storage» is a precondition to lifetime start, and somehow there is doubt that non-allocating operator new obtains storage 🤡
And in the case of non-implicit type, it would be just a pointer * before * the start of a lifetime!
Example for (1). If the object is created implicitly, then placement new shall return a pointer to it. Therefore this is a perfectly valid code point to the first element of the array.
struct A { int p; };
std::byte s[100 * sizeof(A)];
reinterpret_cast<A*>(new (s) std::byte[100 * sizeof(A)])[4].p = 100; //OK?
Right , but what does it change? For non implicitly created types it may be the pointer to the first element before its lifetime started which can be used in limited ways - just enough to create an object in it using another placement new
That new (s) std::byte[…] always returns a pointer to the first element of the array of some std::bytes, and implicitly created objects do not affect this.
I think there's an interesting decision for CWG to make here. I agree with the issue description:
When reusing storage denoted by some pointer value p, launder(reinterpret_cast<T*>(new (p) byte[n * sizeof(T)])) can be used to implicitly create a suitable array object and obtain a pointer to it.
If T is not an implicit-lifetime type, then, even if an object of type T[m ≤ n] is created, the lifetimes of the array elements are not started, which means launder's preconditions are violated:
Preconditions: p represents the address A of a byte in memory. An object Xthat is within its lifetime and whose type is similar to T is located at the address A.
(If T is an implicit-lifetime type, or even for all types T, launder may not even be required, see below)
I think we can resolve the disagreement between the claim about a valid use of launder and the precondition of launder in (at least) three ways. We could say that:
The precondition of launder is wrong, and it should alternatively suffice for there to exist an array object of type T[n] within its lifetime such that p represents the address of an element of the array, which need not be within its lifetime.
(Really a special case of (1).) We want a separate launder_array function for this case.
The precondition of launder is wrong, and it should alternatively suffice for there to exist an array object of type T[n] within its lifetime such that p represents the address of an element of the array, which need not be within its lifetime.
Option 2 is probably the simplest if it works.
int arr[1];
for (int i = 0; i < INT_MAX; i++) ::new (&arr[0]) int {};
std::launder(&arr[0]); // which of INT_MAX+1 objects std::launder returns pointer to?
int arr[1];
for (int i = 0; i < INT_MAX; i++) ::new (&arr[0]) int {};
std::launder(&arr[0]); // which of INT_MAX+1 objects std::launder returns pointer to?
The rule for transparently replacing makes all of these pointer values equivalent, IIUC.
Activity
theundergroundsorcerer commentedon Mar 23, 2021
The usage is indeed incorrect. The valid cast is
launder(reinterpret_cast<T(*)[n]>(new (p) byte[n * sizeof(T)]))(May benis not necessary) I believe. The problem is that value of the array (as a pointer) is valid pointer for purposes of arithmetic and casting to it and laundering it should be allowed as long as I do not de-reference it, (without constructing an object first).xmh0511 commentedon Jan 17, 2022
Incidentally, the standard never specifies which implicitly created objects whose addresses are the address of the start of the region of storage.
jwakely commentedon Jan 17, 2022
That's intentional. The right one is selected, by magic, if that would make the program correct.
None of this is editorial.
xmh0511 commentedon Jan 17, 2022
So, we cannot plainly determine whether a program is well-formed. For instance
No one can say the return value of
malloccan be the address of objectX. AlthoughXis implicitly-lifetime type, however, we don't know whether that object's address is the address of the start of the region of storage. I.E., only the implementor of the functionmallocknows that.jwakely commentedon Jan 17, 2022
No, that's not what the wording means. There is no "implementor of the function
malloc," because it's part of the implementation and so behaves as the standard says (not as some developer says). The standard says that if beginning the lifetime of an object of typeXat that location would make the program have defined behaviour, then that's what happens. So the answer to the "is well-formed?" question is yes. By definition. If creating anXthere makes it well-formed, then that's what happens, and so it's well-formed.languagelawyer commentedon Jan 17, 2022
«well-formed» should not be confused with «well-defined»
jwakely commentedon Jan 17, 2022
Yes, and
ptr->x = 0is always well-formed whenptrhas typeX*. It's also well-defined becausemallocimplicitly creates objects of the type needed to make it have defined behaviour.xmh0511 commentedon Jan 18, 2022
@jwakely However, the standard/implementation never says that the object of type
Xwould be located at the start address of that region. This is the confusion here. Actually, [intro.object] p11 imposes two requirements:we can say the object of type
Xsatisfies the first bullet since the standard defines the "implicitly-lifetime type", hencemallocimplicitly creates the object with typeX. However, the standard never says these objects' addresses are what(i.e., whether their addresses satisfy the second bullet). Based on this logic, we cannot determine whetherptr->x = 0;is well-defined or not.xmh0511 commentedon Jan 18, 2022
Cite the quotes of the definition of
mallocin CThe standard only specifies that an object of type
Tand the member subobjectT::iis pointer-interconvertible, hence they have the same address. However, neither in C++ nor C standard, they ever specify, through any provision, that the addresses of a typeTobject and its subobjectT::iare identical to the address of the start of the region of the storage that is allocated by themallocfunction. when we say the above example is well-defined is just based on the premise that these objects whose addresses are the address of the start of the allocated space. However, this premise has no formal definition to prove in the current standard.frederick-vs-ja commentedon Jul 20, 2022
P2590R0 addressed this issue by replace the use with
start_lifetime_as_array<T>(p, n), but this change was removed in later revisions.jensmaurer commentedon Mar 12, 2023
Yes, because that's a situation where we're not intending to re-use the existing bytes for the to-be object representation. As-is, the optimizer can apply dead-store elimination for any store to the affected region.
frederick-vs-ja commentedon Mar 13, 2023
start_lifetime_as_array<T>(new (p) byte[n * sizeof(T)], n)seemingly works as intended. Although there's an open issue CWG1997 indicating that it may be unclear whether the new-expression renders the contents in the storage indeterminate.jensmaurer commentedon Mar 13, 2023
That core issue far predates
std::start_lifetime_as. I think the goal of CWG1997 is (and continues to be) to clarify that placement-new without initialization also yields an object with indeterminate value.languagelawyer commentedon Mar 13, 2023
The goal of CWG1997, in the first place, should be to clarify that the lifetime of objects created by new-expression using non-allocating
operator newstarts, because «obtaining storage» is a precondition to lifetime start, and somehow there is doubt that non-allocatingoperator newobtains storage 🤡MX20 commentedon Aug 7, 2023
Although std::launder should not be required at all and everything should correct itself.
Example for (1). If the object is created implicitly, then placement new shall return a pointer to it. Therefore this is a perfectly valid code point to the first element of the array.
languagelawyer commentedon Aug 7, 2023
@MX20
new ... T[N], for N > 0, always returns the pointer to the first element of the «array of NT» http://eel.is/c++draft/expr.new#10.sentence-1MX20 commentedon Aug 7, 2023
Right , but what does it change? For non implicitly created types it may be the pointer to the first element before its lifetime started which can be used in limited ways - just enough to create an object in it using another placement new
languagelawyer commentedon Aug 7, 2023
That
new (s) std::byte[…]always returns a pointer to the first element of the array of somestd::bytes, and implicitly created objects do not affect this.MX20 commentedon Aug 7, 2023
I see , fair enough
zygoloid commentedon Aug 13, 2023
I think there's an interesting decision for CWG to make here. I agree with the issue description:
I think we can resolve the disagreement between the claim about a valid use of
launderand the precondition oflaunderin (at least) three ways. We could say that:launder#6452 if that works.launderis wrong, and it should alternatively suffice for there to exist an array object of typeT[n]within its lifetime such thatprepresents the address of an element of the array, which need not be within its lifetime.launder_arrayfunction for this case.Option 2 is probably the simplest if it works.
languagelawyer commentedon Aug 21, 2023
frederick-vs-ja commentedon Jul 30, 2024
The rule for transparently replacing makes all of these pointer values equivalent, IIUC.
frederick-vs-ja commentedon Aug 3, 2024
LWG4130
frederick-vs-ja commentedon Oct 23, 2026
This should have been fixed by #8926 (d67ab97).