Is placement new legally required for putting an int into a char array?
Asked Answered
P

3

19

There seems to be some agreement that you can't willy nilly point (an int*) into a char array because of the C++ aliasing rules.

From this other question -- Generic char[] based storage and avoiding strict-aliasing related UB -- it seems that it is allowed to (re-)use storage through placement new.

alignas(int) char buf[sizeof(int)];

void f() {
  // turn the memory into an int: (??) from the POV of the abstract machine!
  ::new (buf) int; // is this strictly required? (aside: it's obviously a no-op)

  // access storage:
  *((int*)buf) = 42; // for this discussion, just assume the cast itself yields the correct pointer value
}

So, is the above legal C++ and is the placement new actually needed to make it legal?

Princely answered 12/1, 2017 at 23:8 Comment(5)
Related: #38862592Princely
godbolt.org/g/k2nVI9Princely
Highly relevant, potential dupe: #40874020Meet
suggest changing the wording of the title to make it clear that you mean accessing the char array via an int lvalue. You can put an int into a char array by other means, e.g. memcpy it in and out.Waynewayolle
In C (not sure in C++) a pointer to char is excluded from aliasing rules (i.e. it's allowed to alias). To work this in reverse, you normally use a type-punning union with int and char members. Of course, the remaining issue is the alignment, but I assume the underlying architecture allows unaligned access and/or the pointer is properly aligned.Cleanthes
R
14

Yes, the placement new is necessary, otherwise you'd violate strict aliasing (assignment is access).

Is the above legal? Almost (although it will work on virtually all implementations). The pointer you've created through the cast does not point to the object, because the (now destroyed) array and the int object are not pointer-interconvertible; use std::launder((int*)buf), or better yet, use the placement new's return value.

Rosales answered 12/1, 2017 at 23:44 Comment(15)
Strict aliasing isn't violated by reinterpret_cast<int&>(buf) = 42 because it accesses the new int object as an lvalue of type int. Pointer-interconvertible is applicable when converting one "live" pointer to another, not when converting a "dead" pointer-to-non-object to a live one. Edit: I'm not sure whether launder is needed there; the rules were still in flux last I knew.Carlinecarling
@Carlinecarling Which new int object? I was talking about the case in which there was no prior placement new to create any such object. And I read pointer-interconvertability as applicable in general when reinterpreting pointers. Regardless, the pointer created by the op using the cast does not point to the new object as defined by P0137, so launder should be necessary.Rosales
[basic.lval] forbids accessing an object except by lvalues of certain types. It doesn't address the question of whether an object yet exists. [basic.life]/6 in C++14 allows assignment through reinterpret_cast to start a lifetime; this is removed by P0137 so C++17 will (likely) make placement-new a requirement. You're right about pointer interconvertibility; P0137 connects it to reinterpret_cast via static_cast from void*. So launder is necessary to access the live object (but never launder storage to a non-object).Carlinecarling
@Carlinecarling I don't think that's right. Assignment never created objects, except when assigning to inactive union members, no?Rosales
C++14 [basic.life]/6: "such a glvalue refers to allocated storage (3.7.4.2), and using the properties of the glvalue that do not depend on its value is well-defined." Then, lvalue-to-rvalue conversion is forbidden but write access is not. [basic.life]/1 said that obtaining storage creates an object, which implied that every suitably-aligned memory location was a potentially live object of every type. This is one hole that P0137 is patching.Carlinecarling
* every trivially-constructible typeCarlinecarling
Where is the requirement that the objects are pointer-interconvertible?Discharge
@Discharge The code obtains a pointer through reinterpret_cast (indirectly via the C-style cast). This pointer is only valid if the corresponding pointees are pointer-interconvertible, as elucidated by the cited paragraph.Rosales
@Discharge Your comments seem pretty naive. Optimization works by imposing certain restrictions on the language (which translate to assumptions for an optimizer). If the optimizer has a simple set of rules to determine which objects could be accessed by a routine and which can't be, it can perform alias optimizations.Rosales
@Rosales Actually, your comment is naive. C/C++ exist. Compilers can't have fun breaking code that uses casts. The langage is absolutely not specified, relying on vague wording that doesn't help. Please see my pointers questions https://mcmap.net/q/668195/-dereferencing-a-50-out-of-bound-pointer-array-of-array/963864Discharge
@Discharge That cited paragraph has been deleted for a reason. The language must be designed with respect to its practical implementation and usage (in this case with a strong focus on optimization), not vice versa. The code that has been broken through this change exploits pointer arithmetic in an unsound way (I have seen discussions about whether or not past-the-end pointers can be used as pointers to the next array's element as far back as last decade).Rosales
@Rosales So you are saying that C++ decided to disallow any low level pointer arithmetic. This is craziness. They never had a mandate to break that.Discharge
@Discharge "They" are compiler developers that act in your interest: making sound code run faster. I personally care little about code that employs weird pointer hacks, because it's probably broken in the first place.Rosales
@Rosales Can you please describe their "weird" hacks. I don't think so. Compilers shouldn't break valid code. "If the optimizer has a simple set of rules to determine which objects could be accessed by a routine and which can't be, it can perform alias optimizations." Well of course, these are distinct variables!Discharge
@Columbo: Compilers are used for a variety of tasks, which require doing different things. An optimization that assumes a program won't do X will be useful for tasks that don't involve doing X, but at best counter-productive for tasks that could be performed most readily by doing X. The fact that the Standard allows implementations intended for tasks that don't involve doing X to assume that programs won't do X doesn't imply any judgment as to whether all implementations should make such assumptions, nor that programs that don't uphold such assumptions are "unsound".Williamson
T
1

This has since changed with the introduction of implicit-lifetime types by P0593R6 (as a defect report, so this applies to all C++ versions).

alignas(int) char buf[sizeof(int)]; starts the lifetime of a char array char[sizeof(int)]. This will also implicitly start the lifetime of an int object that you access in the expression *((int*)buf) = 42.

Since C++17, you also need to launder the pointer: *std::launder((int*)buf) = 42.

Tartuffe answered 1/2, 2023 at 20:54 Comment(2)
"you also need to launder the pointer" Note that this would not be necessary if you had used malloc or operator new directly.Flung
Will clang and gcc ever use an abstraction model that would be capable of correctly handling the concept of objects having implicit lifetimes?Williamson
D
-4
*((int*)buf) = 42;

writes an int with a int lvalue, so there is no aliasing issue in the first place.

Discharge answered 13/1, 2017 at 17:41 Comment(1)
no aliasing issue in the first place. ... except for the original array members in static storage which are definitely of type char. The strict-aliasing exception that allows you to point (unsigned char*) at the bytes of any other object only goes on way; it doesn't make it legal to point an int* at objects that are definitely char. (It would be fine if this was anonymous memory, e.g. dynamically allocated with mmap or something that returned a void*, and was only ever accessed through char* and int*.)Dashing

© 2022 - 2025 — McMap. All rights reserved.