It's a problem I ran into in my work. Our embedded platform is a customized Android OS running on quad-core Cortex-A7.
Background: I'm receiving a set of parameters from outside my process, and it's fed to me through a callback that looks something like this:
void some_callback(const void * pdata, size_t n)
{
if (*(const uint8_t*)pdata == PARAM_FP32 && n >= 5)
{
pdata++;
my_variable = *(const float *)pdata; ///< Crashes the application
}
/// Other processing
}
Problem: When directly assigning the type-converted byte sequence to my variable (Note: I couldn't remember where this variable is stored - be it static? global? in stack? in heap?), application crashes.
I replaced the assignment with memcpy(), and it worked fine.
void some_callback(const void * pdata, size_t n)
{
if (*(const uint8_t*)pdata == PARAM_FP32 && n >= 5)
{
pdata++;
memcpy(&my_variable, pdata, sizeof(float)); ///< Worked fine
}
/// Other processing
}
I asked someone in the team and got the following reply: Direct assignment failed because of data alignment issues, the bytes to be assigned are (probably) not 4-byte aligned; assigning to a 4-byte aligned variable causes a bus error.
My questions:
There are multiple problems in the posted code:
void
pointer, which is supported as an extension by your compiler but not valid in Standard C.my_variable = *(const float *)pdata
which has undefined behavior and may lead to bugs with optimizing compilers.float
objects at unaligned addresses, which your compiler did not generate as it assumes a float *
to be properly aligned.As for your questions:
- Is my colleague correct? Is that the actual reason why assignment crashes?
Your colleague is correct, bad alignment is the most probably cause for the observed behavior.
- Why am I not experiencing this issue in MCU development - like with STM32/AVR?
Because these other targets happen to accept reading float
values from unaligned pointers (albeit with a small performance penalty). Note that some CPUs will not crash on unaligned pointers but will not read the proper float
value either. Undefined behavior is not always easy to detect.
- Is there another way of doing the data copy correctly, other than using
memcpy()
?
Using memcpy
to copy the data to the destination will ensure proper operation in all cases. Don't worry about performance: an optimizing compiler will generate the appropriate instruction(s) to copy the 4 bytes regardless of alignment, without a function call.
Here is a modified version:
void some_callback(const void *pdata, size_t n)
{
const uint8_t *p = pdata;
if (*p == PARAM_FP32 && n >= 1 + sizeof(float)) {
float my_variable;
memcpy(&my_variable, p + 1, sizeof(my_variable));
/// Further processing using my_variable
}
/// Other processing
}
Please also note another potential problem: the byte stream pointed to by pdata
contains a 4 byte sequence for a float
value after a PARAM_FP32
byte. You assume that these 4 bytes are in the native order for your CPU, which might not be the case if the stream was produced on a different architecture. The endianness used in byte streams exchanged between systems must be specified explicitly to allow producers and consumers to adjust the order if their native endianness (little endian or big endian) differs from the one specified for the stream.