7

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?

red0ct
  • 4,840
  • 3
  • 17
  • 44
Tom
  • 776
  • 1
  • 5
  • 12
  • 3
    This is a stupid warning from PC-LINT. The value of `x2` is known statically, and known to fit in the range of `uint8_t`, so the type is irrelevant. Equally stupid would be issuing a warning for `char x = 1LL;`. – R.. GitHub STOP HELPING ICE Jan 31 '13 at 22:53
  • @R.. the problem is with MISRA not with PC-Lint. `uint8_t x = 1;` is not compliant with MISRA-C which requires no implicit conversion between signed and unsigned types. – ouah Jan 31 '13 at 22:58
  • 2
    This is one of MISRA's concerns with enums: enum constants and enum variables do not necessarily have the same type and therefore they aren't necessarily compatible. This is one of many weird inconsistencies in the C language and no fault of MISRA, who are merely trying to dodge all such irrational flaws of the language. – Lundin Feb 01 '13 at 10:05
  • But why are you defining an ENUM then using it as a UINT8? I agree with @Lundin - that the language allows you (without explicit type conversion) is illogical (and potentially error prone) which is why MISRA flags it. – Andrew Feb 01 '13 at 12:49
  • 3
    @Andrew There are many reasons why you would want an enum to int conversion. For example something like this is common: `enum {BLA_THIS, BLAH_THAT, BLAH_N };` where the last item of the enum is simply a counter of enum items. You could then use that enum counter like `for(int i=0; i – Lundin Feb 01 '13 at 13:30
  • 1
    Fair point, @Lundin - should engage brain... I was referring to the practice of *enum*ing disconnected constants, to be used as ints. – Andrew Feb 01 '13 at 17:16

5 Answers5

11

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;
Community
  • 1
  • 1
Carl Norum
  • 219,201
  • 40
  • 422
  • 469
  • Using 0x80000000 wouldn't necessarily force the enum to be unsigned, since that's a valid representation for a signed 32-bit integer. – Bob Murphy Jan 31 '13 at 22:22
  • Carl - I rather thought that that was the case. Thanks. – Tom Jan 31 '13 at 22:24
  • 2
    @BobMurphy: 0x80000000 is not representable as a signed 32-bit integer. The only way it could be signed is if `int` is wider than 32 bits. – R.. GitHub STOP HELPING ICE Jan 31 '13 at 22:50
  • @R: Sure it is! It's -2147483648 base 10. Which is interesting, because the largest positive integer you can represent with 32 bits is 2147483647. It's just like with an 8-bit signed integer, 0x80 is -128, while the largest positive number you can represent is 0x7F = 127. Two's complement math on integers is funny that way - no matter how many bits you have, as the Wikipedia article says, "there appears to be an 'extra' negative number." – Bob Murphy Jan 31 '13 at 23:37
  • @Bob - 0x80000000 is a positive number, not a negative number. It can only be represented in an unsigned 32-bit type (or a signed larger-than-32-bit type). The fact that when you try to stuff it in a too-small type (like a signed 32-bit `int`) you get a negative result is due to C conversion rules, not anything else. If I had a sign-magnitude system, it might end up `-0`! On every 32-bit C implementation I've ever used, `0x80000000` would be a literal with `unsigned int` type. – Carl Norum Jan 31 '13 at 23:41
  • From the standard, regarding conversions from unsigned types to signed types: "Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised." – Carl Norum Jan 31 '13 at 23:50
  • @Carl: I agree that, per the standard, treating 0x80000000 as an int equal to -2147483648 would be implementation-defined. But I also just verified that clang/llvm (Xcode 4.5.2), gcc (Ubuntu 9.10), and Visual Studio 2010 all define INT_MIN to be precisely that value in limits.h. – Bob Murphy Feb 01 '13 at 02:06
  • This answer doesn't make any sense, you can't "force" enum constants themselves to be of a different type, they don't follow the rules of integer literals. The C standard explicitly states in normative text that an enum constant must be representable as an int, end of story. An implementation is _not_ allowed to implement that differently, or it wouldn't be a conforming compiler. – Lundin Feb 01 '13 at 10:23
7

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."

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • There are definitely type safety advantages to using enums, e.g. with -Wswitch both GCC and clang will make sure you exhaustively check all enum cases in switch statements. – Gus Jan 08 '22 at 18:41
5

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.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
0

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};

technosaurus
  • 7,676
  • 1
  • 30
  • 52
0

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;
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256