范围和不同枚举类型之间的可转换性

huangapple go评论59阅读模式
英文:

Range of and convertibility between different enum types

问题

在哪些条件下可以将一个枚举类型转换为另一个枚举类型?

让我们考虑以下代码:

#include <stdio.h>

int main(void) {
  enum enum_tag_1 { language } c = language;
  enum enum_tag_2 { lawyer = 256 } d = lawyer;
  d = c;
  c = 2;
  printf("%d\n", c);
  printf("%d\n", d);
}

现在我们可以提出诸如 c = 2;d = c; 这样的语句是否保证起作用的问题。

C17标准(草案,6.7.2.2)指出:

> ¶3 枚举列表中的标识符被声明为具有类型int的常量。[...]
> ¶4 每个枚举类型应与char、有符号整数类型或无符号整数类型兼容。类型的选择是实现定义的,[] 但应能够表示枚举的所有成员的值。[...]

也就是说,似乎枚举类型只是可能由实现选择的可能是实现定义的整数类型,其大小足够大以容纳给定枚举列表的枚举值,这些枚举值都必须是int类型

即:

  • enum enum_tag_1 可能是一个可能由实现定义的类型,足够大以包含int 0。这可能是 _Bool 或一个范围为{-1,0,1}的自定义类型。也就是说,赋值 c = 2; 不一定合法。
  • enum enum_tag_2 可能是一个可能由实现定义的类型,足够大以包含int 256。这意味着它至少可以容纳范围 [0, 1, ..., 511](但不一定是 512)。因此赋值 d = language(值 0)始终合法。d = c; 在上面的代码中是合法的,但从 enum enum_tag_1 赋值到 enum enum_tag_2 并不一定允许所有值,因为前者可能是 signed 而后者可能是 unsigned
  • 即使 enum enum_tag_1enum enum_tag_2 的枚举常量只能具有适合于类型 int 的值,但实现可以使这些类型变得非常大,以便可以将非常大的数字分配给它们,就像这样:
    c = −9223372036854775808;
    d = 18446744073709551615;

这依赖于整数类型始终具有形式为 [0, 2N-1](无符号)或 [-2N+1, 2N-1](有符号)的范围,其中 N 是值位数(C17标准,草案,6.2.6.2 ¶1-2)。 (参见这个相关问题。)

但一般情况呢?似乎enum的互相转换取决于实现选择的类型的范围,这使得答案取决于实现

英文:

Under which conditions can one convert from one enumerated type to another?

Let's consider the following code:

#include &lt;stdio.h&gt;

int main(void) {
  enum enum_tag_1 { language } c = language;
  enum enum_tag_2 { lawyer = 256 } d = lawyer;
  d = c;
  c = 2;
  printf(&quot;%d\n&quot;, c);
  printf(&quot;%d\n&quot;, d);
}

We can now ask questions such as whether statements like c = 2; and d = c; are guaranteed to work.

The C17 standard says (draft, 6.7.2.2):

> ¶3 The identifiers in an enumerator list are declared as constants that have type int. [...]
> ¶4 Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined,[] but shall be capable of representing the values of all the members of the enumeration. [...]

That is, it seems that an enumerated type is simply a possibly implementation-defined integer type chosen by the implementation to be large enough for the given enumerator list, whose enumerators must all be of type int.

That is:

  • enum enum_tag_1 is a possibly implementation-defined type large enough to contain the int 0. This could be _Bool or a custom type whose range is {-1, 0, 1}. That is, the assignment c = 2; is not necessarily legal.
  • enum enum_tag_2 is a possibly implementation-defined type large enough to contain the int 256. This implies that it can at least hold the range [0, 1, ..., 511] (but not necessarily 512). Therefore the assignment d = language (value 0) is always legal. d = c; is legal in the above code, but assigning from enum enum_tag_1 to enum enum_tag_2 is not necessarily allowed for all values, since the former might be signed and the latter might be unsigned.
  • Even though the enumeration constants for enum enum_tag_1 and enum enum_tag_2 can only have values fitting into the type int, it is legal for the implementation to makes these types so big that one can assign very large numbers to them, like this:
    c = −9223372036854775808;
    d = 18446744073709551615;

This relies on the fact that integer types always have ranges of the form [0, 2<sup>N</sup>-1] (unsigned) or [-2<sup>N</sup>(+1), 2<sup>N</sup>-1] (signed), where N is the number of value bits (C17 standard, draft, 6.2.6.2 ¶1-2). (See this related question.)

But what about the general case? It seems that inter-convertibility of enums depends on the ranges of the types which the implementation chooses, making the answer implementation-dependent.

答案1

得分: 3

NordicSemi在这个主题上发表了一篇观点文章,链接在这里:https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.2-dev1/tfm/docs/technical_references/design_docs/enum_implicit_casting.html

...其中我引用了以下内容:

根据C99标准1:

  • §6.2.5, 16:枚举包括一组命名的整数常量值。每个不同的枚举构成一个不同的数字类型。
  • §6.7.2.2, 2:定义枚举常量值的表达式必须是可表示为int的整数常量表达式。
  • §6.7.2.2, 3:枚举列表中的标识符被声明为具有int类型的常量,可以出现在允许的任何地方。
  • §6.7.2.2, 4:每个枚举类型都应与char、有符号整数类型或无符号整数类型兼容。类型的选择由实现定义,但必须能够表示枚举的所有成员的值。

