【问题标题】:Is using an union in place of a cast well defined?使用联合来代替强制转换是否定义明确?
【发布时间】:2011-09-02 10:07:39
【问题描述】:

今天早上我与一位同事讨论了检测字节顺序的“编码技巧”的正确性。

诀窍是:

bool is_big_endian()
{
  union
  {
    int i;
    char c[sizeof(int)];
  } foo;


  foo.i = 1;
  return (foo.c[0] == 1);
}

对我来说,union 的这种用法似乎是不正确的,因为设置一个联合成员并读取另一个成员没有明确定义。但我不得不承认,这只是一种感觉,我缺乏实际的证据来加强我的观点。

这个技巧正确吗?谁在这里?

【问题讨论】:

  • 至少 gcc 的人推荐这个用于类型双关语而不是强制转换为另一种类型,这更不明确:-)
  • @drhirsch:我同意。但如果只是变相的演员表,难道不应该一样糟糕吗?
  • 这不仅仅是伪装的演员。它具有关于联合元素的内存位置的更精确的语义作为简单的强制转换。更多关于 gcc.gnu.org/onlinedocs/gcc-4.6.0/gcc/…
  • 确实,你永远不会得到一个完全定义的解决方案来解决这样的废话:P
  • #define BIG_ENDIAN 不是比这更容易吗?无论如何,您仍然必须在大多数系统上检测操作系统和其他东西。

标签: c++ standards endianness unions


【解决方案1】:

您的代码不可移植。它可能适用于某些编译器,也可能不适用。

当您尝试访问联合的非活动成员时,您的行为未定义是正确的[就像给定代码的情况一样]

$9.5/1

在一个联合中,任何时候最多可以有一个数据成员处于活动状态,即任何时候最多可以有一个数据成员的值存储在一个联合中.

所以foo.c[0] == 1 不正确,因为此时c 不活动。如果您认为我错了,请随时纠正我。

【讨论】:

  • 我在 C++0x 标准中找不到引用,我找到了一个部分解除此限制的案例 struct A { int a; char b; }; struct B { int a; double c; }; union U { A a; B b; }; 并说访问 u.a.a 或 @ 987654327@ 总是可能的(因为它是 A 和 B 的公共前缀序列),但没有任何不确定性:/
  • @Matthieu M. :这就是我认为相关的是这个上下文 $9.5 "In a union, at most one of the data members can be active at any time, that is, the value of at most one of the data members can be stored in a union at any time." 尝试访问联合的非活动成员会导致未定义的行为。
  • 肯定是不可移植的。但这并不意味着它没有很好的定义
  • @drhirsch:定义明确,然后定义明确。就标准而言,行为是未定义的。当然,个别编译器可能会提供额外的保证。
  • MISRA-C 标准禁止联合,因为存在各种可移植性问题,主要涉及对齐和填充。例如,他们禁止使用“变体”。但是,它们对规则做了一个例外,并允许联合使用一个 char 数组进行数据打包,其大小与联合中的其他数据类型相同。这是有道理的,因为数据打包可能是 C/C++ 语言中 union 关键字的唯一合理使用。如果这将是未定义的行为,那将有效地使联合无用。如果是这种情况,它们应该已从语言中删除。
【解决方案2】:

函数应该命名为 is_little_endian。我认为你可以使用这个联合技巧。或者也可以转换为 char。

【讨论】:

    【解决方案3】:

    您说得对,该代码没有明确定义的行为。以下是便携式操作的方法:

    #include <cstring>
    
    bool is_big_endian()
    {
        static unsigned const i = 1u;
        char c[sizeof(unsigned)] = { };
        std::memcpy(c, &i, sizeof(c));
        return !c[0];
    }
    
    // or, alternatively
    
    bool is_big_endian()
    {
        static unsigned const i = 1u;
        return !*static_cast<char const*>(static_cast<void const*>(&i));
    }
    

    【讨论】:

    • 我认为即使这样也不能在所有情况下都是可移植的。它将在中位结束架构上返回 true,该架构将在第三个字节中以 1 结束。那么为什么还要为此烦恼,我仍然会使用一个联合,知道它可能并不完美,但至少不会在错误的假设下我的代码在所有情况下都可以工作
    【解决方案4】:

    代码具有未定义的行为,尽管一些(大多数?)编译器会 至少在有限的情况下定义它。

    标准的意图reinterpret_cast被用于 这。然而,这种意图并没有得到很好的表达,因为标准 无法真正定义行为;不想定义它什么时候 硬件不支持它(例如,由于对齐问题)。和 很明显,你不能只在两个之间reinterpret_cast 任意类型并期望它能够工作。

    从实施质量的角度来看,我希望 union 技巧和 reinterpret_cast 工作,如果 unionreinterpret_cast 在同一个功能块中; union 应该 只要编译器可以看到最终类型是union,就可以工作 (尽管我使用了并非如此的编译器)。

    【讨论】:

      【解决方案5】:

      不要这样做,最好使用以下内容:

      #include <arpa/inet.h>
      //#include <winsock2.h> // <-- for Windows use this instead
      
      #include <stdint.h>
      
      bool is_big_endian() {
        uint32_t i = 1;
        return i == htonl(i);
      }
      

      解释:

      htonl 函数将 u_long 从主机转换为 TCP/IP 网络字节顺序(大端序)。


      参考资料:

      【讨论】:

      猜你喜欢
      • 2011-02-22
      • 1970-01-01
      • 2016-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-08
      • 2018-09-11
      相关资源
      最近更新 更多