大型枚举值的表示是否有任何保证?

29

假设我有一个32位的机器:

enum foo {
    val1 = 0x7FFFFFFF, // originally '2^31 - 1'
    val2,
    val3 = 0xFFFFFFFF, // originally '2^32 - 1'
    val4,
    val5
};

val2、val4和val5的值是多少?我知道我可以测试它,但结果是否是标准化的?


1
最好的情况是这将是实现定义的,枚举的大小没有上限。 - Kevin
12
仅供娱乐,这两个表达式都可以编译,并产生非常小的数字 - 28 和 29 - Sergey Kalinichenko
1
@DyP 不是,它以前是一个异或运算符 - OP用它来说明“两的幂”。 - Sergey Kalinichenko
2
@Bathsheba:C和C++是不同的编程语言,你想要哪一个? - Nicol Bolas
2
@Bathsheba:我看到你标记了两个。这就是我的观点:它们之间的答案不同的 - Nicol Bolas
显示剩余7条评论
4个回答

16

在C标准中:

C11 (n1570), § 6.7.2.2 枚举说明符

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

如果编译器使用的基础类型不能表示这些值,则行为未定义。

C11 (n1570), § 4. 符合性

如果违反出现在约束或运行时约束之外的“应”或“不应”要求,则行为未定义。


但是 uint_least64_t 不是必须存在吗?它不是一种 无符号整数类型 吗? - dyp
3
@Dyp,是的,这样编译器就可以选择它。但是,如果您使用'0xFFFFFFFFFFFFFFFFllu',那么如果编译器不支持大于64位的整数,则行为将是未定义的。 - Shahbaz
此外,需要使用unsigned long long来存储至少2^64-1(“的幂”,而不是“异或”),因此0xFF FF FF FF不应该是一个问题。 - dyp
@ArneMertz,关于什么引用?5.2.4.2.1指定了整数类型的最小存储能力(“大小”);6.7.2.2/2是一个约束条件,“定义枚举常量值的表达式必须是一个可表示为int的整数常量表达式”,/3则说明:“枚举列表中的标识符被声明为具有int类型的常量”。 - dyp
@ArneMertz 第一个问题是肯定的,第二个问题是否定的:如果我没记错的话,枚举类型声明引入了一个新的类型(例如 void foo(enum sillyQuestion));但是枚举值并不具有该类型,它们被声明为 int 类型的常量。 - dyp
显示剩余3条评论

10

根据C++11标准(§7.2,6,强调是我的):

如果枚举类型的底层类型未固定,则底层类型为能够表示该枚举类型中所有枚举值的整数类型。 如果没有任何整数类型能够表示所有枚举值,则该枚举类型无效。 底层类型的实际类型是由实现定义的,但底层类型不得大于int,除非枚举值的值无法适应int或unsigned int。

因此,如果有一个比32位更大的整数类型,编译器将会正确处理。如果没有,枚举类型将无效。值不会进行包装处理。

这些值将是:

enum foo {
    val1 =                       0x7FFFFFFF, 
    val2,              //        0x80000000   = 2^31
    val3 =                       0xFFFFFFFF, 
    val4,              //0x0000000100000000   = 2^32
    val5               //0x0000000100000001   = 2^32+1
};

递增的数字也被明确定义了(§7.2,2):

[...]没有初始化器的枚举符定义会使该枚举符的值在上一个枚举符的值基础上递增一。


1
如果存在比32位更大的整数类型,那么C99要求使用unsigned long long来存储至少2^64-1。还有uint_least64_t也可以使用。 - dyp
我认为更重要的是枚举器的类型,而不是枚举类型;参见[dcl.enum] / 5。 "如果底层类型未固定,则每个枚举器的类型是其初始化值的类型",但它将根据子弹3增长(与C99/11中的要求相反)。 - dyp
我认为枚举一种类型,而不是具有类型。在C++中,枚举器的类型是foo,而不是在C中。 C++标准的§7.2,1清楚地说明:“枚举是具有命名常量的独特类型”。 - Arne Mertz
是的,枚举是一种类型,但对于C语言也是如此,6.2.5 /16中指出:“每个不同的枚举都构成了一个不同的枚举类型。”然而,我仍然认为枚举是否是一种类型以及其底层类型并不重要,因为OP谈论的是枚举器。 - dyp
你可能想要添加这句话:“在枚举说明符的右花括号后,每个枚举器都具有其枚举类型。”以及[dcl.enum]/5的一些内容。 - dyp

5

C99 / C11

前言:

5.2.4.2.1 规定 int 至少需要16位;据我所知,没有上限(但long需要更长或相等,详见6.2.5 /8)。

6.5 /5:

如果在表达式求值期间出现异常情况(也就是说,如果结果在其类型的表示范围内不是数学上定义的),则行为未定义。


如果你的`int`宽度为32位(或更小)

那么OP中的例子违反了 约束条件 6.7.2.2 /2:

定义枚举常量值的表达式应该是一个可表示为 int 类型的整数常量表达式。

此外,枚举成员被定义为具有类型 int 的常量,详见 6.7.2.2 /3

枚举列表中的标识符被声明为具有类型 int 的常量,并且可以出现在任何允许使用这些常量的地方。


注意,枚举类型的类型枚举成员/枚举常量的类型之间存在差异:

enum foo { val0 };
enum foo myVariable;        // myVariable has the type of the enumeration
uint_least8_t v = val0*'c'; // if val0 appears in any expression, it has type int

我认为这样可以缩小范围,例如将枚举类型的大小减少到8位:

enum foo { val1 = 1, val2 = 5 };
enum foo myVariable = val1;    // allowed to be 8-bit

但是它似乎不允许扩展,例如:disallow
enum foo { val1 = INT_MAX+1 }; // constraint violation AND undefined behaviour
// not sure about the following, we're already in UB-land
enum foo myVariable = val1;    // maximum value of an enumerator still is INT_MAX
                               // therefore myVariable will have sizeof int

枚举类型的自增

由于6.7.2.2/3规定:

[...] 每个没有包含等号=的枚举常量,其值为前一个枚举常量的值+1得到的常量表达式的值。[...]

因此,该示例会导致未定义行为:

enum foo {
    val0 = INT_MAX,
    val1            // equivalent to `val1 = INT_MAX+1`
};

int 可以窄至 16 位。 - Keith Thompson

2
这是 C++ 的答案:在 7.2/6 中,它声明:
“……底层类型是一个整数类型,可以表示枚举中定义的所有枚举值。如果没有整数类型可以表示所有枚举值,则枚举是不良形式的。使用哪个整数类型作为基础类型是由实现定义的,除非基础类型不应大于 int,除非枚举的枚举值不能适合 int 或 unsigned int。”
因此,与 C 相比:如果编译器找不到类型,则不会出现未定义的行为,并且编译器不能仅使用其 512 位扩展整数类型来处理您的两值枚举。
这意味着在您的示例中,底层类型将可能是某个带符号的 64 位类型 - 大多数编译器始终首先尝试有符号版本的类型。

val4val5无法适应32位无符号整数,因此编译器必须使用比它更大的类型。 - Arne Mertz
好的,忘记了那些。 - Sebastian Redl
@SebastianRedl:你确定有符号性吗?我认为gcc和Clang更倾向于使用无符号的对应项(除非有负值)。 - Matthieu M.
检查Clang的代码,它似乎更喜欢C的无符号类型和C++的有符号类型。我敢打赌,GCC也是这样做的 - 兼容性是Clang进行这些奇怪操作的首要原因。 - Sebastian Redl

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