枚举常量在C和C++中表现不同

PSk*_*cik 81 c c++

为什么这样:

#include <stdio.h>
#include <limits.h>
#include <inttypes.h>

int main() {
    enum en_e {
        en_e_foo,
        en_e_bar = UINT64_MAX,
    };
    enum en_e e = en_e_foo;
    printf("%zu\n", sizeof en_e_foo);
    printf("%zu\n", sizeof en_e_bar);
    printf("%zu\n", sizeof e);
}
Run Code Online (Sandbox Code Playgroud)

4 8 8用C和8 8 8C++ 打印(在4字节整数的平台上)?

我的印象是,UINT64_MAX赋值将强制所有枚举常量至少为64位,但en_e_foo在纯C中保持为32.

这种差异的理由是什么?

Kei*_*son 80

在C中,enum常量是类型int.在C++中,它是枚举类型.

enum en_e{
    en_e_foo,
    en_e_bar=UINT64_MAX,
};
Run Code Online (Sandbox Code Playgroud)

在C中,这是一个约束违规,需要诊断(如果 UINT64_MAX超过INT_MAX,它很可能会这样做).AC编译器可以完全拒绝该程序,或者它可以打印警告然后生成其行为未定义的可执行文件.(并非100%明确表示违反约束的程序必然具有未定义的行为,但在这种情况下,标准并未说明行为是什么,因此仍然是未定义的行为.)

gcc 6.2没有对此发出警告.铿锵的.这是gcc中的一个错误; 当使用来自标准头的宏时,它会错误地禁止某些诊断消息.感谢Grzegorz Szpetkowski查找错误报告:https://gcc.gnu.org/bugzilla/show_bug.cgi?id = 71613

在C++中,每个枚举类型都有一个底层类型,它是一些整数类型(不一定int).此基础类型必须能够表示所有常量值.因此,在这种情况下,两个en_e_fooen_e_bar的类型的en_e,它必须是至少64个位宽,即使int是窄.

  • 快速注意:对于`UINT64_MAX`不超过`INT_MAX`要求`int`至少为65位. (10认同)
  • 真正奇怪的是gcc(5.3.1)用`-Wpedantic`和`18446744073709551615ULL`发出警告但不用'UINT64_MAX`发出警告. (10认同)
  • @dascandy:不,`int`必须是有符号的类型,所以它必须至少为65位才能能够代替`UINT64_MAX`(2**64-1). (4认同)
  • @AndrewHenle:`en_e_bar`不比enum大,`en_e_foo`更小.枚举变量与最大常量一样大. (2认同)

Ben*_*igt 25

该代码首先不是有效的C.

C99和C11中的第6.7.2.2节说:

约束:

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

编译器诊断是必需的,因为它是约束违规,请参阅5.1.1.3:

如果预处理转换单元或转换单元包含违反任何语法规则或约束的情况,则符合要求的实现应生成至少一条诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实现 - 定义.


Hol*_*Cat 23

C中,虽然a enum被认为是一个单独的类型,但枚举器本身总是具有类型int.

C11 - 6.7.2.2枚举说明符

3枚举器列表中的标识符声明为类型为int的常量...

因此,您看到的行为是编译器扩展.

我认为如果其值太大,只扩展其中一个枚举器的大小是有意义的.


另一方面,在C++中,所有枚举器都具有enum声明它们的类型.

因此,每个枚举器的大小必须相同.因此,enum扩展整数的大小以存储最大的枚举器.

  • 它是编译器扩展,但无法生成诊断是不一致的. (11认同)

Grz*_*ski 16

正如其他人指出的那样,由于违反约束,代码格式不正确(在C中).

有GCC错误#71613(2016年6月报告),其中指出一些有用的警告用宏来沉默.

当使用来自系统头的宏时,有用的警告似乎被静音.例如,在下面的示例中,警告对两个枚举都有用,但只显示一个警告.其他警告也可能发生同样的情况.

目前的解决方法可能是使用一元运算+符预先添加宏:

enum en_e {
   en_e_foo,
   en_e_bar = +UINT64_MAX,
};
Run Code Online (Sandbox Code Playgroud)

使用GCC 4.9.2在我的机器上产生编译错误:

$ gcc -std=c11 -pedantic-errors -Wall main.c 
main.c: In function ‘main’:
main.c:9:20: error: ISO C restricts enumerator values to range of ‘int’ [-Wpedantic]
         en_e_bar = +UINT64_MAX
Run Code Online (Sandbox Code Playgroud)


hac*_*cks 12

C11 - 6.7.2.2/2

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

en_e_bar=UINT64_MAX是违反约束,这使上述代码无效.应通过确认C11草案中所述的实施来产生诊断信息:

如果预处理翻译单元或翻译单元包含违反任何语法规则或约束,则符合要求的实现应至少生成一条诊断消息(以实现定义的方式标识),[...]

似乎GCC有一些错误,它无法产生诊断信息.(错误指出在回答格热戈日Szpetkowski

  • "未定义的行为"是一种运行时效果.`sizeof`是一个编译时运算符.这里没有UB,即使有,也不会影响`sizeof`. (8认同)
  • @Sergey:C标准确实说"定义枚举常量值的表达式应该是一个整数常量表达式,其值可以表示为int." 但违反此规定将违反约束,需要诊断,而不是UB. (3认同)
  • @haccks:对吗?这是违反约束的,并且"如果预处理转换单元或转换单元包含违反任何语法规则或约束的条件,则符合条件的实现应生成至少一条诊断消息(以实现定义的方式标识),即使该行为也是明确指定为未定义或实现定义." (3认同)
  • 您应该找到不适合int的枚举数是UB的标准引用.我对这一声明持高度怀疑态度,我的投票将保持稳定-1,直到这一情况得到澄清. (2认同)
  • 溢出和截断之间存在差异.溢出是指您的算术运算产生的值太大而不能达到预期的结果类型,并且有符号溢出是UB.截断是指您的目标类型的值太大(如`short s = 0xdeadbeef`),并且行为是实现定义的. (2认同)

PSk*_*cik 5

我查看了标准,由于6.7.2.2p2,我的程序似乎是C中的约束违规:

约束:定义枚举常量值的表达式应为整数常量表达式,其值可表示为int.

并且由于7.2.5在C++中定义:

如果基础类型不是固定的,则每个枚举器的类型是其初始化值的类型: - 如果为枚举器指定了初始化器,则初始化值与表达式具有相同的类型,而constant-expression应为整数常数表达式(5.19). - 如果没有为第一个枚举数指定初始值设定项,则初始化值具有未指定的整数类型. - 否则初始化值的类型与前一个枚举器的初始化值的类型相同,除非递增的值在该类型中不可表示,在这种情况下,类型是一个未指定的整数类型,足以包含递增的值.如果不存在此类型,则程序格式错误.

  • 它在C中不是"未定义的",它是"形式错误的",因为违反了约束.编译器必须生成有关违规的诊断. (3认同)