Search code examples
clanguage-lawyerstrict-aliasing

Does casting to a char pointer to increment a pointer by a certain amount and then accessing as a different type violate strict aliasing?


For example, you might want to align a void * that is aligned to 4-byte boundary to 16-byte boundary.

int *align16(void *p) {
    return (int *)((char *)p + 16 - (uintptr_t)p % 16);
}

Normally, accessing an int * casted from a char * violates strict aliasing rules and invokes undefined behaviour, while the opposite is safe.

Is it still undefined behaviour in cases such as above?

I found an old question similar to this where the reply says it's fine as far as the alignment is kept. However, that answer does not mention anything about strict aliasing and did not reply to a comment related to the standard specification.


A side question. The optimized compiler output of align16 can be decompiled as follows.

int *align16(void *p) {
    return (int *)((uintptr_t)p + 16 & -16);
}

How does the standard deal with such case, accessing a pointer modified while casted to a uintptr_t?


Solution

  • Does casting to a char pointer to increment a pointer by a certain amount and then accessing as a different type violate strict aliasing?

    Not inherently so.

    Normally, accessing an int * casted from a char * violates strict aliasing rules

    Not necessarily. Strict aliasing is about the (effective) type of the pointed-to object. It is quite possible for the object to which a char * points to be an int, or compatible with int, or to be assigned effective type int as a consequence of the (write) access. In such cases, casting to int * and dereferencing the result is perfectly valid.

    There are, yes, lots of cases in which casting a char * to an int * and then dereferencing the result would constitute a strict-aliasing violation, but it is not specifically because of the involvement of, or the casting to or from, type char *.

    The above applies regardless of how the particular char * value was obtained, so in your particular example case, too. If the result of your pointer computation is a valid pointer, and the object to which it points is genuinely an (effective) int or is compatible with int in one of the specific ways documented in section 6.5 of the language spec, then reading the pointed-to value via the pointer is fine. Otherwise, it is a strict-aliasing violation.

    Attempting to dereference a pointer value that is not correctly aligned for its type is a potential issue in general with pointer manipulation, but the strict aliasing rule is stronger than and effectively inclusive of pointer alignment considerations. If you have an access that satisfies the strict aliasing rule then the pointer involved must be satisfactorily aligned for its type. The reverse is not necessarily true.


    Do note, however, that although on many platforms, your align16() will indeed attempt to perform a read of a 16-byte-aligned object, the C language specifications do not require that to be so. Pointer-to-integer and integer-to-pointer conversions are explicitly allowed, but their results are implementation defined. It is not necessarily the case that value on the integer side of such a conversion reports on or controls the alignment of the pointer on the other side.

    How does the standard deal with such case, accessing a pointer modified while casted to a uintptr_t?

    See above. Pointer-to-integer and integer-to-pointer conversions have implementation-defined effect as far as the language spec is concerned. However, on most implementations you're likely to meet, your two versions of align16() will have equivalent behavior.