Does this really break strict-aliasing rules?
Asked Answered
P

3

36

When I compile this sample code using g++, I get this warning:

warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

The code:

#include <iostream>

int main() 
{
   alignas(int) char data[sizeof(int)];
   int *myInt = new (data) int;
   *myInt = 34;

   std::cout << *reinterpret_cast<int*>(data);
}

In this case, doesn't data alias an int, and therefore casting it back to an int would not violate strict aliasing rules? Or am I missing something here?

Edit: Strange, when I define data like this:

alignas(int) char* data = new char[sizeof(int)];

The compiler warning goes away. Does the stack allocation make a difference with strict aliasing? Does the fact that it's a char[] and not a char* mean it can't actually alias any type?

Phenomenology answered 18/11, 2014 at 20:49 Comment(9)
@molbdnilo char * can always aliasSignora
@ShafikYaghmour Yes, of course. How could I forget?Trevino
Possibly because data is already an alias for &data[0]? Also int const * data; is a closer match to int data[1];Varner
You might want to consider using std::aligned_storage for this: en.cppreference.com/w/cpp/types/aligned_storageSzombathely
@Szombathely Thanks, I'll look into it. At the moment I am simply using #pragma pack(1) to give everything an alignment of 1, but that may give me some more freedomPhenomenology
Why does the warning completely go away since gcc 7.2? LIVE(godbolt.org/g/ci5dKj)Conaway
Yeah that is pretty concerning... I had a bug with 7.2+ that is solved with launder (i think), weird its warnings are broken...Carnage
It seems to be a bug in GCC: gcc.gnu.org/bugzilla/show_bug.cgi?id=80593Retrorse
@Szombathely aligned_storage is still undefined behavior in some cases, c++ - Does reinterpret_casting std::aligned_storage* to T* without std::launder violate strict-aliasing rules? - Stack Overflow // c++ - Were all implementations of std::vector non-portable before std::launder? - Stack OverflowJudkins
B
25

The warning is absolutely justified. The decayed pointer to data does not point to an object of type int, and casting it doesn't change that. See [basic.life]/7:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
(7.1) — [..]
(7.2) — the new object is of the same type as the original object (ignoring the top-level cv-qualifiers),

The new object is not an array of char, but an int. P0137, which formalizes the notion of pointing, adds launder:

[ Note: If these conditions are not met, a pointer to the new object can be obtained from a pointer that represents the address of its storage by calling std::launder (18.6 [support.dynamic]). — end note ]

I.e. your snippet can be corrected thusly:

std::cout << *std::launder(reinterpret_cast<int*>(data));

.. or just initialize a new pointer from the result of placement new, which also removes the warning.

