Is pointer arithmetic still well defined after casting with alignment violation?

Viewed 211

I know that the result of pointer casting with alignment violation invokes undefined behaviour once dereferenced.

But what about pointer casting for address calculation only (without dereferencing)?

void *addr_calc(single_byte_aligned_struct_t *ptr, uint32_t dword_offset)
{
    uint32_t *dw_ptr = (uint32_t *)ptr;

    return dw_ptr + dword_offset;
}

Let's assume the value of ptr is X. Is it guaranteed that that addr_calc() will return X + sizeof(uint32_t) * dword_offset?

My assumption was that it is but recently I saw the following in the C11 standard, section J.2 Undefined behaviour

— Conversion between two pointer types produces a result that is incorrectly aligned (6.3.2.3).

If I understood it correctly, the casting itself invokes undefined behaviour, not only dereferencing, which means that even the pointer arithmetic may behave unpredictably in such case. Am I right?

3 Answers

This can in fact result in undefined behavior if ptr is not properly aligned for uint32_t. Some systems might allow it but others could trigger a fault.

A safe conversion would be to char *, then doing the pointer arithmetic on that.

return (char *)ptr + dword_offset * sizeof(uint32_t);

Yes, you understood it correctly. As an example, on some computers that are word-addressed but nevertheless have a char type that is smaller than the word, an int * pointer could be of smaller size, so there is no telling what casting an unaligned char * to int * would do - but a trap would be a best-case scenario.

If you need byte-wise pointer arithmetic, use a pointer to a character type. All other object pointer types should ever be used only for referencing true objects or arrays of the pointed-to type.

A notable example of a situation where casting an unaligned pointer may result in a malfunction would be when processing:

void test(void *dest, void *src)
{
    uint32_t *d = dest;
    uint32_t *s = src;
    memcpy(d, s, 4);
}

with clang on a platform that doesn't support unaligned word accesses. In cases where the source and destination do not overlap, the behavior of memcpy(d, s, 4); is specified as being equivalent to:

((unsigned char*)d)[0] = ((unsigned char*)s)[0];
((unsigned char*)d)[1] = ((unsigned char*)s)[1];
((unsigned char*)d)[2] = ((unsigned char*)s)[2];
((unsigned char*)d)[3] = ((unsigned char*)s)[3];

Clang, however, will exploit the fact that a uint32_t* can be presumed never to hold an unaligned address, and thus generate code which uses a single 32-bit load and store, and will thus only work if the pointers are aligned. Although the code clang generates to perform the assignment to a uint32_t* wouldn't care about whether the pointer is aligned, and although the pointer is coerced to void* when it is passed to memcpy, the conversion of the pointer to uint32_t* within that sequence of events will cause clang to generate code that does care about alignment.

Related