我有一个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会这样做。这是一个问题,一个错误,还是正常的?如果这是正常的,为什么?
答案 0 :(得分:3)
当您为枚举类型指定packed
和aligned
属性时,您遇到了gcc行为奇怪的情况。这可能是个错误。它至少是一个没有文档记录的功能。
您拥有的简化版本是:
typedef enum __attribute__ (packed, aligned(2)) UpdateType_t {
foo, bar
} UpdateType_t;
枚举常量的值都足够小,可以放在单个字节中,无论是有符号还是无符号。
packed
类型上aligned
和enum
属性的行为有点令人困惑。据我所知,packed
的行为并未完全记录在案。
我使用gcc 5.2.0的实验表明:
__attribute__(packed)
会使其被赋予最小的大小,以适应所有常量的值。在这种情况下,大小为1字节,因此范围是-128 .. + 127或0..255。 (这没有记录。)
__attribute__(aligned(N))
会影响该类型的尺寸。特别是,aligned(2)
为枚举类型提供了2个字节的大小和对齐方式。
棘手的部分是:如果您指定 packed
和aligned(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 char
,short
和int
值,否则它是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
,而不是枚举类型),您也可以使用{{声明只是为了定义常量的值,并使用你喜欢的任何整数类型来声明变量。