簡體   English   中英

有沒有辦法在 C90 標准中使枚舉無符號? (符合 MISRA-C 2004)

[英]Is there a way to make an enum unsigned in the C90 standard? (MISRA-C 2004 compliant)

我試圖找到一種方法來使枚舉“無符號”。

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 喜歡的特定枚舉未簽名?

沒有標准的 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;

這里有幾個問題,其中存在輕微的轉換錯誤的可能性,MISRA 試圖讓您避免這些錯誤:

  • 枚舉常量,即您的示例中的x1等,保證為int (1) 類型。 但是enum變量和enum變量類型不保證是同一類型(2),如果你不走運,它被定義為一個小整數類型,從而受到整數提升規則的約束。

  • MISRA 禁止大整數類型到小整數類型的隱式轉換,主要是為了避免值的無意截斷,同時也是為了避免各種隱式提升規則。

您的特定 MISRA 合規性錯誤實際上來自上述后一個問題,違反了規則 10.3 (3)。

您可以通過向“基礎類型”(預期類型)添加顯式強制轉換來解決此問題,在本例中為對 uint8_t 的強制轉換。 或者您可以通過根本不使用枚舉來解決它,用#defines 替換它們。 這聽起來可能非常激進,但請記住,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:

“整數類型的復雜表達式的值只能轉換為與表達式的基礎類型更窄且符號相同的類型。”

不僅在 C90 中沒有辦法指定enum采用無符號類型,而且在 C90 中:

聲明為枚舉常量的標識符具有 int 類型

這也適用於 C99 (6.4.4.3)。 如果你想要一個無符號類型,你正在尋找一個語言擴展。

枚舉類型可能不是int ,但常量本身必須具有int類型。

您可以通過包含一個足夠大的值使其無法放入 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;
}

更像是標准的 enum 語句,這與我使用的更簡單的版本略有不同,后者為最后一個 enum 使用一個額外的命名參數,而不是__LINE__ hack(這對於錯誤返回 -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 中或為了緊湊性的縮寫之外,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};

除了@Carl的回答之外,為了獲得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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM