C90标准中是否有一种方法可以使枚举无符号?(符合MISRA-C 2004标准)

7
我正在尝试找到一种使枚举"无符号"的方法。
enum{
     x1 = 0,
     x2,
     x3
};
uint8_t = x2; /* <--- PC-LINT MISRA-C 2004 will complain about mixing signed and unsigned here */

当然,我可以添加类型转换来消除错误,但这样会耗费时间并容易出错。
uint8_t = (uint8_t)x2; /* This works, but is a lot of extra work over the course of 1000s of lines of code*/

那么,有没有一种方法可以使特定的枚举无符号,以便符合MISRA-C 2004的要求?


3
这是来自 PC-LINT 的一个愚蠢的警告。变量 x2 的值在编译期就已经确定,并且已知它适合于 uint8_t 范围内的取值,因此类型无关紧要。同样愚蠢的是针对 char x = 1LL; 发出警告。 - R.. GitHub STOP HELPING ICE
@R.. 问题出在MISRA而不是PC-Lint上。uint8_t x = 1; 不符合MISRA-C的规定,该规定要求有符号类型和无符号类型之间不得进行隐式转换。 - ouah
2
这是MISRA对枚举的一个关注点:枚举常量和枚举变量不一定具有相同的类型,因此它们不一定兼容。这是C语言中许多奇怪的不一致之一,而不是MISRA的错,他们只是试图避开语言中所有这些非理性缺陷。 - Lundin
但是你为什么要定义一个ENUM,然后将其用作UINT8?我同意@Lundin的观点-语言允许您(无需显式类型转换)是不合逻辑的(并且可能存在错误),这就是为什么MISRA会标记它的原因。 - Andrew
3
@Andrew,为什么需要将枚举转换为整数有很多原因。例如,像这样的内容很常见:enum {BLA_THIS, BLAH_THAT, BLAH_N }; 其中枚举的最后一项只是枚举项的计数器。然后,您可以像 for(int i=0; i<BLAH_N; i++) handle_each_possible_enum_value(my_enum); 这样使用该枚举计数器以处理每个可能的枚举值。 - Lundin
1
公正的观点,@Lundin - 应该好好思考一下... 我指的是将断开的常量枚举,用作整数的实践。 - Andrew
5个回答

11

目前没有标准的C语言方法可以控制所选择的enum类型。有时可以通过特定实现的方式来实现,例如通过将一个值添加到枚举中以强制类型为无符号:

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

但这甚至不是标准的C语言(因为提供的值不适合int)。不幸的是,你基本上没有办法。以下是标准中相关的一部分:

6.7.2.2 枚举说明符,第4段

每个枚举类型都必须与char、有符号整数类型或无符号整数类型兼容。类型的选择是实现定义的,但必须能够表示枚举的所有成员的值。直到终止枚举器声明列表的}之后,枚举类型才是不完整的;此后枚举类型是完整的。

您最好使用#define而不是enum来创建常量:

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

uint8_t x = x2;

使用0x80000000并不一定会强制枚举为无符号,因为这是有符号32位整数的有效表示。 - Bob Murphy
2
@BobMurphy:0x80000000不能表示为有符号的32位整数。唯一的可能是如果“int”比32位宽,则可以被标记为有符号。 - R.. GitHub STOP HELPING ICE
@R: 当然没错!它是-2147483648的十进制表示。这很有趣,因为你可以用32位表示的最大正整数是2147483647。就像使用8位带符号整数一样,0x80是-128,而你可以表示的最大正数是0x7F = 127。对于整数的二进制补码运算方式很有趣 - 不管你有多少位,正如维基百科文章所说,“似乎会出现一个‘额外’的负数”。 - Bob Murphy
@Carl:我同意,根据标准,将0x80000000视为等于-2147483648的int类型是实现定义的。但我还验证了clang/llvm(Xcode 4.5.2)、gcc(Ubuntu 9.10)和Visual Studio 2010在limits.h中都将INT_MIN定义为该值。 - Bob Murphy
这个答案没有任何意义,你不能“强制”枚举常量本身成为不同类型,它们不遵循整数字面量的规则。C标准在规范文本中明确指出,枚举常量必须能够表示为int,就此结束。一个实现不被允许以不同方式来实现它,否则它将不是一个符合标准的编译器。 - Lundin
显示剩余3条评论

