Question

I have a problem with strict aliasing in C. I'm using GCC 4.7.1.

Example 1:
When compiling this code with -fstrict-aliasing -Wstrict-aliasing=3 I get "warning: dereferencing type-punned pointer will break strict-aliasing rules"

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

int main(void)
{
    uint8_t a[4] = {0x01, 0x23, 0x45, 0x67};
    uint32_t b;

    b = *(uint32_t *)a;

    printf("%x\n", b);

    return(0);
}


Example 2:
This code gives no warning with -fstrict-aliasing and -Wstrict-aliasing=3 or -Wstrict-aliasing=2 or -Wstrict-aliasing=1

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

int main(void)
{
    uint8_t a[4] = {0x01, 0x23, 0x45, 0x67};
    uint32_t b;
    void *p;

    p = a;
    b = *(uint32_t *)p;

    printf("%x\n", b);

    return(0);
}


Both examples work correctly.

Using union is also undefined behavior and using memcpy() is too slow in my case.
So, is the first example safe (false positive) or second example is also unsafe (false negative) or ...?

Thanks.

Was it helpful?

Solution

If you want to manufacture a uint32_t from 4 uint8_t, then do just that: manufacture it. Don't try to pull one from something that isn't one by pointer casts. The code you presented will vary in results depending on whether you're platform is little or big endian, not to mention its just plain wrong.

They're both bad. Both samples provided are unsafe no matter what. data alignment requirements are bypassed with such casts. If anything you're casting 'to' requires potentially more restrictive alignment than anything you're casting 'from', you inviting a bus-error. Heed the initial warning. intermediate pointer-to-void simply masks the problem (as it does for most problems).

You want to know what byte is going "where" when you build that uin32_t.

uint8_t a[4] = {0x01, 0x23, 0x45, 0x67};
uint32_t b = ((uint32_t)a[0] << 24) |
             ((uint32_t)a[1] << 16) |
             ((uint32_t)a[2] << 8) |
             (uint32_t)a[3];

This will always put the a[0] byte in the high-byte of the target 32bit unsigned, a[1] in the next byte, etc, regardless of endian'ness. b will always be 0x01234567.

OTHER TIPS

I'd say the second example is also unsafe - it's just the case there that the compiler is not smart enough to find out that p and a actually point to the same (1-byte-aligned) location, and since void * cannot be aligned (by definition - what would be sizeof(void)?), it doesn't issue a warning.

In both cases you are accessing your array elements (type uint8_t) by another type (uint32_t) which is not the signed variant of the original type nor is a character type.

C says you have to access an object by its own type or a signed variant or by a character type otherwise you are violating aliasing rules.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top