5

I expected this to print 11112222deadbeef (it does at -O0), but at -O3 it prints 1111222233334444.

https://godbolt.org/z/MfeEM8fqP

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

__attribute__((noinline))
void MyFun(void *p)
{ 
    *(uint32_t*)p = 0xDEADBEEF;
}

int main(void){
    uint64_t x = 0x1111222233334444ULL;
    MyFun(&x);
    printf("%016llx\n", (unsigned long long)x);
}

Searching found a compiler option -fno-strict-aliasing which basically states:

With -fno-strict-aliasing, you are advising the compiler that you might have written some incorrect code, and to please be defensive with the optimizations it performs regarding aliasing.

Applying that option -O3 -fno-strict-aliasing does output the expected result 11112222deadbeef. Example

What's the correct, portable way to write 0xDEADBEEF into the low-addressed 4 bytes of a uint64_t so it does not get optimized away or is it safe to add -fno-strict-aliasing as a temporary stop-gap?

9
  • 8
    You are in the zone of undefined behavior. The keywords: strict aliasing rule. Commented Oct 11 at 14:47
  • The portable way is memcpy(). Commented Oct 11 at 14:51
  • 2
    "I expected this to print ..." - it's problematic to expect anything when UB is invoked. Commented Oct 11 at 14:56
  • 3
    Tip: Instead of printf( "%016llx\n", ( unsigned long long )x ), you can use printf( "%016" PRIx64 "\n", x );. No casting. You need to include inttypes.h, but you can omit stdint.h if you do. Commented Oct 11 at 15:37
  • 6
    @vengy "only the low 32 bits" --> do you want the lowest significant bits or the lowest addressed bytes? Commented Oct 11 at 16:55

1 Answer 1

9

The correct way to handle this kind of aliasing situation is to use memcpy:

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

void MyFun(void *p) {
    uint32_t val = 0xDEADBEEF;
    memcpy(p, &val, sizeof(val));
}

int main(void) {
    uint64_t x = 0x1111222233334444ULL;
    MyFun(&x);
    printf("%016" PRIu64 "\n", x);
}

Compilers generate optimal code for this unless optimisations are disabled, as can be verified with Godbolt's Compiler Explorer

Sign up to request clarification or add additional context in comments.

3 Comments

PRIx64 would be more revealing, and consistent with the question.
That is interesting. Per draft n3220, 6.5.1/7, an object can be accessed through an lvalue expression of a compatible type or of character type; but memcpy is declared with void* parameters. We must assume that its implementation copies to the destination through a char pointer, but that may factually not be the case at all (for example, it may be intrinsic machine code). (Of course this is exactly what memcpy is meant for, and before we had void, it was taking char* parameters, being in string.h. But still. I cannot see an exception for memcpy in the draft.)
C 2024 7.26.1 (about <string.h>) says “For all functions in this subclause, each character shall be interpreted as if it had the type unsigned char (and therefore every possible object representation is valid and has a different value)” and 7.26.2.1 (about memcpy) says “The memcpy function copies n characters from the object pointed to by s2 into the object pointed to by s1.” The wording could be better, but it is clear memcpy is intended to be able to copy bytes in arbitrary objects as if character lvalues were used, which is permitted for aliasing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.