【问题标题】:Enum constants behaving differently in C and C++枚举常量在 C 和 C++ 中的行为不同
【发布时间】:2017-06-09 18:02:44
【问题描述】:

为什么会这样:

#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);
}

在 C 中打印 4 8 8 并在 C++ 中打印 8 8 8(在具有 4 字节整数的平台上)?

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

差异的原因是什么?

【问题讨论】:

  • 哪些编译器?我不知道这是否会有所不同,但它可能会。
  • @MarkRansom 它提出了 gcc 但 clang 的行为相同。
  • “在具有 4 字节整数的平台上” 确定类型宽度的不仅仅是平台,而是编译器。这可能就是一切。 (根据 Keith 的回答,实际上不是,但一般要注意这种可能性)
  • @PSkocik:并不是真正的改变,只是这个问题发现了cc++ 的有效使用(询问为什么某些代码会导致两者之间的不同行为)。也可以:询问如何从 C++ 调用 C 库,以及如何编写可以从 C 调用的 C++。非常不好:询问 C 问题并在“这样它获得更多眼球”上加上 C++ 标签。也不好:问一个 C++ 问题并作为事后的想法“确保你也回答 C”。 (对于通常的抱怨者——非常不好:将 C++ 标记更改为 C 标记,因为代码使用了两种标准中都存在的函数)

标签: c++ c


【解决方案1】:

C11 - 6.7.2.2/2

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

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

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

GCC 似乎有一些错误,它未能生成诊断消息。 (由Grzegorz Szpetkowski 指出answer 中的错误

【讨论】:

  • “未定义行为”是运行时效果。 sizeof 是编译时运算符。这里没有UB,就算有也不会影响sizeof
  • 您应该找到标准引用,即不能放入 int 的枚举数是 UB。我对这一声明高度怀疑,我的投票将保持稳定的-1,直到这一点得到澄清。
  • @Sergey:C 标准实际上确实说过“定义枚举常量值的表达式应该是一个整数常量表达式,其值可表示为 int。”但违反这将是违反约束,需要诊断,而不是 UB。
  • @hacks:是吗?这是一个约束违规,并且“如果预处理翻译单元或翻译单元包含违反任何语法规则或约束的行为,则符合要求的实现应产生至少一个诊断消息(以实现定义的方式标识),即使该行为也是明确指定为未定义或实现定义。”
  • 溢出和截断是有区别的。溢出是当你有一个算术运算产生的值对于预期的结果类型来说太大了,并且有符号溢出是 UB。截断是指您的值对于目标类型而言太大(例如short s = 0xdeadbeef),并且行为是实现定义的。
【解决方案2】:

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

C11 - 6.7.2.2 枚举说明符

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

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

我会说,如果它的值太大,只扩展其中一个枚举器的大小是有意义的。


另一方面,在 C++ 中,所有枚举器的类型都为 enum 所声明的类型。

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

【讨论】:

  • 这是一个编译器扩展,但未能生成诊断是不合格的。
【解决方案3】:

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

C99 和 C11 中的第 6.7.2.2 节说:

约束:

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

编译器诊断是强制性的,因为它违反了约束,请参阅 5.1.1.3:

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

【讨论】:

    【解决方案4】:

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

    约束:定义枚举常量值的表达式应 是一个整数常量表达式,其值可表示为 诠释。

    由于 7.2.5 而在 C++ 中定义:

    如果底层类型不固定,则每个枚举器的类型为 其初始化值的类型: — 如果指定了初始化程序 对于枚举器,初始化值的类型与 表达式和常量表达式应为整数常量 表达式 (5.19)。 — 如果没有为第一个指定初始化器 枚举器,初始化值具有未指定的整数类型。 — 否则初始化值的类型与类型相同 前面的枚举数的初始化值,除非 增量值在该类型中不可表示,在这种情况下 type 是一个未指定的整数类型,足以包含 增值。如果不存在这样的类型,则程序格式错误。

    【讨论】:

    • 它在 C 中不是“未定义”,而是“格式错误”,因为违反了约束。编译器必须生成有关违规的诊断信息。
    • @BenVoigt 感谢您教我不同之处。在答案中修复了它(我之所以这样做,是因为我在其他答案中错过了 C++ 标准的引用)。
    【解决方案5】:

    在 C 中,enum 常量的类型为 int。在 C++ 中,它是枚举类型。

    enum en_e{
        en_e_foo,
        en_e_bar=UINT64_MAX,
    };
    

    在 C 中,这是一个违反约束,需要诊断(如果UINT64_MAX 超过INT_MAX,它很可能会这样做)。 C 编译器可能会完全拒绝该程序,或者它可能会打印一个警告,然后生成一个行为未定义的可执行文件。 (不是 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 位。​​
    • 真正奇怪的是 gcc (5.3.1) 会发出警告 -Wpedantic18446744073709551615ULL 而不是 UINT64_MAX
    • @dascandy: 不,int 必须是有符号类型,因此它必须至少有 65 位才能表示 UINT64_MAX (2**64-1)。
    • @KeithThompson,6.7.2.2 说“枚举器列表中的标识符被声明为具有 int 类型的常量,并且可以出现在任何允许的地方。”我的理解是,单个 C 枚举声明的常量不使用枚举的类型,因此从那里使它们成为不同的类型并不是一件大事(特别是如果它是作为标准的扩展实现的)。
    • @AndrewHenle:en_e_bar 不大于枚举,en_e_foo 更小。枚举变量与最大常量一样大。
    【解决方案6】:

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

    存在 GCC 错误 #71613(2016 年 6 月报告),它指出一些有用的警告会被宏静音。

    当来自系统标头的宏时,有用的警告似乎被静音了 被使用。例如,在下面的示例中,警告会很有用 对于两个枚举,但只显示一个警告。同样可以大概 发生其他警告。

    当前的解决方法可能是在宏前面加上一元 + 运算符:

    enum en_e {
       en_e_foo,
       en_e_bar = +UINT64_MAX,
    };
    

    使用 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-15
      • 2011-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-26
      • 1970-01-01
      相关资源
      最近更新 更多