7

这里有几个问题,存在轻微的转换错误风险,MISRA试图让你避免这些问题:

  • 枚举常量,如你示例中的x1等,保证为int类型(1)。但枚举变量和枚举变量类型未保证为相同类型(2),如果不幸的话,它可能被定义为小整数类型,从而受到整数提升规则的影响。

  • MISRA禁止大整数类型隐式转换为较小类型,主要是为了避免值的无意截断,同时也为了避免各种隐式提升规则。

你特定的MISRA合规性错误实际上来自于上述后一项担忧,违反了规则10.3(3)。

你可以通过将“底层类型”(预期类型)进行显式转换来解决此问题,在这种情况下,需要将其转换为uint8_t。或者你可以完全不使用枚举,用#define替换它们。这听起来可能很激进,但请记住,C没有任何类型安全性,因此使用枚举除了可读性之外似乎没有任何好处。

通常会用以下方式替换枚举:

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

(尽管此示例的目的主要是使BOOL类型可移植,并保证为8位而不是16位,因为它可能是枚举类型。)


参考文献:

(1)C11 6.2.7.7/2:

"定义枚举常量值的表达式应为整数常量表达式,其值可以表示为int。"

(2)C11 6.2.7.7/4:

"每个枚举类型都应与char、有符号整数类型或无符号整数类型兼容。类型的选择是实现定义的,但应能够表示枚举成员的所有值。"

(3)MISRA-c:2004规则10.3:

"整数类型的复杂表达式的值只能强制转换为比表达式的基础类型更窄且带有相同符号的类型。"


使用枚举类型肯定有类型安全的优势,例如使用 -Wswitch 时,GCC 和 clang 都会确保您在 switch 语句中详尽检查所有枚举情况。 - Gus

5

不仅在C90中不存在指定enum采用无符号类型的方法,而且在C90中:

作为枚举常量声明的标识符具有 int 类型。

这也适用于C99(6.4.4.3)。如果要使用无符号类型,则需要使用语言扩展。

枚举类型可能是除int以外的其他类型,但是常量本身必须具有int类型。


0
除了@Carl's answer之外,为了获得一些enum声明的好处并得到一些无符号类型的结果,代码可以使用以下内容。
// 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)

这可能无法帮助枚举常量超出int范围的情况。

当然,代码可以像 OP 知道的那样进行转换。

// uint8_t = x2;
uint8_t = x2 * 1u;

0

您可以通过包含一个足够大的值来强制它成为无符号数,以至于它不能适应 int(根据规范)。对于大小 >= sizeof int 的类型来说,这非常简单,但是对于 unsigned char/short 来说则更加复杂,需要编译器特定的打包。当然,实现技术上仍然可以将 UINT_MAX 表示为 unsigned long long……虽然我从未见过。

#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 })

这里有一个检查,以确保它按预期工作:

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

为了更像标准的枚举语句,这里稍微有些不同于我使用的简单版本,它需要一个额外的命名参数来表示最后一个枚举,而不是使用__LINE__技巧(对于返回错误时返回-1的函数也很有用,因为它会转换为U*_MAX)。以下是该版本的样式:
#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 })

除了将枚举类型打包成char或short以节省空间外,size_t枚举类型是最有趣的,因为它们可以作为数组索引使用而无需额外的MOV指令。
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]);
}

请注意,如果您使用的是C++11而不是C语言,您可以使用以下代码:enum Foo : char { A, B, C}; 或者 enum class Bar : size_t { X, Y, Z};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接