问题
I have read this question: How does casting uint8* to uint32* work? but I am unsure of the answer given.
I'm newbie embedded C programmer working on an project that uses GCC and I've been refactoring chunks of code to reduce the memory usage. An example is where I changed the data types of some variables from uint32_t
to smaller sized types:
uint8_t colour;
uint16_t count;
uint16_t pixel;
func((uint32_t*)&colour);
func((uint32_t*)&count);
func((uint32_t*)&pixel);
Where func(uint32_t* ptr)
modifies the value passed in to data it receives on a slave port. I'm coming across a problem where the above code does not behave correctly when -O1
, -O2
, -O3
or -Os
optimisation is enabled. E.g when values 1
5
1
are received on the comms port and the function respectively, the value that is set for the variables are 0
0
1
. When no optimisation is enabled, the values are set correctly.
The code behaves correctly if I change the data types back to uint32_t
. I don't understand why I don't get any warnings from the compiler (I have extra warnings turned on). Is the reason this is happening to do with the endianess/allignment?
What are the pitfalls of upcasting from a uint8_t
pointer to a uint32_t
pointer?
回答1:
TLDR
Doing something like passing the address of a uint8_t
to something expecting the address of a uint32_t
can result in corrupted memory, unknown results, subtle bugs, and code that just plain blows up.
DETAILS
First, if the function is declared as
void func( uint32_t * arg );
and modifies the data arg
points to, passing it the address of a uint8_t
or a uint16_t
will lead to undefined behavior and likely data corruption - if it runs at all (keep reading...). The function will modify data that is not actually part of the object the pointer passed to the function refers to.
The function expected to have access to the four bytes of uint32_t
but you gave it the address of only a single uint8_t
bytes. Where do the other three bytes go? They likely stomp on something else.
And even if the function only reads the memory and doesn't modify it, you don't know what's in the memory not in the actual object, so the function may behave unpredictably. And the read might not even work at all (keep reading again...).
Additionally, casting the address of a uint8_t
is a strict aliasing violation. See What is the strict aliasing rule?. To summarize that, in C you can not safely refer to an object as something that it is not, with the only exception being that you can refer to any object as if it were composed of the proper number of [signed|unsigned] char
bytes.
But casting a uint8_t
address to a uint32 *
means you are trying to access a set of four unsigned char
values (assuming uint8_t
is actually unsigned char
, which is almost certainly true nowadays) as a single uint32_t
object, and that's a strict aliasing violation, undefined behavior, and not safe.
The symptoms you see from violating the strict aliasing rule can be subtle and really hard to find and fix. See gcc, strict-aliasing, and horror stories for some, well, horror stories.
Additionally, if you refer to an object as something it's not, you can run afoul of 6.3.2.3 Pointers, paragraph 7 of the C (C11) standard:
A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.
It's not safe even on x86 no matter what someone might tell you about x86-based systems.
If you ever hear someone say, "Well, it works so that's all wrong.", well, they're very, very wrong.
They just haven't observed it failing.
Yet.
来源:https://stackoverflow.com/questions/60890346/c-casting-uint8-t-to-uint32-t-behaviour