【问题标题】:How to set the value of an enumeration constant outside the range of int?如何将枚举常量的值设置在 int 范围之外?
【发布时间】:2013-08-08 01:42:47
【问题描述】:

C99 标准要求用于定义枚举常量值的表达式具有可表示为 int 的值。

第 6.7.2.2 节 C99 标准的第 2 段中:

定义枚举常量值的表达式应为整数 具有可表示为 int 的值的常量表达式。

但是,枚举类型可以由实现定义为与任何整数类型兼容,包括那些值范围在int 之外的类型。

第 6.7.2.2 节 C99 标准的第 2 段中:

每个枚举类型都应兼容 char、有符号整数类型或 无符号整数类型。

这意味着虽然您不能将枚举常量的值显式设置在int 的范围之外,但如果实现将枚举类型定义为与范围在int 之外的整数类型兼容。


现在我知道了一种方法来获取超出为枚举常量设置的int 范围的特定值:dummy enumerators

enum hack{
    DUMMY0 = INT_MAX,
    DUMMY1,
    /* supply as many more dummy enumerators as needed */
    ...
    /* declare desired enumerator */
    FOOBAR
};

这要归功于 C99 标准的第 6.7.2.2 节第 3 段:

带有= 的枚举器定义了它的 枚举常量作为常量表达式的值。
...
没有= 的每个后续枚举数 将其枚举常量定义为由下式获得的常量表达式的值 将前一个枚举常量的值加 1。

不幸的是,这仅适用于大于INT_MAX 值,因为每个后续枚举器的值只会递增。另一个需要注意的是,可能需要创建许多 虚拟枚举器,以获取所需的特定枚举器。


这会导致以下问题:

  1. 有没有办法将枚举常量的值设置为int 范围之外的值?
  2. 有没有更好的方法将int 范围之外的值设置为枚举常量?
  3. 关于我的 虚拟枚举器 hack,C99 标准是否对可以在单个 enum 中声明的枚举器数量设置了限制?

【问题讨论】:

  • 不,你不能。但是,您可以做的是实现自己的使用大数字的枚举函数。但老实说,我不知道你为什么要这样做。
  • @Jocke 枚举函数的实现是什么样的?
  • 创建超过 2^32 个枚举时会发生什么?
  • 您当然不能将其实现为语法。但作为一个函数,它可以通过一个哈希表来完成,你可以用这些大数字填充这些大数字,并将键作为你自己选择的字符串。
  • 据我所知,在这里使用虚拟枚举器没有帮助 - 将前一个枚举常量的值加 1 会导致有符号整数溢出;但是,您的编译器可能支持将枚举提升为更大的整数类型作为非标准扩展......

标签: c enums enumeration c99 language-lawyer


【解决方案1】:

如何设置超出int范围的枚举常量的值?

你没有。

C99 标准要求用于定义值的表达式 枚举常量的值可以表示为int

是的,C11 标准并没有改变这一切。

但是,枚举类型可以由实现定义为 与任何整数类型兼容,包括范围为 int 之外的值。

也正确。

这意味着虽然您不能显式设置 int 范围之外的枚举常量, 枚举常量可以超出int 的范围,如果 实现将枚举类型定义为与 范围在int 之外的整数类型。

这是不正确的,但我认为您发现标准中的措辞存在弱点。 (更新:我不认为这真的是一个弱点;见下文)。你引用了 6.7.2.2:

定义枚举常量值的表达式应 是一个整数常量表达式,其值可表示为 int

这似乎只适用于由 explicit 表达式定义的值,而不是这样的情况:

enum too_big {
    big = INT_MAX,
    even_bigger
};

但这实际上不起作用,因为even_bigger 被声明为int 类型的常量,它显然不能具有INT_MAX + 1 的值。

我强烈怀疑 intent 是上述声明是非法的(违反约束);可能应该重新措辞 6.7.2.2 以使其更清楚。 (更新:我现在认为已经很清楚了;见下文。)

