Domanda

I'm trying to find a way to make an enum "unsigned".

enum{
     x1 = 0,
     x2,
     x3
};
uint8_t = x2; /* <--- PC-LINT MISRA-C 2004 will complain about mixing signed and unsigned here */

Of course, I can add a typecast to get rid of the error, that is time consuming and error prone.

uint8_t = (uint8_t)x2; /* This works, but is a lot of extra work over the course of 1000s of lines of code*/

So, is there a way to make a specific enum unsigned that MISRA-C 2004 will like?

È stato utile?

Soluzione

There is no standard C way to control the type chosen for an enum. You can do it in implementation specific ways sometimes, like by adding a value to the enumeration that forces the type to be unsigned:

enum {
  x1,
  x2,
  x3,
  giant_one_for_forcing_unsigned = 0x80000000;
};

But that's not even standard C, either (since the value provided won't fit in an int). Unfortunately, you're pretty much out of luck. Here's the relevant bit from the standard:

6.7.2.2 Enumeration specifiers, paragraph 4

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration. The enumerated type is incomplete until immediately after the } that terminates the list of enumerator declarations, and complete thereafter.

You might be better off using #define rather than enum to make your constants:

#define x1 0U
#define x2 1U
#define x3 2U

uint8_t x = x2;

Altri suggerimenti

There are several concerns here, where there is a slight potential for conversion bugs, which MISRA is trying to make you avoid:

  • Enum constants, that is x1 etc in your example, are guaranteed to be of type int (1). But enum variables and the variable type enum is not guaranteed to be of the same type (2), if you are unlucky it is defined to be a small integer type and thereby subject to the integer promotion rules.

  • MISRA bans implicit conversions for large integer types to smaller ones, mainly to dodge unintentional truncation of values, but also to dodge various implicit promotion rules.

Your specific MISRA-compliance error actually comes from the latter concern above, violation of rule 10.3 (3).

You can either solve this by adding an explicit cast to the "underlying type" (intended type), in this case a cast to uint8_t. Or you can solve it by never using enums at all, replace them with #defines. That might sound very radical, but keep in mind that C has no type safety whatsoever, so there is no apparent benefit of using enums apart from perhaps readability.

It is somewhat common to replace enums in this manner:

#define FALSE 0
#define TRUE  1
typedef uint8_t BOOL;

(Though the purpose in this example is mainly to make the BOOL type portable, with a guarantee to be 8 bits and never 16 bits, as might happen in case it was an enum.)


References:

(1) C11 6.2.7.7/2:

"The expression that defines the value of an enumeration constant shall be an integer constant expression that has a value representable as an int."

(2) C11 6.2.7.7/4:

"Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration."

(3) MISRA-c:2004 rule 10.3:

"The value of a complex expression of integer type may only be cast to a type that is narrower and of the same signedness as the underlying type of the expression."

Not only is there not a way in C90 to specify that an enum take on an unsigned type, but in C90:

An identifier declared as an enumeration constant has type int

This also applies to C99 (6.4.4.3). If you want an unsigned type, you're looking at a language extension.

The enumeration type may be something other than int, but the constants themselves must have int type.

You can force it to be unsigned by including a value large enough that it cannot fit in an int (per specification). This is pretty simple for types >= sizeof int, but unsigned char/short is more complicated and requires compiler specific packing. Of course implementations could technically still represent UINT_MAX as an unsigned long long... not that I've ever seen though.

#include <stdio.h> //only included for printf example
#include <limits.h>
#include <stdint.h>

/** set up some helper macros **/
#ifdef _MSC_VER 
    #define PACK( ... ) __pragma( pack(push, 1) ) __VA_ARGS__ __pragma( pack(pop) )
#else /* for gcc, clang, icc and others */
    #define PACK( ... ) __VA_ARGS__ __attribute__((__packed__))
#endif
#define _PASTE(x,y) x ## y
#define PASTE(x,y) _PASTE(x,y)

/* __LINE__ added for semi-unique names */
#define U_ENUM(n, ... ) \
    enum n { __VA_ARGS__ , PASTE( U_DUMMY , __LINE__ ) = UINT_MAX }
#define UL_ENUM(n, ... ) \
    enum n { __VA_ARGS__ , PASTE( UL_DUMMY , __LINE__ ) = ULONG_MAX }
#define SZ_ENUM(n, ... ) /* useful for array indices */ \
    enum n { __VA_ARGS__ , PASTE( SZ_DUMMY , __LINE__ ) = SIZE_MAX }
#define ULL_ENUM(n, ... ) \
    enum n { __VA_ARGS__ , PASTE( ULL_DUMMY , __LINE__ ) = ULLONG_MAX }
#define UC_ENUM(n,...) \
    PACK(enum n { __VA_ARGS__ , PASTE( UC_DUMMY , __LINE__ ) = UCHAR_MAX })
#define US_ENUM(n,...) \
    PACK(enum n { __VA_ARGS__ , PASTE( US_DUMMY , __LINE__ ) = USHRT_MAX })

Here is a check to see that it works as expected:

typedef UC_ENUM(,a) A_t;
typedef US_ENUM(,b) B_t;
typedef U_ENUM(,c) C_t;
typedef UL_ENUM(,d) D_t;
typedef ULL_ENUM(,e) E_t;
typedef SZ_ENUM(,e) F_t;
int main(void) {
  printf("UC %d,\nUS %d,\nU %d,\nUL %d,\nULL %d,\nSZ %d,\n",sizeof(A_t),
    sizeof(B_t),sizeof(C_t),sizeof(D_t),sizeof(E_t),sizeof(F_t));
  return 0;
}

To be more like a standard enum statement this is slightly different than the simpler version I use, which takes an additional named parameter for the last enum instead of the __LINE__ hack (this is also useful for functions that return -1 on error, because it will cast to U*_MAX) Here is how that version looks:

#define U_ENUM( n, err, ...)      enum n { __VA_ARGS__ , err = UINT_MAX  }
#define UL_ENUM(n, err, ...)      enum n { __VA_ARGS__ , err = ULONG_MAX }
#define ULL_ENUM(n,err, ...)      enum n { __VA_ARGS__ , err = ULLONG_MAX}
#define SZ_ENUM(n, err, ...)      enum n { __VA_ARGS__ , err = SIZE_MAX  }
#define UC_ENUM(n, err, ...) PACK(enum n { __VA_ARGS__ , err = UCHAR_MAX })
#define US_ENUM(n, err, ...) PACK(enum n { __VA_ARGS__ , err = USHRT_MAX })

Apart from packing enums in char or short for compactness, size_t enums are the most interesting, because they can be used as array indices without an extra MOV instruction.

typedef SZ_ENUM(message_t,MSG_LAST,MSG_HELLO,MSG_GOODBYE,MSG_BAD) message_t;
static const char *messages[]={"hello","goodbye","bad message"};
void printmsg(message_t msg){
  if (msg > MSG_BAD) msg = MSG_BAD;
  (void) puts(messages[msg]);
}

Note if you use C++11 vs C, you can enum Foo : char { A, B, C}; or enum class Bar : size_t { X, Y, Z};

In addtion to @Carl's answer, to get some of the benefits of an enum declaration and result in some unsigned type, code could use the below.

// Form values 0, 5, 6
enum { 
 x1, 
 x2 = 5, 
 x3
};

// Form values 0u, 5u, 6u
#define ux1 (1u * x1)
#define ux2 (1u * x2)
#define ux3 (1u * x3)

This may not help with enumerations constants outside the int range.

Of course code could do the conversion instead as OP knows.

// uint8_t = x2;
uint8_t = x2 * 1u;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top