Does This Really Break Strict-Aliasing Rules

Does this really break strict-aliasing rules?

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.

Does this break the strict aliasing rule?

Quoting C11, chapter §6.5p7

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

  1. a type compatible with the effective type of the object

Also

Quoting C11, chapter §6.5p6

The effective type of an object for an access to its stored value is the declared type of the object, if any....

Emphases mine

Here the effective type of the object pointed by num is indeed int and hence it is okay to dereference it with a pointer to int.

Strict aliasing rules broken with templates and inheritance

Nope, there is no strict aliasing rule violation in the provided code. It looks like a bug in gcc.

You can submit a bugreport to gcc (I was not able to find anything already there related to the snippet provided), however, judging by life and times of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41874 I would not expect immediate fix.

Is this code breaking strict-aliasing rules?

You are attacking the strict aliasing rules from the wrong angle. Casting an array of char to your structure would in fact be UB. This is not only because of aliasing but also because alignment properties can be different. Don't do that.

You'd have to do it the other way round: declare your structure of the real type that you want to have, and then use a void* or char* pointer to that structure to read or copy your data into it.

This is always guaranteed to work:

  • character types are exempted from the strict aliasing rules
  • passing a pointer of an object to a function (memcpy or other) always ensures that the compiler can't make any assumptions about the state of that object after the call, so he has to reload the entire object.

Edit: Perhaps some the confusion comes from the strange gcc warning about "aliasing rules". This is only one facet of the problems that come from type-punning through pointer casts. Generally accessing an object through a pointer of a wrong type other than character types can have undefined behavior. Aliasing is only one of several things that can go wrong with that. Just avoid it.

Does this code violate the strict aliasing rule?

No it doesn't, but this is only because the memory was allocated, and written into using a character type.

Memory is allocated using malloc. That object doesn't have declared1 type because it was allocated with malloc. Thus the object doesn't have any effective type.

Then the code accesses and modifies the object using the type char. As the type is2 char and no object having an effective type is copied5, copying doesn't set the effective type to char for this and subsequent accesses, but sets the effective type to char, only for the duration of the access3. After the access, the object doesn't have an effective type anymore.

Then the type int is used to access and only read that object. As the object doesn't have an effective type, it becomes3 int, for the duration of the read. After the access the object doesn't have an effective type anymore. As int was obviously compatible with the effective type int, the behavior is defined.

(Assuming the values read are not trap representation for int.)


Had you accessed and modified the object using a non-character type that is also not compatible with int, the behavior would be undefined.

Let's say your example was (assuming sizeof(float)==sizeof(int)):

int i;
void *buf = calloc(5, sizeof(float)); // buf initialized to 0

{
float *ptr1 = buf;
for(i = 0; i < 5*sizeof(float); ++i)
ptr1[i] = (float)i;
}

int *ptr2 = buf;
for(i = 0; i < 5; ++i)
printf("%d", ptr2[i]);

The effective type of the object, when floats are being written into, becomes of type float, for the duration of the write and all subsequent accesses to the object that don't modify it2. When those objects are then accessed by int the effective type remains float, as the values are only being read not modified. The previous write using float set the effective type to float permanently until the next write into this object (which didn't happen in this case). Types int and float are not compatible4, thus the behavior is undefined.


(All text below is quoted from: ISO:IEC 9899:201x)

1 (6.5 Expressions 6)

The effective type of an object for an access to its stored value is the declared type of the object, if any. 87) Allocated objects have no declared type.

2 (6.5 Expressions 6)

If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value.

3 (6.5 Expressions 6)

For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.

4 (6.5 Expressions 8)

An object shall have its stored value accessed only by an lvalue expression that has one of
the following types: 88)
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the
object,
— a type that is the signed or unsigned type corresponding to a qualified version of the
effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its
members (including, recursively, a member of a subaggregate or contained union), or
— a character type.

5 (6.5 Expressions 6)

If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one.

Am I breaking strict aliasing rules?

There is only one intrinsic that "extracts" the lower order double value from xmm register:

double _mm_cvtsd_f64 (__m128d a)

You could use it this way:

return _mm_cvtsd_f64(x);

There is some contradiction between different references. MSDN says: This intrinsic does not map to any specific machine instruction. While Intel intrinsic guide mentions movsd instruction. In latter case this additional instruction is easily eliminated by optimizer. At least gcc 4.8.1 with -O2 flag generates code with no additional instruction.

Is it a violation of strict aliasing to cast to a super-class and back in C?


  • My primary question is whether do_with_a() is always defined behavior

    As a sub-question: I believe that converting an struct a * to a struct header * by &ap->hdr or (struct header *)ap would both be well-defined, is this the case?

    It's well-defined as per the initial member rule C17 6.7.2.1/15 emphasis mine:

    Within a structure object, the non-bit-field members and the units in which bit-fields
    reside have addresses that increase in the order in which they are declared. A pointer to a
    structure object, suitably converted, points to its initial member
    (or if that member is a
    bit-field, then to the unit in which it resides), and vice versa. There may be unnamed
    padding within a structure object, but not at its beginning.

    This is also consistent with the effective type/strict aliasing rule you quote in 6.5 §6 and §7


  • I'm reasonably certain that do_with_b() will always be undefined behavior

    Yes, it's not a compatible struct. So it's a strict aliasing violation and possibly also an alignment problem. Note however that the strict aliasing rule is compatible with an oddball rule called "common initial sequence", which in this case would allow you to inspect the header part of b. C17 6.5.2.3/6:

    One special guarantee is made in order to simplify the use of unions: if a union contains
    several structures that share a common initial sequence (see below), and if the union
    object currently contains one of these structures, it is permitted to inspect the common
    initial part of any of them anywhere that a declaration of the completed type of the union
    is visible. Two structures share a common initial sequence if corresponding members
    have compatible types (and, for bit-fields, the same widths) for a sequence of one or more
    initial members.

    That is, if you add something like typedef union { struct a a_; struct b b_; } dummy; to the translation unit, you would be allowed to inspect the header part of each struct in a well-defined manner. But not that compilers might have shaky standard compliance when it comes to implementing this feature and there were some defect reports about it to the committee (I'm unsure of its status as per C23).


  • GCC, however, does signal warnings about both down-casts when set to -Wstrict-aliasing=1

    These options in gcc have status somewhere between broken and very broken. -fno_strict_aliasing to disable it entirely is reliable however.

    The strict aliasing rule itself got many flaws: for example the effective type of the memory you allocated is actually a struct header and a float, not a struct a, because you didn't write an lvalue of type struct a to the memory returned by malloc. Similarly, given that we allocate a chunk of memory with malloc then treat it as an array of type by initializing it in a for loop, then we actually don't have the effective type type[] but individual objects. But if implemented like that the whole C language unravels.

custom optional breaks strict aliasing rules

What can I do to make this class without UB?

  • Use placement-new to create the object.
  • Call the destructor of the created object in destructor of optional
  • Do not reinterpret the address of the storage. Use the pointer returned from placement new. This unfortunately means that you need to store the pointer as a member. You can replace the bool since null pointer would signify empty state.
  • Take care of alignment. This can be quite tricky pre-C++11. You may need to rely on non-standard language features to achieve this. Given a pointer member which has quite strict alignment, and the fact that C++98 has no overaligned types, you might get away with ignoring alignment for most types.

It would be much easier to allocate the object dynamically. Slower, very likely. But simpler and standard conformant.



Related Topics



Leave a reply



Submit