Reputation: 433
When I have a user defined type like the following:
typedef struct MyData_t {
uint16_t val;
...
} MyData;
And a simple array that I want to use to store different types of structures in:
uint8_t buffer[];
And I then want to create a structure pointer that uses the array to store the data of that structure:
MyData* freelist = (MyData*) buffer;
Then I get the MISRA 2012 Error:
Note 9087: cast performed between a pointer to object type and a pointer to a different object type [MISRA 2012 Rule 11.3, required]
This rule says because of possible alignment issues it is never safe to cast pointers between different types of objects. My question is: How common is it in an embedded environment for compilers to cause any issues in this case? And how could I prevent these issues when forced to keep the implementation concept (about the buffer array that stores different types of objects)?
Upvotes: 2
Views: 1056
Reputation: 2312
The rationale for this Rule is actually the same for restricting the use of union
s - there are many pitfalls for the unwary.
struct
ure, is anotherIf you take appropriate steps to (a) ensure alignment and (b) ensure the packing/unpacking in to and out of the structure is correct, and (c) ensuring you do not violate the strict aliasing considerations, you could probably get away with it.
You could, of course, disapply R.18.1 and use a union
of myData_t myData
and uint8_t data[]
...
But frankly, you'd probably be better off explicitly unpacking the data field by field.
See profile for affiliations
Upvotes: 0
Reputation: 153447
How common is it in an embedded environment for compilers to cause any issues in this case?
Common enough as it fails to meet alignment needs. E. g. buffer[]
may exist on an odd address and access to uint16_t
needs an even one. Result: bus violation. Any casting will not help.
how could I prevent these issues
Use a union
of uint8_t[]
and struct MyData_t
to align and avoid aliasing issues too.
Various ways to insure uint8_t buffer[]
is aligned well. Example:
#include <stddef.h>
#define BUF_N 100
union {
uint8_t buffer[BUF_N];
max_align_t a; // Or any wide type like complex long double
} u;
And use u.buffer
instead of buffer
.
Also research _Alignas
Upvotes: 1
Reputation: 213721
If you dereference freelist
then you invoke undefined behavior. Both because of possible alignment issues as well as strict aliasing. It's a bug, MISRA or no MISRA. The easiest fix is to use memcpy
instead.
How common is it in an embedded environment for compilers to cause any issues in this case?
In case of alignment, it depends on the hardware. Some architectures like MIPS are very picky with alignment, others like generic 8-bitter MCUs couldn't care less.
As for strict aliasing bugs, it was common for the gcc compiler to go haywire upon strict aliasing violations at the time it started to gain popularity in embedded systems, somewhere around the beginning of the ARM hype year 2008-2010 somewhere (gcc versions around 3.x something?). Modern gcc versions have less strict aliasing mis-optimizations. Still always compile with -fno-strict-aliasing
when using gcc, since the compiler is instable and generally dangerous to use when strict aliasing is allowed.
As for the regular embedded systems compilers, they are usually not as stupid as to abuse strict aliasing optimizations since they want to sell their compilers.
Notably, the other way around - going from struct to character pointer - is fine. MISRA C:2012 11.3 then lists the following explicit exception:
Exception
It is permitted to convert a pointer to object type into a pointer to one of the object types char, signed char or unsigned char.
EDIT
If it's ok to break a few advisory MISRA rules like casting between integers and pointers, then perhaps something like the example below could be an option. No pointer conversions, no pointer arithmetic, no strict aliasing problems. You'll have to cast the integer into a struct pointer type on the caller side, which violates advisory rules. You have to set aside an aligned chunk of data at the address of mempool_addr
with size mempool_maxsize
in your linker script.
#include <stdint.h>
#include <stddef.h>
#define mempool_maxsize 1024u
#define mempool_addr 0x10000u
static size_t mempool_size=0u;
uintptr_t static_alloc (size_t size)
{
uintptr_t result;
if(mempool_size + size > mempool_maxsize)
{
return 0;
}
if((size % _Alignof(int)) != 0)
{
size += _Alignof(int) - (size % _Alignof(int));
}
result = mempool_addr + mempool_size;
mempool_size += size;
return result;
}
Upvotes: 1
Reputation: 3185
You can probably circumvent the error through first casting it to a void*
pointer.
That being said, it is still implementation defined behaviour and therefore goes against MISRA guidelines. You are only guaranteed validity as per the standard if you cast to void*
and back to the exact same type.
However, there may often be cases in embedded systems where this is needed, eg. to access specific memory areas. I have had some at least. In these cases you'd need to have this use signed-off by management as per MISRA.
Upvotes: -1