C语言 如何将枚举常量的值设置在int的范围之外?

wtlkbnrh  于 2023-05-28  发布在  其他
关注(0)|答案(3)|浏览(217)

C99标准要求用于定义枚举常量值的表达式具有可表示为int的值。
部分6.7.2.2C99标准的第2段中:
定义枚举常量值的表达式应为整数常量表达式,其值可表示为**int
然而,枚举类型可以由实现定义为与任何整数类型兼容,包括具有int之外的值范围的整数类型。
部分6.7.2.2C99标准的第2段中:
每个枚举类型应与
char**、有符号整数类型或无符号整数类型兼容。
这意味着虽然不能显式地将枚举常量的值设置在int的范围之外,但如果实现将枚举类型定义为与范围在int之外的整数类型兼容,则枚举常量的值可以在int的范围之外。
现在我知道了一种方法来获取枚举常量int范围之外的特定值:* 虚拟枚举器 *。

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

这要归功于6.7.2.2C99标准的第3段部分www.example.com
具有=的枚举器将其枚举常量定义为常量表达式的值。
...
不带=的每个后续枚举数将其枚举常量定义为常量表达式的值,该表达式是通过将前一个枚举常量的值加1获得的。
不幸的是,这只适用于大于INT_MAX值,因为每个后续枚举器的值只递增。另一个警告是需要创建可能多个 *dummy enumerator * 来获取所需的特定枚举器。
这就引出了以下问题:
1.有没有办法将枚举常量的值设置为int范围之外的负值
1.是否有更好的方法将int范围外的值设置为枚举常量?
1.关于我的 dummy enumerator hack,C99标准是否限制了可以在单个enum中声明的枚举数?

8yoxcaq7

8yoxcaq71#

如何设置枚举常量的值在int范围之外?

(目前,如果你使用的是C23或更高版本的编译器,请参阅下面的UPDATE 2)。
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
我强烈怀疑 * 意图 * 是上面的声明是非法的(违反约束);也许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,5.2.4.1C11标准的www.example.com部分(粗略地)说,编译器必须在单个枚举中支持 * 至少 * 1023个枚举常量。大多数编译器实际上并没有强加一个固定的限制,但在任何情况下,所有的常量都必须具有INT_MIN范围内的值。INT_MAX,所以这对你没有多大好处。(同一类型的多个枚举常量可以具有相同的值。)
(The翻译限制要求实际上比这更复杂。编译器必须支持 * 至少一个 * 程序,该程序包含所有枚举限制列表的至少一个示例。这是一个相当无用的要求,如所述。其目的是,满足标准要求的最简单方法是避免施加任何固定限制。)

更新:

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

enum too_big {
    big = INT_MAX,
    even_bigger
};

是约束冲突,需要编译器诊断。
我担心的是禁止int范围之外的 * 显式 * 值的措辞:
定义枚举常量值的表达式应为整数常量表达式,其值可表示为int
不适用,因为不涉及(显式)* 表达式 。但是6.7.7.2p3说:
没有
*=的每个后续枚举器将其枚举常量定义为常量expression**的值,该常量通过将前一个枚举常量的值加1获得。
(强调部分另加)。因此,有一个 * 表达式 *,其值必须可表示为int;只是没有出现在源代码中。我不是100%满意,但我想说意图足够清楚。
下面是关于comp.std.c的讨论。

更新二:

2023版的ISO C标准(在我写这篇文章时尚未发布,但最新的草案是N3054)允许 enum-type-specifier 指定枚举类型的底层类型。
gcc 13.1.0通过-std=c2x选项部分支持C23。

#include <stdio.h>
#include <limits.h>
int main(void) {
    enum small { zero, one };
    enum big : long long { BIG = UINT_MAX + 1LL, BIGGER };
    printf("%d %d %lld %lld\n", zero, one, BIG, BIGGER);
}

在我的系统上,gcc 13.1.0的输出是:

0 1 4294967296 4294967297
jaql4c8m

jaql4c8m2#

也许我不能说任何基思汤普森没有告诉你,还没有。
不管怎样,我会尽力的。

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))。
正如基思Tompson所指出的,值(=“枚举常量?BIGG的“)不是来自 * 表达式 *,表示超出范围的值(对于
int**)。它的值(在数学意义上)是 X+1,因为应用了下一个规则:
(3c)每个后续的枚举数no =内斯其枚举常量定义为常量表达式的值,该常量表达式通过将前一个枚举常量的值加1获得。
标准中没有任何规则(关于整数算术)定义这种情况下编译器的行为。因此,它将属于implementation defined类...
然而,在编译器接受这个超出范围的值的情况下,我相信(数学)X+1 将被转换为int范围内的值。
但是,(3b)中的预期行为似乎是C表达式**(X+1)== BIGG总是true**。如果我是对的,那么我同意基思的观点,编译器必须用Out of range错误拒绝声明。

4.枚举类型的整数类型

我们可以读到更多:
(4a)每个枚举类型应与char、有符号整数类型或无符号整数类型兼容。
声明enum T定义了一个新的integer类型
此类型不一定与 expressionexpr的类型相关,也不一定与 enumeratorX的类型相关。
它只是另一种类型的另一件事:与我们正在定义的enum类型T相关联的整数类型。
(This类型将是分配给变量VAR_T的类型)。
实现可以决定哪种整数类型更合适。
如果表达式(如expr)的值非常小,这几乎总是发生的,那么编译器可能会决定T的类型为char
如果出于某种原因需要long long,则T的类型将是long long,依此类推。
但是,这并没有改变表达式expr和枚举器X必须遵循的对int的限制。它们是int
exprT类型之间的唯一关联规则是:
(4b)类型的选择由实现定义,但应能够表示枚举的所有成员的值。
因此,如果你有enum T { X = 0, Y = 5, Z = 9 },那么T的类型可以是char
(The这种情况类似于一个字符常量,如'c',它总是具有类型int,被传递给一个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)。
1.表达式的类型(如expr)始终为int。值的类型似乎很难在int的范围内(INT_MAX+1似乎不允许)。枚举器的类型为int(如X)。而enum类型(如T)是由实现在所有允许的整数类型中选择的,只是适合声明中的值。
1.在表达式中,我们有:
#define NN 5
enum T { X = 0. Y = 3, Z = (NN*3) } evar;

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

i34xakig

i34xakig3#

标准是...the value of the constant expression obtained by adding 1 to the value...
因此,非显式表达式“value”也被认为来自常量表达式
然而,为了确保只允许int“values”,我们必须共同考虑第2段声明的限制:The expression that defines the value of an enumeration constant ... has a value representable as an int
因此,(非显式表达式)值也必须适合int
我现在确信,预期的约束是每个枚举器值都适合int

相关问题