Please take a look at this code:
#include <stdio.h>
#include <stdint.h>
int main()
{
uint8_t run = 1;
/* Define variable of interest and set to random value */
uint32_t dwTime = 0xdeadc0de;
/* Define uint16_t pointer */
uint16_t* tmpU16;
/* Assign least 16 bits from dwTime to tmpU16 by downcasting from uint32_t to uint16_t */
tmpU16 = (uint16_t*)&dwTime;
/* Print content of tmpU16 */
fprintf(stderr, "TEST: x\n", *tmpU16); /* Will print "TEST: c0de" here */
/* This loop will run exactly once */
while (run)
{
/* Print content of tmpU16 AGAIN (content of tmpU16 should not have changed here!) */
/* Will print "TEST: 0000" here when optimization is enabled */
/* Will print "TEST: c0de" here when optimization is disabled */
fprintf(stderr, "TEST: x\n", *tmpU16);
/* Increment tmpU16 pointer by 1 (yes, this does not make sense but it should not do any harm
either, unless something gets written to its address AFTER incrementing */
tmpU16 ;
run--;
}
return 0;
}
When compiling this code with GCC 11 and optimization enabled (for example -O2
) it will produce following output:
TEST: c0de
TEST: 0000
When this code is compiled without optimization (-O0
) it will produce:
TEST: c0de
TEST: c0de
I assumed after the first fprintf
is executed, the next fprintf
inside the while loop will be executed immediately. In between these two fprintf
s nothing should happen, hence content of tmpU16
stay unchanged.
However, when I comment out the tmpU16
pointer incrementation, it produces correct output with optimization.
Why is this, what's happening here?
CodePudding user response:
On this line:
tmpU16 = (uint16_t*)&dwTime;
You're committing a strict aliasing violation by treating an object of one type as if it were another incompatible type.
Section 6.5p7 of the C standard lists the conditions in which aliasing may occur:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88)
- a type compatible with the effective type of the object,
- 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,
- an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
- a character type.
88 ) The intent of this list is to specify those circumstances in which an object may or may not be aliased
Using a pointer to a uint16_t
to access a uint32_t
does not fall under any of these categories. Such a violation triggers undefined behavior.
With gcc in particular, enforcement of strict aliasing rules isn't enabled unless you either use -O2
or greater, or explicitly use -fstrict-aliasing
. By enforcing this rule, the compiler is able to make optimizations it wouldn't be able to make otherwise.