2

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)?

Daniel
  • 403
  • 2
  • 15
  • 1
    Note that accessing `buffer` via a pointer to `MyData` will violate "strict aliasing rule" and it will invoke *UB* – tstanisl Jun 02 '22 at 10:21
  • I don't have MISRA at hand, but this sort of conversions can be usually safely done using `memcpy` instead of invoking UB like here. Is MISRA linter complaining about it, too? – alagner Jun 02 '22 at 10:22
  • @Gerhardh no way, I'm simply suggesting using `memcpy` to serialize/deserialize the structure (by copying it into a buffer or the other way around). – alagner Jun 02 '22 at 10:26
  • @alagner OK. I have misinterpreted that. – Gerhardh Jun 02 '22 at 10:27
  • @Gerhardh no problem. Just to make it clear: `memcpy(&destStruct, buffer, sizeof(struct DestStructType);` or sth in that manner. – alagner Jun 02 '22 at 10:29
  • @alagner Unfortunately that requires copying the whole struct and does not solve the underlying problem. It would not allow passing an address to a function with a more generic interface as you must use the exact type for each pointer. – Gerhardh Jun 02 '22 at 10:34
  • You should get rid of the non-standard type `uint8` in favour of C standard `uint8_t` from `stdint.h`. – Lundin Jun 02 '22 at 10:40
  • @Gerhardh it might depend on optimizations being set one way or another. But granted, for most MISRA cases I'm aware they are off. – alagner Jun 02 '22 at 10:40
  • 1
    @alagner Using memcpy is fine for MISRA compliance. – Lundin Jun 02 '22 at 10:53

4 Answers4

1

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;
}
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • The software is in this case following a dynamic-allocation concept. Meaning the structure data only exists inside the buffer array. There is no structure data outside that could be copied into the buffer. Maybe the buffer itself could be placed at an aligned memory address? – Daniel Jun 02 '22 at 11:27
  • @Daniel To solve misalignment and strict aliasing both, you could simply reserve a chunk of memory in RAM with no declared variables at all inside it. Have your dynamic concept assign pointers into this chunk without actually accessing the memory cells. Then you can regard this memory are as having no declared type, in which case strict aliasing doesn't apply until something is written down there. – Lundin Jun 02 '22 at 11:39
  • Since this code will run on various platforms with no real dynamic allocation allowed, the idea was to create this fixed size statically allocated array. I think by explicitly adjusting the alignment of all objects inside the buffer to the individual platforms pointer size boundary I could ignore the misra warning because then it would be safe to say that no alignment can happen right? – Daniel Jun 02 '22 at 11:55
  • @Daniel Again, alignment is only half of the problem. If you are just looking for a way to dynamically assign memory to various parts of the program without reclaiming it, you could use a simple memory pool as shown [here](https://stackoverflow.com/a/70667901/584518), though ditch the `mempool` array declaration in favour of a pointer pointing into a reserved memory area which the compiler knows nothing about. Then it is safe as far as strict aliasing is concerned, and you could implement the memory pool to never give out chunks which aren't evenly divisible by your alignment. – Lundin Jun 02 '22 at 12:00
  • In fact this mempool is used exactly the same way as my buffer array. Since allocating users object size is unknown my allocation function inserts a number of padding bytes for alignment, reserves a block of memory starting at that address and returns the pointer to that aligned, allocated memory block. But at the beginning I reinterpret the start (+padding) of the buffer/mempool as first empty allocation block and this is where I do the cast and get the Misra warning. – Daniel Jun 02 '22 at 12:13
  • @Daniel I added some example code to the answer, which might be a feasible work-around for you. The MISRA rule of no integer/pointer conversions can't be used in embedded systems anyway. – Lundin Jun 02 '22 at 12:54
  • To the result variable in your example also the _Alignof value must be added right? When you say the rule can't be used in embedded systems: How would you justify that? Is it because in systems with very limited resources it is common to use some of the less safe tricks? – Daniel Jun 02 '22 at 13:10
  • "To the result variable in your example also the _Alignof value must be added right?" I don't understand what you mean. As this is written, `result` should always give an aligned address. – Lundin Jun 02 '22 at 13:12
  • @Daniel "When you say the rule can't be used in embedded systems: How would you justify that? Is it because in systems with very limited resources it is common to use some of the less safe tricks?" With a company/project wide deviation in your MISRA C documentation. One doesn't need formal deviations for advisory rules but you should document why the rule doesn't make sense. The justification is easy: all real-world computers use absolute addresses and it's impossible to implement basic things like register maps while not doing integer/pointer conversions. – Lundin Jun 02 '22 at 13:14
  • (The rationale for the rule forbidding integer/pointer conversion is misalignment... so they mean well, but you can't follow the rule in practice. I think I once wrote a deviation against 11.4 like "I have to run my C code on a microcontroller so I can't follow this rule". As in, it is impossible to do any form of hardware-related programming without using absolute addresses.) – Lundin Jun 02 '22 at 13:22
1

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

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Using union means you get in trouble with other MISRA rules though. But they are lightweight advisory rules, unlike the rule 11.3 here which is Required. – Lundin Jun 02 '22 at 10:55
  • @Lundin OK - IAC, for a good alternative, best if OP posts true code rather than only a description of code. – chux - Reinstate Monica Jun 02 '22 at 11:00
0

The rationale for this Rule is actually the same for restricting the use of unions - there are many pitfalls for the unwary.

  • Alignment, as discussed, is probably the primary issue
  • Strict aliasing, again as mentioned
  • Padding within the structure, is another

If 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

Andrew
  • 2,046
  • 1
  • 24
  • 37
-1

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.

PhilMasteG
  • 3,095
  • 1
  • 20
  • 27
  • 1
    You might take a look at *Rule 11.5 A conversion should not be performed from pointer to void into pointer to object* – Gerhardh Jun 02 '22 at 10:29
  • I just meant that the C standard is giving you this guarantee, not that it sits well with MISRA. :) – PhilMasteG Jun 02 '22 at 10:34
  • OK. MISRA wise it's just some kind of "chose your pain". :) – Gerhardh Jun 02 '22 at 10:35
  • 4
    "You can probably circumvent the error through first casting it to a void* pointer." No you can't, it would still be undefined behavior in case you cast to a struct pointer etc then dereference. And that's not related to MISRA as such. Simply avoid writing programs with undefined behavior. MISRA is just telling you "bug here go fix it", it's a perfectly sensible warning. – Lundin Jun 02 '22 at 10:41
  • Again, never said it was a good idea. Maybe circumvent was the wrong word - make it "silence a correct warning about the bug in your code". – PhilMasteG Jun 02 '22 at 10:47
  • Regardless, it is explicitly undefined behavior, not just implementation-defined. (Well, it's implementation-defined if the system cares about alignment or not. If it does, then it's undefined behavior for 2 reasons and not just strict aliasing.) – Lundin Jun 02 '22 at 10:50