从C99标准1的这四个引用中,可以得出以下结论:

  • 枚举定义了一个新的类型,应该将其视为这种类型。
  • 枚举常量必须只包含可表示为int的值。
  • 枚举常量具有int类型。
  • 枚举的实际类型可以在char、有符号和无符号int之间选择。编译器在可以表示枚举的所有已声明常量的类型中选择所需的类型。

结论:

  • 将枚举常量分配给int始终是安全的,但最好进行强制转换以显示意图。
  • 当将枚举常量从一个类型强制转换为另一个类型时,应检查常量是否适合目标类型。
  • 当将整数类型(如uint32_t、int32_t等)从整数类型强制转换为枚举类型时,应检查整数的值是否是枚举常量之一。比较应该在两者中较大的类型上进行,以确保不会丢失任何信息。C整数提升应该自动为程序员执行此操作(请参阅§6.3.1.8, 1以获取规则)。
  • 当将枚举类型强制转换为整数类型时,应检查枚举类型的值是否适合整数类型。具有枚举类型的变量的值不限于该类型的枚举常量。枚举常量将始终适合int。
英文:

NordicSemi has an opinion piece on this topic here: https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.2-dev1/tfm/docs/technical_references/design_docs/enum_implicit_casting.html

... from which I quote:

> According to the C99 standard 1:
>
> §6.2.5, 16: An enumeration comprises a set of named integer constant
> values. Each distinct enumeration constitutes a different numerated
> type.
>
> §6.7.2.2, 2: The expression that defines the value of an enumeration
> constant shall be an integer constant expression that has a value
> representable as an int.
>
> §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.
>
> §6.7.2.2, 4: Each enumerated type shall be compatible with char, a
> signed integer type, or an unsigned integer type. The choice of type
> is implementation-defined, but shall be capable of representing the
> values of all the members of the enumeration.
>
> From these four quotes from the C99 standard 1, the following
> conclusions can be made:
>
> - an enumeration defines a new type and should be treated as such
>
> - the enumeration constants must only contains value representable as an int
>
> - the enumeration constants have type int
>
> - the actual type of the enumeration can be between char, signed and unsigned int. The compiler chooses the type it wants among those
> that can represent all declared constants of the enumeration.

The conclusion:

> it is always safe to assign an enumeration constant to an int, but
> might be better to cast to show intent.
>
> when casting an enumeration constant to another type, it should be
> checked that the constant can fit into the destination type.
>
> when casting from an integer type (uint32_t, int32_t, etc) to an
> enumeration type, it should be checked that the integer’s value is one
> of the enumeration constants. The comparison needs to be done on the
> biggest type of the two so that no information is lost. C integer
> promotion should automatically do that for the programmer (check
> §6.3.1.8, 1 for the rules).
>
> when casting from an enumeration type to an integer type, it should be
> checked that the enumeration type value fits into the integer type.
> The value of a variable which has the type of an enumeration type is
> not limited to the enumeration constants of the type. An enumeration
> constant will always fit into an int.

答案2

得分: 2

根据我在C17标准和其他地方针对这个问题的研究(请参见原问题),我认为正确的答案如下:

  • 实现始终可以将任何枚举类型设置为与int相同。
    • 即使这会使枚举类型彼此可互换,各个枚举器列表必须两两不相交;也就是说,它们不能共享枚举常量。
  • 枚举类型的范围必须符合上述形式,即 [0, 2N-1](无符号)或 [-2N(+1), 2N-1](有符号),只要枚举器列表中的枚举常量被涵盖,这些选择都是合法的。
    • 这些范围定义了可以分配给这些enum类型变量的值,无论在相应的枚举器列表中是否有具有这些值的int枚举常量。
  • 如果某个值恰好位于enum B的基础整数类型的范围内,可以将enum A类型的个别值转换为enum B类型。
  • 如果实现已为它们选择了相应的整数类型type_atype_b,其中前者的范围是后者的子集,可以将enum A类型的所有值转换为enum B类型。
    • 可以计算出将enum A类型的枚举常量转换为enum B确保能够工作的条件。细节会很繁琐,但例如,如果enum A的所有枚举常量都位于范围[-511, 511]内,如果该类型的枚举列表中有常量-1511,则可以将所有这些常量(但不一定是enum A类型的所有值)转换为enum B
英文:

Given what I researched for this question in the C17 standard and elsewhere (see original question), I believe that the correct answer is the following:

  • It is always legal for an implementation to make any enumerated type identical to int.
    • Even though this would make the enumerated types interchangeable with each other, the various enumerator lists must be pairwise disjoint; that is, they can't share enumeration constants.
  • The range of the enumerated type must be of the form above, that is [0, 2<sup>N</sup>-1] (unsigned) or [-2<sup>N</sup>(+1), 2<sup>N</sup>-1] (signed), with any of these choices being legal, as long as the enumeration constants from the enumerator list are covered.
    • These ranges define what values can be assigned to variables of these enum types, irrespective of whether there are int enumeration constants with those values in the respective enumerator lists.
  • One can convert an individual value of type enum A to type enum B if it happens to fall into the range of the underlying integer type of enum B.
  • One can convert all values of type enum A to type enum B if the implementation has chosen respective integer types type_a and type_b for them where the range of the former is a subset of the range of the latter.
    • One can work out conditions under which conversions of enumeration constants of type enum A to enum B are guaranteed to work. The details would be cumbersome to write up, but for instance, if all enumeration constants of enum A fall into the range [-511, 511], all these constants (but not necessarily all values of type enum A) are convertible to enum B if that type has constants -1 and 511 in its enumerator list.

huangapple
  • 本文由 发表于 2023年5月21日 14:38:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76298604.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定