Skip to content

Example in [allocator.requirements.general] incorrectly uses launder? #4553

Open
@languagelawyer

Description

@languagelawyer

From https://stackoverflow.com/q/66755218

[tab:cpp17.allocator] has the following example added by p0593r6:

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 X that is within its lifetime and whose type is similar to T is located at the address A.

Activity

theundergroundsorcerer

theundergroundsorcerer commented on Mar 23, 2021

@theundergroundsorcerer

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).

added
decision-requiredA decision of the editorial group (or the Project Editor) is required.
on May 2, 2021
added
cwgIssue must be reviewed by CWG.
on Sep 24, 2021
xmh0511

xmh0511 commented on Jan 17, 2022

@xmh0511
Contributor

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.

jwakely

jwakely commented on Jan 17, 2022

@jwakely
Member

That's intentional. The right one is selected, by magic, if that would make the program correct.

None of this is editorial.

xmh0511

xmh0511 commented on Jan 17, 2022

@xmh0511
Contributor

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

struct X{
   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.

jwakely

jwakely commented on Jan 17, 2022

@jwakely
Member

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.

languagelawyer

languagelawyer commented on Jan 17, 2022

@languagelawyer
ContributorAuthor

«well-formed» should not be confused with «well-defined»

jwakely

jwakely commented on Jan 17, 2022

@jwakely
Member

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.

xmh0511

xmh0511 commented on Jan 18, 2022

@xmh0511
Contributor

@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.

xmh0511

xmh0511 commented on Jan 18, 2022

@xmh0511
Contributor

Cite the quotes of the definition of malloc in C

Description:

  • 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.
struct T{
   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.

frederick-vs-ja

frederick-vs-ja commented on Jul 20, 2022

@frederick-vs-ja
Contributor

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

jensmaurer commented on Mar 12, 2023

@jensmaurer
Member

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

frederick-vs-ja commented on Mar 13, 2023

@frederick-vs-ja
Contributor

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

jensmaurer commented on Mar 13, 2023

@jensmaurer
Member

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.

13 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cwgIssue must be reviewed by CWG.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @zygoloid@jwakely@tkoeppe@xmh0511@frederick-vs-ja

        Issue actions

          Example in [allocator.requirements.general] incorrectly uses launder? · Issue #4553 · cplusplus/draft