1

Misra standard demand a single point of exit for a function, but I have the following "conversion" code

typedef enum { CASE_A, CASE_B, CASE_C } my_enum_t;

int my_conv_funct(my_enum_t value)
{
    switch(value)
    {
         case CASE_A:
             return 0;
         case CASE_B:
             return 1;
         case CASE_C:
             return 2;
         default:
             break;
    }
    log_error("ERROR!!!!!");
    assert(1==0);
}

Is this valid? I need to convert it to a single return function? And what is the best way of handling the default case?

this creates an unreachable code in theory (the error is to warn in case one add a value in the enum and not add a corresponding case)

This is an embedded system btw having those asserts create issues?

Thanks, Nick

EDITED:

the default case should be never called if there are no errors (for example a programmer add another value in the enum and doesn't add a corresponding case

another options would be to remove the default at all but that violates another misra rule

typedef enum { CASE_A, CASE_B, CASE_C } my_enum_t;

int my_conv_funct(my_enum_t value)
{
    switch(value)
    {
         case CASE_A:
             return 0;
         case CASE_B:
             return 1;
         case CASE_C:
             return 2;
    }
    //should never reach this line
    assert(1==0);
}

This will generate a warning if I compile and don't specify all the cases in the enum (I think)

  • You create a "int retVal;" which you return at the end. – Fredrik Oct 28 '21 at 21:30
  • 3
    And if you are on an embedded system what do you expect to happen with the assert statement? Maybe you want the MCU to restart? Then you could do that. – Fredrik Oct 28 '21 at 21:33
  • 4
    The code is not well formed in general. Because `assert` is a macro that may be preprocessed away resulting in a function that has code paths that don't return a valid value. Best to define an error return code and use that for the default path (in addition to the assert). – kaylum Oct 28 '21 at 21:38
  • 1
    This is excellent code. But remember: misra was invented to condition/punish sub-standard programmers. – wildplasser Oct 28 '21 at 22:14
  • If you are on an embedded system you need to have code that handles assertions. The sort of thing you might want to do is to store relevant debug information in a section of memory that won't be re-initialised on reboot, and then force a reboot. Some useful guidance is here https://barrgroup.com/Embedded-Systems/How-To/Design-by-Contract-for-Embedded-Software. Without such special code, what will your `assert` do? – DavidHoadley Oct 29 '21 at 01:26
  • Regarding assert & error handling: in case it makes sense to do so, you should design the function so that it returns an error code to the caller. Ideally have a centralized error handler which can evaluate various errors and take action as needed. Fragmented error handling all over the program is a pain to maintain. Plus in many cases there's no sensible way to continue - hanging and waiting for the watchdog might be the correct error handling in simpler applications. – Lundin Oct 29 '21 at 12:45
  • What I want is a clear error (possibly dunring compilation) if someone add a new value in the enum and forget to add the corresponding CASE. The 3 cases are the only posisble and the compiler checks if the value passed is in the enum -> and it throws an error. What it doesn't check is that if I add another case that that case is handled in the switch. another option would have been to avoid the default at all -> but that violates another misra rules -> switch cases should have a default. the -1 as error doesn't make sense in my case – Nicola Lunghi Nov 01 '21 at 13:03
  • as that case should never be possible -> and the function calling this does'n know how to handle it . the "default" denotes a programmers error – Nicola Lunghi Nov 01 '21 at 13:05

5 Answers5

2

Very simply:

int my_conv_funct(my_enum_t value)
{
    int result = -1;
    switch(value)
    {
         case CASE_A:
             result = 0;
             break;
         case CASE_B:
             result = 1;
             break;
         case CASE_C:
             result = 2;
             break;
         default:
             break;
    }
    if(result == -1)
    {
         log_error("ERROR!!!!!");
         assert(1==0);
    }
    return result;
}

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Why not put the `log_error("ERROR!!!!!");` inside the `switch` statement at the `default` clause? – chqrlie Oct 29 '21 at 07:14
  • Hi ok no I probably didn't explain myself. My problem is that I don't want to return a value like -1 to check, I want a **clear error** (possibly compilation error) if the programmer add a new value in the enum and doesn't add the corresponding case in the switch. the three (or more) options are the only one possible so the function should never reach the log error line. THis function is called internally, there are no user input so it can't return anything elese other that the cases defined. – Nicola Lunghi Nov 01 '21 at 12:56
  • @NicolaLunghi it is not possible to detect it compile time. I think you try to resolve X-Y problem. – 0___________ Nov 01 '21 at 13:10
2

Is this valid?

It does not comply with the MISRA rule you described.

I need to convert it to a single return function?

To comply with the MISRA rule, yes.

And what is the best way of handling the default case?

We cannot judge what is "best" for your particular circumstances and use.

This is an embedded system btw having those asserts create issues?

The idea of an assertion is that it helps you find programming errors during development, but (in principle) it gets disabled via build options in code that is intended to be used in production. If that model is followed then the assertion itself probably does not create an issue, but the fact that the function does not return a value in the default case (if assertions are disabled) does. If the program must terminate in the event that the default case is exercised then it should call abort(), or some other function having that effect. Otherwise, it should return a sensible value in the default case.

I would probably write the function more like this:

int my_conv_funct(my_enum_t value)
{
    switch(value)
    {
         case CASE_A:
         case CASE_B:
         case CASE_C:
             break;
         default:
             log_error("ERROR!!!!!");
             assert(0);
             break;
    }
    return value;
}

There is now just one exit point from the function, and if it returns at all then it returns its argument (implicitly converted to type int).

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • I believe the cases are trivial only for the sake of simplicity of the example. Trivializing even more does not provide any value to the answer – 0___________ Oct 28 '21 at 21:46
  • You're assuming `CASE_A` has the integer value of `0`. That might not be true. – Andrew Henle Oct 28 '21 at 21:48
  • 1
    @AndrewHenle, I am assuming only that the definition of `my_enum_t` given in the question is the same one that applies to the function argument in the question. In that event, the value of `CASE_A` is required to be 0. – John Bollinger Oct 28 '21 at 21:49
  • @JohnBollinger Fair enough. I think I may have grown too skeptical/cynical... – Andrew Henle Oct 28 '21 at 21:51
  • 1
    I am also interpreting the original function implementation in light of its name and argument type definition as performing a *type* conversion (with range checking) rather than a value conversion. I think the implementation presented in this answer is a particularly clear expression of that purpose. – John Bollinger Oct 28 '21 at 22:03
  • Additionally, although the function could return an error value in the event of an unexpected input, if I have interpreted the purpose of the function correctly then I estimate that returning the input value in that case is less likely to cause failures in production. Especially so since the existing code calling the original version of this function cannot presently be usefully checking for an error code. – John Bollinger Oct 28 '21 at 22:16
  • In case the enum constants correspond to values 0, 1 etc then the whole function is questionable and could have been replaced with a one-liner value/range check. – Lundin Oct 29 '21 at 12:48
  • It depends on the desired usage model, @Lundin. Structuring it as a function allows the range check to be wrapped around accesses inside an expression, and it especially factors out the range check to a single, reusable unit. I would consider the latter to be worthwhile all by itself, with the function returning `void`. This is in fact one of my considerations in interpreting the function implementation as I have done. – John Bollinger Oct 29 '21 at 13:09
2

First of all please check this answer: Best practice for compute the function return value. The MISRA-C rule is advisory and I recommend to make a permanent deviation against it. Personally I replace it with a rule such as:

"Multiple return statements in a function should be avoided unless they make the code more readable/maintainable."

The rationale to avoid returning from multiple places inside nested, complex code is sound, but far less so in clean and readable functions.

In your specific case though, I would perhaps have rewritten the function like this (MISRA compliant without ignoring the rule):

uint32_t my_conv_funct (my_enum_t value)
{
  uint32_t result;

  switch(value)
  {
    case CASE_A: result = 0; break;
    case CASE_B: result = 1; break;
    case CASE_C: result = 2; break;
    default:
    {
      // error handling here
    }
  }
  return result;
}

Alternatively (deviating from the rule):

uint32_t my_conv_funct (my_enum_t value)
{
  static const uint32_t lut[] = { CASE_A, CASE_B, CASE_C };

  for(size_t i=0; i<sizeof lut/sizeof *lut; i++)
  {
    if(lut[i] == value)
    {
      return i;
    }
  }

  /* error handling */

  return some_error_code;
}

This assuming that the amount of items isn't large, in which case a binary search might be more inefficient.

This in turn assuming that the enum constants don't correspond to 0, 1 and 2 in which case the whole function is nonsense.

Lundin
  • 195,001
  • 40
  • 254
  • 396
0

The rationale for the MISRA C "single exit" Rule is because it is a requirement for the functional safety standards (eg IEC 61508 and ISO 26262).

It is also Advisory so can be disapplied if the circumstances so require.

My personal view is that a switch statement seldom needs multiple exits - it is easy to structure to avoid them... however there are situations (eg parameter validation) where it may make sense.

--

As an aside, use of assert() is non-compliant with MISRA C as it expands to abort() which is in breach of Rule 21.8 - this is also a very undesirable behaviour in an embedded system (and questionable in a hosted environment)...

-- See profile for affiliation.

Andrew
  • 2,046
  • 1
  • 24
  • 37
  • Regardless of what I personally think of this specific rule, this is a strange circular dependency. IEC 61508 & friends require MISRA-C ("safe subset"). MISRA-C in turn apparently requires IEC 61508 in order to (supposedly) make sense of some rules. This would in turn mean that MISRA-C doesn't make sense in "SIL 0" applications such as regular embedded systems. That's an unfortunate dependency, because at least I believe that MISRA-C does a whole lot of good to general embedded systems too, and not just safety-critical. – Lundin Nov 01 '21 at 11:24
  • So maybe MISRA-C should be updated to contain a rationale/valid sources of it's own. For example _Hatton - Safer C_ which is frequently stated as a source elsewhere in the guide lines and a far better one than this Yourdon book from the 1970s. 6.1.3 of _Safer C_ addresses multiple returns briefly and basically comes to the conclusion that there is no problem with having them _unless_ they increase cyclomatic complexity. That's pretty close to my own re-definition of this rule too - avoid multiple returns _unless_ they make the code more readable/less complex. – Lundin Nov 01 '21 at 11:37
  • Disagree that its cirucular... 61508 *requires* single entry/exit... MISRA provides an *advisory* rule for this. Likewise, I regular discuss this Rule in presentations, suggesting you need to engage brain. The problem is too many box-tickers that won't try and *understand* what's going on. – Andrew Nov 01 '21 at 11:46
  • IEC 61508 has "recommended" or "highly recommended", so it's actually rather vague too, even though it might be hard to convince the assessor of deviations from "highly recommended". As for the MISRA-C rule, the problem is rather that when you _do_ engage your brain, you find out that there is no valid rationale for the rule. Advisory or not, the rules should preferably be based on scientific evidence or well-known engineering practice. – Lundin Nov 01 '21 at 12:56
  • Once again, we will have to agree to disagree :-) – Andrew Nov 01 '21 at 13:37
0

The updated question has now been extended to include:

the default case should be never called if there are no errors (for example a programmer add another value in the enum and doesn't add a corresponding case

another options would be to remove the default at all but that violates another misra rule

I disagree...

The default is there as an error trapping mechanism - especially in real-time/embedded systems, data values may change unexpectedly (cosmic rays anyone) and it is a brave real-world engineer that does not protect against the unexpected.

How often has a default or else clause containing a comment /* Can never reach here */ actually been reached?

Andrew
  • 2,046
  • 1
  • 24
  • 37