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?

Columbo
  • 60,038
  • 8
  • 155
  • 203
Red Alert
  • 3,786
  • 2
  • 17
  • 24
  • 3
    @molbdnilo char * can always alias – Shafik Yaghmour Nov 18 '14 at 21:12
  • @ShafikYaghmour Yes, of course. How could I forget? – molbdnilo Nov 18 '14 at 21:18
  • Possibly because `data` is already an alias for `&data[0]`? Also `int const * data;` is a closer match to `int data[1];` – rafeek Nov 18 '14 at 21:22
  • 3
    You might want to consider using `std::aligned_storage` for this: http://en.cppreference.com/w/cpp/types/aligned_storage – mattnewport Nov 20 '14 at 20:42
  • @mattnewport 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 freedom – Red Alert Nov 20 '14 at 20:50
  • 3
    Why does the warning completely go away since gcc 7.2? LIVE(https://godbolt.org/g/ci5dKj) – sandthorn Feb 01 '18 at 02:25
  • Yeah that is pretty concerning... I had a bug with 7.2+ that is solved with launder (i think), weird its warnings are broken... – David Ledger Nov 27 '18 at 14:48
  • It seems to be a bug in GCC: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80593 – Flamefire Aug 14 '19 at 10:03
  • @mattnewport 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](https://stackoverflow.com/questions/47735657/does-reinterpret-casting-stdaligned-storage-to-t-without-stdlaunder-violat?noredirect=1&lq=1) // [c++ - Were all implementations of std::vector non-portable before std::launder? - Stack Overflow](https://stackoverflow.com/questions/62642542/were-all-implementations-of-stdvector-non-portable-before-stdlaunder?noredirect=1&lq=1) – user202729 Feb 25 '22 at 14:02

3 Answers3

24

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.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 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? – Red Alert Nov 20 '14 at 20:52
  • 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. – Red Alert Nov 20 '14 at 20:59
  • 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](https://bugzilla.redhat.com/show_bug.cgi?id=638477#c129). – Kevin Feb 15 '15 at 22:40
  • @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). – supercat Jun 29 '16 at 17:08
  • 1
    @supercat: How does the relative efficiency of memcpy and memmove relate to this Q&A? – Kevin Jun 29 '16 at 17:11
  • @Kevin: Sorry--neglected to indicate that I was making an observation about the *if things break anyway* link. – supercat Jun 29 '16 at 17:14
  • 1
    @supercat: Read Linus more closely. The difference is one CPU cycle. If you cannot afford one cycle, you should be writing assembly, not C. – Kevin Jun 30 '16 at 00:02
  • @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... – supercat Jun 30 '16 at 15:38
  • ...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. – supercat Jun 30 '16 at 15:40
  • 3
    Why does the warning completely go away since gcc 7.2? LIVE(https://godbolt.org/g/ci5dKj) – sandthorn Feb 01 '18 at 02:26
  • "_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. – curiousguy Jul 15 '18 at 11:19
  • 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]. – curiousguy Jul 15 '18 at 11:46
  • 1
    @curiousguy Nope. – Columbo Jul 15 '18 at 11:46
  • Do you believe that any real use of a union was UB in all previous C++ std? – curiousguy Jul 15 '18 at 11:47
  • 1
    @curiousguy 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. – Columbo Jul 15 '18 at 11:48
  • 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? – curiousguy Jul 15 '18 at 11:48
  • 1
    @curiousguy 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. – Columbo Jul 15 '18 at 11:49
  • @curiousguy The value of a pointer includes its category, regardless of what its value representation actually contains. – Columbo Jul 15 '18 at 11:50
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176007/discussion-between-curiousguy-and-columbo). – curiousguy Jul 15 '18 at 11:53
  • @Columbo Shouldn't you `launder(data)`, not `launder(reinterpret_cast(data))`? Or does it not matter as long as you you launder pre-dereference? – Barry Jul 17 '18 at 15:17
  • 1
    @Barry Surely, you want to `launder` after a cast, not before? – Columbo Jul 18 '18 at 01:10
  • @Columbo I dunno about "surely," but you're right http://eel.is/c++draft/ptr.launder – Barry Jul 18 '18 at 05:42
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.
  • [...]
xmh0511
  • 7,010
  • 1
  • 9
  • 36
-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.

Delaware
  • 45
  • 1
  • 6
    The underlying problem is still there. – HolyBlackCat Jul 14 '18 at 22:42
  • @HolyBlackCat There is no "underlying problem" here. – curiousguy Jul 15 '18 at 11:07
  • @curiousguy 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. – HolyBlackCat Jul 15 '18 at 11:14
  • @HolyBlackCat I don't see any UB. – curiousguy Jul 15 '18 at 11:16
  • @curiousguy Breaking strict aliasing is UB. – HolyBlackCat Jul 15 '18 at 11:24
  • @HolyBlackCat There is no such thing as "breaking strict aliasing". It's an absurd idea. If there was, that couldn't be such a case. – curiousguy Jul 15 '18 at 11:25
  • 7
    @curiousguy 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?](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule). – HolyBlackCat Jul 15 '18 at 11:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176006/discussion-between-curiousguy-and-holyblackcat). – curiousguy Jul 15 '18 at 11:35