gcc枚举错误的值

时间:2015-10-23 00:00:09

标签: gcc enums

我有一个enum typedef,当我分配一个错误的值(不在枚举中)并打印它时,它会显示一个枚举值,而不是坏值。为什么呢?

这是一个例子:

#define attribute_packed_type(x ) __attribute__( ( packed, aligned( sizeof( x ) ) ) )

typedef enum attribute_packed_type( uint16_t ) UpdateType_t
{
    UPDATE_A = 4,
    UPDATE_B = 5,
    UPDATE_C = 37,
    UPDATE_D = 43,

     //   UPDATE_TYPE_FORCE_UINT16 = 0xFFFF,

} UpdateType_t;


UpdateType_t myValue;
uint16_t     bad = 1234;

myValue = bad;

printf( "myValue=%d\n", myValue );

return 1;

,此示例的输出为:

myValue=210

如果我在枚举中启用“UPDATE_TYPE_FORCE_UINT16”,则输出为:

myValue=1234

我不明白为什么gcc会这样做。这是一个问题,一个错误,还是正常的?如果这是正常的,为什么?

1 个答案:

答案 0 :(得分:3)

当您为枚举类型指定packedaligned属性时,您遇到了gcc行为奇怪的情况。这可能是个错误。它至少是一个没有文档记录的功能。

您拥有的简化版本是:

typedef enum __attribute__ (packed, aligned(2)) UpdateType_t {
    foo, bar
} UpdateType_t;

枚举常量的值都足够小,可以放在单个字节中,无论是有符号还是无符号。

packed类型上alignedenum属性的行为有点令人困惑。据我所知,packed的行为并未完全记录在案。

我使用gcc 5.2.0的实验表明:

    应用于枚举类型的
  • __attribute__(packed)会使其被赋予最小的大小,以适应所有常量的值。在这种情况下,大小为1字节,因此范围是-128 .. + 127或0..255。 (这没有记录。)

  • __attribute__(aligned(N))会影响该类型的尺寸。特别是,aligned(2)为枚举类型提供了2个字节的大小和对齐方式。

棘手的部分是:如果您指定 packedaligned(2),那么aligned规范会影响尺寸枚举类型,但不是范围。这意味着即使enum e大到足以保存0到65535之间的任何值,任何超过255的值都会被截断,只留下该值的低8位。

无论aligned规范如何,您使用packed属性的事实意味着gcc会将枚举类型的范围限制为可以符合以下值的最小范围所有的常数。 aligned属性可以更改大小,但不会更改范围。

在我看来,这是gcc中的一个错误。 (并且clang,在很大程度上与gcc兼容,表现不同。)

最重要的是,通过打包枚举类型,您已告诉编译器缩小其范围。避免这种情况的一种方法是定义一个值为0xFFFF的额外常量,您可以在注释中显示。

通常,C enum类型与某种整数类型兼容。选择使用哪种整数类型是实现定义的,只要所选类型可以表示所有指定的值。

根据最新的gcc手册:

  

通常情况下,如果没有否定,则类型为unsigned int   枚举中的值,否则为int。如果是-fshort-enums   指定,如果有负值则是第一个   可以代表所有人的signed charshortint   值,否则它是unsigned char中的第一个,unsigned short   和unsigned int可以代表所有价值观。

     

在某些目标上,-fshort-enums是默认值;这是   由ABI确定。

同时引用gcc手册:

  

packed属性指定变量或结构字段   应该具有尽可能小的对齐 - 一个字节用于a   除非指定更大的变量,否则变量和字段的一位   具有aligned属性的值。

这是一个基于你的测试程序,但显示了一些额外的信息:

#include <stdio.h>
int main(void) {
    enum __attribute((packed, aligned(2))) e { foo, bar };
    enum e obj = 0x1234;
    printf("enum e is %s, size %zu, alignment %zu\n",
           (enum e)-1 < (enum e)0 ? "signed" : "unsigned",
           sizeof (enum e),
           _Alignof (enum e));
    printf("obj = 0x%x\n", (unsigned)obj);
    return 0;
}

这会产生编译时警告:

c.c: In function 'main':
c.c:4:18: warning: large integer implicitly truncated to unsigned type [-Woverflow]
     enum e obj = 0x1234;
                  ^

并输出:

enum e is unsigned, size 2, alignment 2
obj = 0x34

对程序的最简单更改是添加

UPDATE_TYPE_FORCE_UINT16 = 0xFFFF

您已注释掉,强制该类型的范围至少为0到65535.但这是一种更便携的选择。

标准C没有提供指定enum类型表示的方法。 gcc确实如此,但是我们已经看到它没有明确定义,并且可以产生令人惊讶的结果。但是,除了uint16_t之外,还有一种替代方案不需要任何不可移植的代码或假设:

enum {
    UPDATE_A = 4,
    UPDATE_B = 5,
    UPDATE_C = 37,
    UPDATE_D = 43,
};
typedef uint16_t UpdateType_t;

匿名enum类型仅用于定义常量值(类型为int,而不是枚举类型)。您可以声明类型为UpdateType_T的对象,并且它们与uint16_t具有相同的表示形式,我认为这是您真正想要的。

由于C枚举常量无论如何都与它们的类型紧密相关(例如UPDATE_A的类型为int,而不是枚举类型),您也可以使用{{声明只是为了定义常量的值,并使用你喜欢的任何整数类型来声明变量。