Bamberger answered 20/11, 2014 at 20:33 Comment(22)
So, even though the char[]s lifetime has ended, the pointer data decays into is still a valid way to access the int stored within?Phenomenology
I see, thanks. My main fear was that the compiler could assume that data couldn't alias an int, and therefore could optimize out the effect *myInt = 34; would have on the print statement.Phenomenology
If a compiler gives an incorrect warning about a situation such as this, I would be at least mildly concerned about the possibility of miscompilation. Being technically correct won't help if things break anyway.Magellan
@Kevin: If C really wants to facilitate use of the most efficient ways of copying memory in various situations, it should have more variants of memcpy including one which promises to work in cases where the destination does not identify any byte of the source after the first and one which promises to work in cases where the source does not identify any byte of the destination after the first (if source and destination might be equal, but rarely enough that the cost of adding a check to every copy would exceed the cost of a few redundant copies, I see nothing to be gained by mandating a check).Izaguirre
@supercat: How does the relative efficiency of memcpy and memmove relate to this Q&A?Magellan
@Kevin: Sorry--neglected to indicate that I was making an observation about the if things break anyway link.Izaguirre
@supercat: Read Linus more closely. The difference is one CPU cycle. If you cannot afford one cycle, you should be writing assembly, not C.Magellan
@Kevin: On many implementations, the size of inline code to perform a fixed bottom-up or fixed top-down memcpy is comparable to the cost of calling a library function (in some cases, it may be a tiny bit cheaper); inline code for memmove would be more than twice as large. C isn't only used for x64 development; it's also used for small embedded systems where such things matter. As for assembly vs C, much of the purpose of C was to reduce the need to use assembly code in systems programming. Personally, I think C should split into recognized distinct dialects for people who want...Izaguirre
...a language which uses C syntax but can use the same kinds of powerful optimizations available to FORTRAN compilers at the expense of having the same semantic limitations as FORTRAN, versus those who want an genuine low-level language.Izaguirre
Why does the warning completely go away since gcc 7.2? LIVE(godbolt.org/g/ci5dKj)Conaway
"Drafting note: this maintains the status quo that malloc alone is not sufficient to create an object" That was never the status quo. It's an extremely recent invention.Overeat
If pointers are still trivial types, std::launder cannot be necessary on common implementations. But the std is lying about pointers. And lifetime. And most of [basic].Overeat
@Overeat Nope.Bamberger
Do you believe that any real use of a union was UB in all previous C++ std?Overeat
@Overeat I absolutely don't see how the latter part of your sentence derives from the former. The triviality of pointers has nothing to do with the legitimacy of how their value was derived, and whether an implementation is permitted to assume that all derivations are legitimate. Certain pointer values are invalid, and always have been, and copying them is (IIRC) undefined, regardless of the pointer types' triviality.Bamberger
If pointers are trivial, then all objects of a given type with a bit pattern has the same value. What's the value of a pointer?Overeat
@Overeat Err, yeah? It was certainly undefined to assume that a non-alive member was alive. Just because this was not articulated until recently does not mean the coherent rule system that the committee intended just permitted a load of junk. Now it's explicated.Bamberger
@Overeat The value of a pointer includes its category, regardless of what its value representation actually contains.Bamberger
Let us continue this discussion in chat.Overeat
@Bamberger Shouldn't you launder(data), not launder(reinterpret_cast<int*>(data))? Or does it not matter as long as you you launder pre-dereference?Nudibranch
@Nudibranch Surely, you want to launder after a cast, not before?Bamberger
@Bamberger I dunno about "surely," but you're right eel.is/c++draft/ptr.launderNudibranch
Z
0

*myInt = 34; this expression is well-formed, because data provide storage for object of type int and myInt is a pointer to an object of type int. So, dereference such a pointer can access an object of type int.

For *reinterpret_cast<int*>(data); this expression, it would violate the strict pointer aliasing. Firstly, there's an array-to-pointer conversion that applied to data, The result is a pointer to the initial element of data,it means the operand of reinterpret_cast<int*> is a pointer to a subject of data.
According to the following rule:

If an object is created in storage associated with a member subobject or array element e, the created object is a subobject of e's containing object if:

  • the lifetime of e's containing object has begun and not ended, and
  • the storage for the new object exactly overlays the storage location associated with e, and
  • the new object is of the same type as e (ignoring cv-qualification).

An object of type int satisfy none of these rules. Hence, the operand of reinterpret_cast<int*> is not a pointer to an object that pointer-interconvertible with an object of type int. So, The result of reinterpret_cast<int*> is not an pointer to an object of type int.

a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:

  • the dynamic type of the object.
  • [...]
Zalea answered 30/6, 2020 at 5:31 Comment(0)
C
-5

What about changing

std::cout << *reinterpret_cast<int*>(data);

to

int *tmp   = reinterpret_cast<int*>(data);
std::cout << *tmp;

?

In my case it got rid of the warning.

Christiechristin answered 14/7, 2018 at 22:36 Comment(8)
The underlying problem is still there.Reference
@Reference There is no "underlying problem" here.Overeat
@Overeat I'm not a native speaker, so maybe "underlying" is not the right word. The point is that the warning is there because OP's code causes undefined behaviour (as explained in the other answer), and the right thing to do is to change the code to remove said UB, instead of merely trying to silence the warning like this answer does.Reference
@Reference I don't see any UB.Overeat
@Overeat Breaking strict aliasing is UB.Reference
@Reference There is no such thing as "breaking strict aliasing". It's an absurd idea. If there was, that couldn't be such a case.Overeat
@Overeat If you disagree with the other answer, could you post your own one, explaining why exactly the behaviour of OP's code is well-defined and why the warning is incorrect? "There is no such thing as "breaking strict aliasing". It's an absurd idea." You should read What is the strict aliasing rule?.Reference
Let us continue this discussion in chat.Overeat

© 2022 - 2024 — McMap. All rights reserved.