gcc 的作者似乎同意我的观点:

$ cat c.c
#include <limits.h>
enum huge {
    big = INT_MAX,
    even_bigger
};
$ gcc -c c.c
c.c:4:5: error: overflow in enumeration values

因此,即使您的解释是正确的,您也不太可能编写和使用依赖于它的代码。

一种解决方法是使用整数(无论如何,枚举类型或多或少都是经过伪装的整数)。不幸的是,const 整数对象不是常量表达式,因此您可能不得不求助于使用预处理器:

typedef long long huge_t;
#define big ((huge_t)INT_MAX)
#define even_bigger (big + 1)

这假设long longint 宽,这很可能但不能保证(如果int 至少为64 位,intlong long 的大小可能相同)。

您的问题 1 和 2 的答案是否定的;您不能在int 的范围之外定义枚举常量,负数或正数

至于您的问题 3,C11 标准的第 5.2.4.1 节(大致)说编译器必须在单个枚举中支持至少 1023 个枚举常量。大多数编译器实际上并没有施加固定的限制,但在任何情况下,所有常量的值都必须在INT_MIN ..INT_MAX 范围内,所以这对你没有多大好处。 (同一类型的多个枚举常量可以取相同的值。)

(翻译限制要求实际上比这更复杂。编译器必须支持至少一个程序,该程序包含所有枚举限制列表的至少一个实例。这是一个相当无用的要求如前所述。目的是满足标准给出的要求的最简单方法是避免施加任何固定限制。)

更新:

我在 comp.std.c Usenet 新闻组上提出了这个问题。 Tim Rentsch 在那次讨论中提出了一个很好的观点,我现在认为:

enum too_big {
    big = INT_MAX,
    even_bigger
};

是违反约束的,需要编译器诊断。

我担心的是禁止在int 范围之外的显式值的措辞:

定义枚举常量值的表达式应 是一个整数常量表达式,其值可表示为 int

不适用,因为不涉及(显式)表达式。但是 6.7.7.2p3 说:

没有 = 的每个后续枚举器都定义了它的枚举 常数为常数表达式加1得到的值 为上一个枚举常量的值。

(强调添加)。所以有一个表达式,它的值必须可以表示为int;它只是没有出现在源代码中。我对此不是 100% 满意,但我会说意图足够明确。

这是 comp.std.c 上的 discussion

【讨论】:

  • 是第 6.7.2.2 节第 3 段 (The identifiers in an enumerator list are declared as constants that have type int and may appear wherever such are permitted.),其中枚举常量必须是 int 类型,因此 even_bigger 将无效?如果是这样,是否有具体原因为什么使用“identifiers”一词而不是“enumerators”,或者这只是作者的偏好选择?跨度>
  • 因为标识符就是它们的本质。
【解决方案2】:

也许我还不能说 Keith Thompson 没有告诉你的任何事情。
反正我会努力的。

1. int 范围内的值

ISO C99 的文档中,我可以在 6.7.2.2 部分的第 2 和第 3 段中看到以下语句:

(2) 定义枚举常量值的表达式应为整数 具有可表示为 int 的值的常量表达式。

如果你写enum T { X = (expr) } VAR,那么expr是一个int范围内的整数,它至少包括-32767 .. +32767,您可以在 5.2.4.2.1 中阅读。
当然,(2) 段对标识符X 的类型没有任何限制。

2。枚举器标识符的类型为 int

在第 3 段中,我们可以看到这一行:

(3a) 枚举器列表中的标识符被声明为具有 int 类型的常量,并且可以出现在任何允许的地方。

这是对标识符类型的限制。现在 X 的类型为 int

3.价值观讨论

此外,标准说:

(3b) 带有 = 的枚举数将其枚举常量定义为常量表达式的值。

但这句话,现在,受到(2)(3a)的限制。
所以,我对标准C99的解读如下: 如果你写

 enum T { X = INT_MAX, BIGG}  

