C++中的枚举是有符号的还是无符号的?

119

C++枚举类型是有符号还是无符号的?通过推断,检查输入是否小于等于最大值并省略大于等于最小值(假设你从0开始递增1)来验证输入是否安全吗?


当我们在需要枚举类型的符号的上下文中使用它时,实际上是在隐式地将枚举转换为整数类型。C++03标准指出,这是通过整数提升来完成的,与枚举的底层类型无关。因此,我不明白为什么这里的每个答案都提到底层类型未被定义?我在这里描述了预期的行为:https://dev59.com/cYHba4cB1Zd3GeqPTZz6 - JavaMan
10个回答

111

让我们看一下C++03标准(ISO/IEC 14882:2003)文档中7.2-5(枚举声明)的规定:

枚举类型的底层类型是可以表示所定义的所有枚举值的整数类型。除非枚举类型的某个枚举常量的值不能用int或unsigned int类型表示,否则实现可以自行选择作为枚举类型底层类型的整数类型,但是底层类型的大小不得超过int。

简而言之,编译器会根据需要选择底层类型(如果您的枚举值中有负数,则将使用带符号类型)。


我们如何避免编译器的猜测,并告诉它在所有枚举值都是小的正整数时使用底层的无符号类型?(我们正在捕获一个 UBsan 发现,因为编译器选择了 int,而 int 会发生溢出。该值是无符号和正数,我们的使用依赖于无符号包装来提供递减或“负步幅”)。 - jww
@jww - 这将取决于您使用的编译器。由于标准没有规定底层类型,并将其留给实现,因此需要查看工具的文档,以确定是否可能存在此选项。如果您想在代码中保证某种行为,为什么不将您在表达式中使用的枚举成员强制转换一下呢? - ysap

65

你不应该依赖于任何特定的表示方式。请阅读下面的链接。此外,标准规定枚举类型使用哪种整数类型作为底层类型是由实现定义的,除非某些值无法适应int或unsigned int。

简而言之:你不能指望一个枚举类型是有符号的还是无符号的。


32
Michael Burr的回答(引用标准)实际上意味着,如果您将枚举值定义为负数,则可以依赖其为有符号类型,因为该类型能够“表示在枚举中定义的所有枚举器值”。 - Samuel Harmer

27

你不应该依赖它们是有符号的还是无符号的。如果你想要明确指定它们是有符号或无符号的,可以使用以下代码:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

12
只有在未来的 C++0x 标准中才有。 - dalle
3
微软编译器也支持带类型的枚举(enum)。文档链接:http://msdn.microsoft.com/en-us/library/2dzy4k6e(v=vs.80).aspx - teodozjan

17

根据标准,枚举类型的底层类型是由实现定义的,不能保证其为有符号或无符号。大多数实现中,其为有符号整数。

在C++0x中将添加强类型枚举,允许您指定枚举类型的类型,例如:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

即使现在,您仍然可以通过将枚举用作变量或参数类型来实现一些简单的验证,就像这样:

enum Fruit { Apple, Banana };

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit
                    // even though it has the same value as banana.

我认为你的第二个例子有点混淆了 :) - Miral

8

即使有些旧的答案获得了44个赞,但我不同意它们。简而言之,我认为我们不应该关心枚举类型的底层类型。

首先,在C++03标准中,枚举类型是一种独立的类型,没有符号的概念。因此,从C++03标准开始,dcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

当我们谈论枚举类型的符号时,比如使用 < 运算符对比两个枚举操作数时,实际上是在隐式将枚举类型转换为某个整型。 关键是这个整型的符号。并且在将枚举转换为整型时,以下语句适用:

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

而且,显然,枚举类型的底层类型与整数提升无关。因为标准将整数提升定义为这样:
4.5 Integral promotions conv.prom
.. An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2: int, unsigned int, long, or unsigned long.

因此,枚举类型是成为signed int还是unsigned int取决于signed int是否可以包含所有定义的枚举器的值,而不是枚举的基础类型。

请参阅我的相关问题将C++枚举类型转换为整数类型后符号不正确


当你使用-Wsign-conversion编译时,它是很重要的。我们使用它来帮助捕捉代码中意外的错误。但是要为引用标准和指出枚举没有与之关联的类型(如signedunsigned)加一点分。 - jww

5
在未来,使用C++0x,强类型枚举将会可用,并具有多个优点(例如类型安全、显式底层类型或显式作用域)。通过这种方式,您可以更好地确保类型的符号。

5

编译器可以决定枚举类型是有符号的还是无符号的。

另一种验证枚举类型的方法是使用枚举本身作为变量类型。例如:

enum Fruit
{
    Apple = 0,
    Banana,
    Pineapple,
    Orange,
    Kumquat
};

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit even though it has the same value as banana.

4
除了其他人已经提到的关于signed/unsigned的内容外,以下是标准对枚举类型范围的说明:
7.2(6):"对于一个枚举类型,其中e(min)是最小的枚举值,e(max)是最大的枚举值,枚举的值是底层类型在范围b(min)到b(max)内的值,其中b(min)和b(max)分别是可以存储e(min)和e(max)的最小位域的最小值和最大值。可以定义一个枚举,它具有任何一个枚举器都没有定义的值。"
例如:
enum { A = 1, B = 4};

定义了一个枚举类型,其中e(min)为1,e(max)为4。如果底层类型是有符号整数,则所需的最小位域为4位,如果您的实现中的整数是二进制补码,则枚举的有效范围为-8到7。如果底层类型是无符号整数,则它具有3位,范围为0到7。如果您关心这个问题,请查阅编译器文档(例如,如果您想将除枚举器以外的整数值转换为枚举类型,则需要知道该值是否在枚举范围内 - 如果不在,则得到的枚举值是未指定的)。
这些值是否是您函数的有效输入可能与它们是否是枚举类型的有效值不同。您的检查代码可能更关注前者而不是后者,在此示例中应至少检查>=A和<=B。

4

使用 std::is_signed<std::underlying_type 检查 + 作用域枚举默认为 int

https://en.cppreference.com/w/cpp/language/enum 表示:

main.cpp

#include <cassert>
#include <iostream>
#include <type_traits>

enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};

int main() {
    // Implementation defined, let's find out.
    std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;

    // Guaranteed. Scoped defaults to int.
    assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));

    // Guaranteed. We set it ourselves.
    assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}

GitHub上游

编译和运行:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

输出:

0

在Ubuntu 16.04和GCC 6.4.0上进行测试。


-1

虽然以上一些答案可以说是正确的,但它们并没有回答我的实际问题。编译器(gcc 9.3.0)对以下内容发出了警告:

enum FOO_STATUS {
    STATUS_ERROR = (1 << 31)
};

有关使用的警告已发出:

unsigned status = foo_status_get();
if (STATUS_ERROR == status) {

(除了这段代码是错误的事实...不要问。)
当正确询问时,编译器不会发出错误。
enum FOO_STATUS {
    STATUS_ERROR = (1U << 31)
};

请注意,1U 会使表达式变为无符号。

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