1
votes

The C Standard mandates that values of unsigned types which rank below "int", and whose values are within the range of "int", are always promoted to "signed int" when used in expressions. Thus, given something like:

uint8_t a,b;

if ((a-b) > (uint8_t)1) ...

the subtraction and comparison must behave as though all values are converted to type signed int. I'm working on a MISRA-C 2004 project, however, with a compiler that's supposed to validate code to that standard, and it appears that MISRA's rules, at least in the 2004 version, are oblivious to such standard promotions.

Of the following expressions, which ones should be valid in MISRA-C 2004, MISRA-C 2012, both, or neither:

if ((a-b) > (uint8_t)1) ...          // Accepted by the TI compiler
if ((uint8_t)(a-b) > (uint8_t)1) ... // Accepted by the TI compiler
if ((a-b) > 1u) ...                  // Accepted by the TI compiler
if ((a-b) > 1) ...                   // Rejected by the TI compiler

If the TI compiler is accurately reflecting what is required by the MISRA-C 2004 rules, it would seem like the group that wrote those rules expected that C's promotion rules would be different from what C89 required, since the form rejected by the compiler has the same semantics as the first one, while the third form the compiler accepts looks superficially like the first one but has different corner-case behavior, and the second form has yet another different behavior.

Changing all occurrences of uint8_t to uint16_t or uint32_t does not seem to affect which forms are accepted or rejected, but would cause the first acceptable form to have semantics which on some compilers would match the rejected form (as it did with uint8_t), but on other compilers would match the other two accepted forms (which would then have the same semantics as each other).

Is the TI compiler correctly interpreting the requirements of MISRA-2004 in accepting code whose behavior would vary among implementations, and rejecting code whose behavior (in the uint8_t case) would be consistent on all conforming implementations? Have the rules changed in MISRA-C 2012 in a way that would affect the above expressions?

Also, if MISRA-C 2004 would require either pre-casting the operands to the expression or post-casting the result, but the compiler was erroneously accepting the first and third forms, which did neither, in what cases would such casts be required? Does either the 2004 or 2012 edition allow the casts to be omitted in the cases where the authors of C rationale would expect that signedness shouldn't matter (e.g. thus allowing:

uint8_t a,b,c,d;
a=b-c+d;

because even though the expression adds a signed value (b+c) to an object of unsigned type, the result is coerced in such a way that will treat as equivalent all results that are congruent mod 256)?

1
That sounds like a compiler problem, not a MISRA problem. As I understand it, there is no such thing as a "MISRA compiler".user2357112 supports Monica
MISRA C is a set of programming guidelines; it does not change how the actual C language behaves. I don't think MISRA C is actually relevant here. My guess is you're in one of the following situations: int is 8-bit, your compiler has a bug, or your real code has some crucial difference from your snippet.user2357112 supports Monica
In what sense is the compiler you're using a "MISRA compiler"? Do you mean that it's a C compiler that issues warnings for violations of MISRA guidelines? Does the generated code behave in a manner inconsistent with C standard semantics? And can you include the text of the diagnostic in the question?Keith Thompson
@supercat: MISRA isn't what's saying the left operand is unsigned. Your compiler is what's saying the left operand is unsigned. Your compiler may have a bug, or for all we know, it may be right. You still haven't told us what compiler you're using, what platform you're targeting, what compiler options you've set, or what the actual error message is, and you haven't posted a minimal reproducible example demonstrating the problem.user2357112 supports Monica
Which MISRA-C version? Which rule is the tool complaining about? MISRA-C:2004 and MISRA-C:2012 are somewhat different here.Lundin

1 Answers

3
votes

MISRA-C:2004 (old) and MISRA-C:2012 (current) are slightly different. The former had a concept called underlying type, which is the type an expression would have had if not for implicit type promotion (either by integer promotion or by usual arithmetic conversions).

