8

Misra says to ban all unions. I also know that deviations are allowed as long as they are discussed and documented thoroughly.

We have a microcontroller and an external eeprom to store statistical data (event/error logging, parameter settings and whatnot).

The eventlog consists of around 80+ event counters some being 8, 16 and 32 bits (all unsigned). The parameter storage consists of around 200 parameters, also mixed with 8, 16 and 32 bit values (unsigned).

We are rewriting all code to be MISRA compliant and as these values were previously defined is as follows:

typedef struct
{
  U16BIT eventLogVar1;
  U32BIT eventLogVar2;
  U8BIT  eventLogVar3;
  U8BIT  eventLogVar4;
  U32BIT eventLogVar5;
} EVENT_LOG;

typedef union
{
  EVENT_LOG log;
  U8BIT     array[sizeof(EVENT_LOG)];
} ELOG;

ELOG log;

Now this is not really MISRA compliant. Same goes for the parameter log. However this is the most easiest way to read and write from the eeprom, because I just have to read/write through the array to read/write from the eeprom.

We have a few other rules that we simply are not allowed to break. No global (extern) variables (through header files). All local variables, if needed should only be accessed through get/set functions.

That implies that if we need to fully write out all these parameters that these each should get their own get/set functions for altering them throughout the application.

On of the solutions that I've thought on was the following:

#ifdef EITHER
enum
{
    eventLogVar1 = 0; /* 00 */
    pad01;            /* 01 */
    eventLogVar2;     /* 02 */
    pad03;            /* 03 */
    pad04;            /* 04 */
    pad05;            /* 05 */
    eventLogVar3;     /* 06 */
    eventLogVar4;     /* 07 */
    eventLogVar5;     /* 08 */
    pad09;            /* 09 */
    pad10;            /* 10 */
    pad11;            /* 11 */
}
#else /* OR */
#define eventLogVar1 0 /* 2 bytes */
#define eventLogVar2 2 /* 4 bytes */
#define eventLogVar3 6 /* 1 byte  */
#define eventLogVar4 7 /* 1 byte  */
#define eventLogVar5 8 /* 4 bytes */
#endif
#define eventLogLastLength 4

U8BIT eventLog[eventLogVar5 + eventLogLastLength];

U8BIT getU8BIT(U8BIT index){}
U16BIT getU16BIT(U8BIT index){}
U32BIT getU32BIT(U8BIT index){}

void setU8BIT(U8BIT index, U8BIT val){}  
void setU16BIT(U8BIT index, U16BIT val){}
void setU32BIT(U8BIT index, U32BIT val){}

However, this poses a combersome refactoring if values are added or removed. It also means that values of type array can not be used (and there are a few) which can be altered by defines in length if more or less of a certain type of, e.g., sensors, are used.


What are your thoughts on this specific problem. Would I/we be better off documenting our deviation from the MISRA-standard in this specific case and only use this deviation at this specific place or are there better solutions to this problem?

Peter K.
  • 8,028
  • 4
  • 48
  • 73
Daan Timmer
  • 14,771
  • 6
  • 34
  • 66
  • 2
    Not using unions where hardware registers can be 1-8-16-32-bit addressable is a bit silly, but I suppose I should expect that from a standards committee Typical controller hardware registers are a prime example of where a union shuld be used, eg: a register that has to be read/written as 32-bits but is actually a nasty collection of bit fields, some useful, some don't care and some 'never write 1'. CAN controller registers, (something not unknown in automotive:), are a prime example. Soddin' committees.. – Martin James Aug 09 '12 at 15:14
  • @Martin: keep in mind that unions and bit fields are different things. And C bit fields are not a good thing to use to model register or file format bit fields - just last week I ran across some code that used bit fields to model MPEG headers, and what do you know: the x86 compiler laid them out differently than the ARM compiler. Whatever you might think about unions, avoid C bit fields. Use explicit shifts, masks and bit twiddling instead - even if you have to hide them behind macros. – Michael Burr Aug 13 '12 at 07:21
  • @Martin - we're looking for new mugs volunteers to join, so feel free to offer [R&DFC] – Andrew Nov 08 '12 at 07:34

3 Answers3

7

Your log union is exactly the kind of union you should be allowed to use, what it is doing is data packing, something MISRA explicitly states to be an acceptable deviation, so a deviation is what you should do. Pretty much everyone who uses MISRA deviates from this rule in this very manner. (It is a rather bad rule and it looks like it will get demoted to advisory or removed completely in the next MISRA version.)

But you will need to document:

  • whether padding bytes and alignment will be an issue.
  • whether endianess might be an issue, if the code needs to be portable.

To dodge the padding/alignment issue, you can write something like:

COMPILE_TIME_ASSERT( (sizeof(union_member1)+sizeof(union_member2)+...) ==
                     sizeof(union_type) );

where COMPILE_TIME_ASSERT is some macro that yields a compiler error if not passed a positive value. This ensures that no struct/union padding is present.


Further comments:

enum is a bad solution since it has a number of flaws of its own: a variable of the enum type has implementation-defined size, while an enumeration constant has type signed int. This will collide with the MISRA rules regarding implicit type conversions, you will be forced to add numerous typecasts.

All local variables, if needed should only be accessed through get/set functions.

They also need to be declared as static to reduce their scope. I noticed from your snippet that you don't do this. static is enforced by MISRA:2004 8.11.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I erroneously left them out apparently. All our file-scope variables are defined static. Except for in my example. We have indeed chosen to deviate with your exact same tips on what to document. Thanks for answering. – Daan Timmer Aug 10 '12 at 12:24
5

Unions are a very useful construct, when used properly.

They also have undefined behaviour when one tries to do clever things with them. It is to prevent the undefined behaviour that the MISRA guidelines are trying to prevent. But even the Rule (18.2 in MISRA C:2004) gives cases where unions are useful.

The example you give is one such useful situation, and is exactly the sort of situation that the MISRA deviation procedure is there for.


Disclaimer: I am a member of the MISRA C working group, but I am posting in a personal capacity. My views should not be taken as official MISRA policy.

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

What does MISRA say about memcpy? Instead of having a union, why not just use EVENT_LOG? When you serialize to/from EEPROM use a temporary array like this:

EVENT_LOG log;

U8BIT array[sizeof(EVENT_LOG)];
// populate array

memcpy(&log, array, sizeof(EVENT_LOG));

// do similar thing when writing to eeprom

The union mixes the data and the serialization format together, where a function might be more suited?

void EVENT_LOG_write_to_eeprom(const EVENT_LOG*);
void EVENT_LOG_read_from_eeprom(EVENT_LOG*);
Josh Petitt
  • 9,371
  • 12
  • 56
  • 104
  • Reasonable. I thought about it, but we are a bit short of memory and only halfway through rewriting the program. thus creating extra arrays is not a real option and we can't just throw away around 300 bytes of stackspace. – Daan Timmer Aug 09 '12 at 12:51
  • If you put the serialization behind an interface (i.e. the function calls), you could always change it to a long-hand read/write (i.e. access and serialize each member individually). This is assuming you will always read/write an EVENT_LOG as a single chunk. I think the "smell" here is exposing the union data type that was really for serialization. – Josh Petitt Aug 09 '12 at 13:21
  • 1
    There are all sorts of undefined behaviour associated with memcpy() - so use with caution! See Amendment 1 to MISRA C;2012 – Andrew Oct 13 '17 at 13:20