Strict aliasing within an expression
Asked Answered
T

1

8

Suppose we have the following code:

#include <stdio.h>
#include <stdint.h>

int main()
{
    uint16_t a[] = { 1, 2, 3, 4 };
    const size_t n = sizeof(a) / sizeof(uint16_t);
    
    for (size_t i = 0; i < n; i++) {
        uint16_t *b = (uint16_t *) ((uint8_t *) a + i * sizeof(uint16_t));
        printf("%u\n", *b);
    }
    
    return 0;
}

Clearly, casting a to an uint8_t pointer is not a violation, so this question is about casting that resulting pointer to an uint16_t pointer. In my understanding, according to the standard it does violate the strict aliasing rule. However, I am not sure from a practical point of view, since the types of a and b are compatible. The only potential violation is b aliasing the uint8_t pointer that exists only throughout this one expression. So in my understanding, even if it violates the rule, I would doubt that it can cause undefined behavior. Can it?

Note that I am not saying that this code is meaningful. The question is meant for purely educational purposes regarding the understanding of strict aliasing.

Terminology answered 8/3, 2021 at 15:56 Comment(0)
D
5

This is not a strict aliasing violation.

The conversion of a to uint8_t and the subsequent pointer arithmetic is safe due to the exception given to a conversion to a pointer-to-character type.

Section 6.3.2.3p7 of the C standard states:

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned68)for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to theo riginal pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.

The conversion back and subsequent dereference is safe because b is pointing to an object of type uint16_t (specifically a member of the array a), matching the pointed-to type of b.

Section 6.5p7 states:

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

  • 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.
Disinterested answered 8/3, 2021 at 16:3 Comment(6)
No need for "pointer-to-character type" conversion - the uint32_t a[]; b = (uint32_t*)((uint16_t*)a + i * sizeof(uint32_t)/sizeof(uint16_t)) would be also fine, right? The thing is, the resulting uint16_t pointer value is properly aligned to uint16_t.Wayfaring
@Wayfaring That's not strictly safe because you don't have an array of type uint16_t that you are allowed to do arithmetic on. 6.3.2.3p7 specifically allows for pointer arithmetic on a character type to access the object's representation.Disinterested
@Disinterested What if it was uint32_t for a and sizeof(uint32_t) in both cases accordingly, but b remained uint16_t as it is? Maybe that better fits my intention of aliasing within an expression.Terminology
@Terminology That would be an aliasing violation upon dereference because you would be accessing a uint32_t as if it were a `uint16_t.Disinterested
@Disinterested I think I can answer my own question: b wouldn't be compatible with a any more, so that would violate the strict aliasing rule.Terminology
The conversion back ... is safe Could you add a link to the wording supporting this?Monandry

© 2022 - 2024 — McMap. All rights reserved.