This concept was refined in MISRA-C:2012 and replaced with essential type, which is rather a group of types between which implicit promotions are harmless. For example a conversion from signed char to signed int is not dangerous - both types belong to the essentially signed group.

Did the group that wrote MISRA expect that C's promotion rules would be different from what C89 required, with the groups who wrote subsequent revisions then being unwilling to recognize the Standard-mandated behavior?

No, they expect the promotion rules to be as mandated by C89 and later, but recognize how dangerous those promotion rules are.

I could understand (and actually applaud) a MISRA rule that required that arithmetic on unsigned types must immediately cast the result back to the same unsigned type in cases where the result would be used in a way where signedness or upper bits might matter [as with the above comparison].

This is indeed what both MISRA:s require, unless the implicit conversion happens to a type of same signedness. In your case you go from uint8_t to int which is not ok. To be MISRA-C:2004 compliant you can either cast back to the "underlying type" (uint8_t)(a-b), or cast in advance to eliminate the implicit conversion entirely, for example: (uint32_t)a - (uint32_t)b.

In addition, you aren't allowed to > compare a signed operand with an unsigned. Meaning that you have two options to make the whole expression MISRA compliant:

if ( (uint8_t)(a-b) > 1u)

or

if ( (uint32_t)a - (uint32_t)b > 1u)

(You may have to add additional parenthesis to sate other MISRA rules)

Is the intention of the rule that ((a-b) > 1) be considered unacceptable but ((a-b) > 1u) would be acceptable

Neither is acceptable, since a-b gives an implicit unsigned to signed conversion and it fails on that.

Otherwise, (uint8_t)(a-b) > 1 would still not be ok, but (uint8_t)(a-b) > 1u would be fine.


EDIT after question update.

Side note: In addition to not allowing conversion to larger integer types of different signedness, MISRA-C:2004 10.1 does not allow implicit conversion to a different underlying type of operands in "complex expressions" either. Where "complex expression" is a MISRA-C:2004 term for everything that's not a constant expression, lvalue or return value of a function.

So there's actually multiple violations of 10.1 here.

Now if I get this right, then this is what the tool should say:

  1. if ((a-b) > (uint8_t)1). Not compliant. Integer promotion changes signedness of all operands to - and > both. Complex expression with promotion to different underlying type.

  2. if ((uint8_t)(a-b) > (uint8_t)1). Not compliant. Integer promotion changes signedness of operands to >. Complex expression with promotion to different underlying type. The left operand's sub-expression is compliant in itself, but not as part of a larger expression.

  3. if ((a-b) > 1u). Not compliant. Integer promotion changes signedness of operands to -. Complex expression with promotion to different underlying type.

  4. if ((a-b) > 1). Not compliant. Integer promotion changes signedness of operands to - and > both. Complex expression with promotion to different underlying type.

(Ironically, my old, crappy MISRA-C:2004 checker (LDRA) merrily accepts all 4 expressions. But that's definitely a broken tool.)

Now the reason why if ( (uint8_t)(a-b) > 1u) is compliant, is because there is a cast back to the underlying type and then from there the usual arithmetic conversions come in to save the day. (uint8_t)(a-b) will normally be integer promoted to int but usual arithmetic ensures it ends up unsigned int, which is the same signedness as the underlying type, uint8_t.

My conclusion is that the "TI checker" in the question is giving the wrong results.

As for the rationale behind these rules in MISRA, it is merely to ensure that there are never any unintended implicit type promotions. Programmers who are unaware of implicit promotions write bugs and should be educated. Programmers who write code that intentionally and silently depends on implicit promotions write unmaintainable code rot and should be fired.

For example an expression such as a=b-c-d; (I altered it slightly), signedness could matter plenty. Consider b=0, c=255, d=2. Was the intention then to exploit uint8_t unsigned wrap-around, or to perform the calculation on signed int (which is what will actually happen), or to perform the calculation on unsigned int? All 3 are possible intentions and they may give different results. In addition there's an implicit lvalue conversion here - intentional or not?