【问题标题】:Endianness in constexprconstexpr 中的字节顺序
【发布时间】:2016-10-18 09:28:40
【问题描述】:

我想创建一个返回系统字节序的 constexpr 函数,如下所示:

constexpr bool IsBigEndian()
{
    constexpr int32_t one = 1;
    return (reinterpret_cast<const int8_t&>(one) == 0);
}

现在,由于函数将在编译时而不是在实际的目标机器上执行,C++ 规范提供什么保证来确保返回正确的结果?

【问题讨论】:

  • 好问题!这曾经是一个问题(其他语言的其他编译器)w.r.t.浮点常量/表达式和交叉编译器(回到有不同浮点格式的时代)。我想知道 C++ 标准答案。
  • 很难有任何实际意义。我无法想象两种架构可以执行相同的编译源代码,但字节序不同。
  • @πάντα ῥεῖ:根本不是这个问题。这是关于检测字节序的特定情况。
  • @SergeyA 交叉编译呢?
  • @RichardCritten,我想,不同架构的交叉编译必须在所有意图和目的上尊重这种架构字节序……但我承认这是一个好点。

标签: c++ language-lawyer endianness constant-expression


【解决方案1】:

正如 Barry 所说,您的代码不是合法的 C++。但是,即使您去掉了 constexpr 部分,它仍然不是合法的 C++。您的代码违反了严格的别名规则,因此代表未定义的行为。

确实,在 C++ 中没有办法在不调用未定义行为的情况下检测对象的字节顺序。将其转换为 char* 不起作用,因为该标准不需要大端或小端顺序。因此,虽然您可以通过一个字节读取数据,但您无法从该值合法地推断出任何内容。

通过union 键入双关语会失败,因为您根本不允许在C++ 中通过union 键入双关语。即使你这样做了......再次,C++ 不会将实现限制为大端或小端顺序。

就 C++ 作为标准而言,无论是在编译时还是运行时,都无法检测到这一点。

【讨论】:

  • 但是为什么呢?这不是未定义的行为!这是未定义的行为,它们是不同的。 OP 正是这样做的——他正在访问底层表示(这是允许的),而不是根据表示做出决定。我在这里没有看到 UB。
  • @SergeyA:未定义的行为按定义未定义。这正是“未定义”一词的含义:未定义。此外,问题是询问 standard 对此类代码的规定。答案是(除了不合法之外)标准没有定义这个函数的行为。
  • 我不同意。未定义与未定义的良好测试如下 - 它可以产生鼻恶魔吗? (或者,更现实地说,编译器可以生成在逻辑上不等同于 C++ 代码的代码)。在未定义行为的情况下,它可以。在未定义行为的情况下,它不能。特别是,在特定情况下,不会召唤鼻恶魔,而是根据布尔值的表示返回 1 或 0。
  • @SergeyA:这个问题被标记为“语言律师”。这意味着唯一重要的“未定义与未定义的测试”是 C++ 规范 所说的。此外,无论标签如何,这都是对未定义行为的 terrible 定义。已定义或未定义的是客观标准;确实,这就是制定标准的全部意义
  • @NicolBolas:如果实现文档表明将 1.00f 存储到 float 中会导致它保存字节 0x80 00 00 00,并且还指定将字节值 0x80 00 00 00 存储到 long 中使其保持 -2147483648,然后在没有任何相反规则的情况下,这些事实将有效地将 1.00f 存储到联合中的行为定义为 float 并将其读取为 long。只有禁止此类行为的规则才能使其未定义。
【解决方案2】:

没有。事实上,这个程序是错误的。来自 [expr.const]:

条件表达式 e 是一个核心常量表达式,除非 e 的求值遵循 抽象机 (1.9),将评估以下表达式之一:
— [...]
——reinterpret_cast
— [...]

并且,来自 [dcl.constexpr]:

对于既不是默认值也不是模板的constexpr 函数或constexpr 构造函数(如果没有参数) 值的存在使得函数或构造函数的调用可以是评估的子表达式 核心常量表达式(5.20),或者,对于构造函数,某个对象的常量初始化器(3.6.2), 程序格式错误;无需诊断。


这样做的方法只是希望您的编译器足够好,可以为您的机器的字节序提供宏。例如,在 gcc 上,我可以使用 __BYTE_ORDER__:

constexpr bool IsBigEndian() {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    return false;
#else
    return true;
#endif
}

【讨论】:

  • 这不是它格式错误的原因。它格式错误的真正原因是违反了严格的别名规则。你引用的只是意味着IsBigEndian 不会是constexpr
  • @uhohsomebodyneedsapupper 违反严格别名是 UB。不能作为 const 表达式计算的 constexpr 函数格式不正确。
  • @uhohsomebodyneedsapupper 没有必要违反。 int8_t 可能是 char,并且允许通过 char* 进行别名。
  • @SergeyA:别名是允许的,但唯一允许您通过该指针访问对象的方法是将其复制到别处。该标准不保证那里存储的价值。它只保证如果您通过char* 复制int32_t 大小的内存块,那么您将拥有有效int32_t 的值表示。
  • @NicolBolas,它不代表实际值,但这正是 OP 想要检查的。它不是 UB,它允许访问底层表示,并且 OP 正在检查表示以得出结论。
猜你喜欢
  • 2010-12-07
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
  • 2010-11-14
  • 1970-01-01
  • 2016-08-24
  • 2013-05-24
  • 1970-01-01
相关资源
最近更新 更多