那么BIGG 的类型为int(根据(3a))。
正如 Keith Tompson 所指出的,BIGG 的值(=“枚举器常数?”)并非来自 表达式,而是代表了一个输出——范围值(对于 int)。它的值(在数学意义上)是X+1,因为应用了下一条规则:

(3c) 没有 = 的每个后续枚举数 将其枚举常量定义为通过以下方式获得的常量表达式的值 将前一个枚举常量的值加 1。

标准(关于整数运算)中没有任何规则定义编译器在这种情况下的行为。因此,它将属于 implementation defined 类...
但是,在编译器接受这个超出范围的值的情况下,我相信(数学) X+1 将被转换为 int 中的值范围。

但是,(3b) 中的预期行为似乎是 C 表达式 (X+1) == BIGG 总是正确。 如果我是对的,那么我同意 Keith,编译器必须以 Out of range 错误拒绝声明。

4.枚举类型的整数类型

我们可以阅读更多内容:

(4a) 每个枚举类型应与 char、有符号整数类型或 无符号整数类型。

声明enum T 定义了一个新的整数类型
此类型与 表达式 expr 的类型无关, 既不是 枚举器 X 的类型。
它只是另一种类型:与我们正在定义的 enum 类型 T 关联的整数类型。
(这种类型将是分配给变量VAR_T 的类型)。

实现可以决定哪种整数类型更合适。
如果表达式(如expr)的值非常小,几乎总是会发生, 那么编译器可能会决定 Tchar 类型,例如。
如果出于某种原因需要 long long, 那么T 的类型将是long long,以此类推。

但是,这不会改变表达式expr 和枚举器X 必须遵循的int 限制。他们是int
exprT 的类型相关的唯一规则是:

(4b) 类型的选择由实现定义,但应能够表示枚举所有成员的值。

因此,如果您有enum T { X = 0, Y = 5, Z = 9 },那么T 的类型可以是char
(这种情况类似于将始终具有 int 类型的字符常量,如 'c' 传递给 char 变量的情况:char c = 'c';:虽然'c' 是一个 int,它的值在 char 的范围内。

另一方面,如果您有enum T { X = 20202, Y = -3 },则编译器无法为T 选择char。似乎 intT 的完美类型(对于任何 enum 类型),但编译器可以为 T 选择任何其他整数类型,其范围包含值 20202-3

如果 none 值为负,则编译器可以选择 unsigned 整数类型。

5.总结

总而言之,我们可以说:

  1. enum 声明涉及 4 种类型:对于表达式 (expr),值(或枚举常量,来自表达式或只是隐式) 、枚举数 (X) 和 enum 类型 (T)。
  2. 表达式的类型(如expr)始终为int。值的类型似乎几乎不打算在 int 的范围内(INT_MAX+1 似乎是不允许的)。枚举数的类型是int(如X)。 enum 类型(如T)由实现选择,在允许的所有可能的整数类型中,只适合声明中的值。
  3. 在表达式中,我们有:

    #define NN 5
    enum T { X = 0. Y = 3, Z = (NN*3) } evar;

表达式(NN * 3) + 1 是一个int
表达式 (Z + 1) 是一个 int
如果编译器将T 定义为char
那么表达式((evar = Z), ++evar) 是一个char

【讨论】:

    【解决方案3】:

    回应 Keith Thompson 最近的更新

    我认为你是对的:标准上写着...the value of the constant expression obtained by adding 1 to the value...
    因此,非显式表达式“值”也被认为来自常量表达式

    但是,为了确保只允许 int “值”,必须同时考虑第 2 段声明的限制:The expression that defines the value of an enumeration constant ... has a value representable as an int
    因此,(非显式表达式)值也必须适合 int

    我现在确信,预期的约束是每个枚举器值都适合一个 int

    谢谢你,基思。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-25
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多