What rules are there for qualifiers of effective type?

So I was re-reading C17 6.5/6 - 6.5/7 regarding effective type and strict aliasing, but couldn't figure out how to treat qualifiers. Some things confuse me:

  • I always assumed that qualifiers aren't really relevant for effective type since the rules speak of lvalue access, meaning lvalue conversion that discards qualifiers. But what if the object is a pointer? Qualifiers to the pointed-at data aren't affected by lvalue conversion.

    Q1: What if the effective type is a pointer to qualified-type? Can I lvalue access it as a non-qualified pointer to the same type? Where in the standard is this stated?

  • The exceptions to the strict aliasing rule mention qualifiers in these cases:

    — 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,

    None of these address qualifiers of the effective type itself, only by the lvalue used for access. Which should be quite irrelevant, because of lvalue conversion... right?

    Q2: Does lvalue conversion happen before or after the above quoted rules of effective type/strict aliasing are applied?

    Q3: Does the effective type come with qualifiers or not? Where in the standard is this stated?


"Qualified type" being a defined term, the definition is potentially relevant:

Any type so far mentioned is an unqualified type. Each unqualified type has several qualified versions of its type, corresponding to the combinations of one, two, or all three of the const, volatile, and restrict qualifiers. The qualified or unqualified versions of a type are distinct types that belong to the same type category and have the same representation and alignment requirements. A derived type is not qualified by the qualifiers (if any) of the type from which it is derived.

(C17 6.2.5/26)

I note that the _Atomic keyword is different from the other three categorized as type qualifiers, and I presume that this is related to the fact that atomic types are not required to have the same representation or alignment requirements as their corresponding non-atomic types.

I also note that the specification is explicit that qualified and unqualified versions of a type are different types.

With that background,

Q1: What if the effective type is a pointer to qualified-type? Can I lvalue access it as a non-qualified pointer to the same type? Where in the standard is this stated?

I take you to mean this:

const uint32_t *x = &some_uint32;
uint32_t * y = *(uint32_t **) &x;

The effective type of x is const uint32_t * (an unqualified pointer to const-qualified uint32_t), and it is being accessed via an lvalue of type uint32_t * (an unqualified pointer to unqualified uint32_t). This combination is not among the exceptions allowed by the language spec. In particular, uint32_t * is not a qualified version of a const uint32_t *. The resulting behavior is therefore undefined, as specified in C17 6.5, paragraphs 6 and 7.

Although the standard does not discuss this particular application of the SAR, I take it to be justified indirectly. The issue in cases such as this is not so much about accessing the pointer value itself as about producing a pointer whose type discards qualifiers of the pointed-to type.

Note also that the SAR does allow this variation:

const uint32_t *x = &some_uint32;
const uint32_t * const y = *(const uint32_t * const *) &x;

, as const uint32_t * const is a qualified version of const uint32_t *.

Q2: Does lvalue conversion happen before or after the above quoted rules of effective type/strict aliasing are applied?

I don't see how lvalue conversion could be construed to apply before strict aliasing. The strict aliasing rule is expressed in terms of the lvalues used for accessing objects, and the result of lvalue conversion is not an lvalue.

Additionally, as @EricPostpischil observed, the SAR applies to all accesses, which include writes. There is no lvalue conversion in the first place for an lvalue that is being written.

Q3: Does the effective type come with qualifiers or not? Where in the standard is this stated?

Qualified and unqualified versions of a type are different types. I see no justification for interpreting the paragraph 6.5/6's "the declared type of the object" or "the type of the lvalue" as if the type were supposed to be considered stripped of its qualifiers, much less as if all qualifiers in the type(s) from which it is derived were stripped. The words "the type" mean what they say.