【问题标题】:Is it possible to skip bytes when using union, in c++?在 c++ 中使用联合时是否可以跳过字节?
【发布时间】:2018-08-22 08:20:40
【问题描述】:

我正在尝试对几个结构使用联合,特别是将 2D(4 x 4)浮点数组与 4 个 Vector4 对齐。

据我了解,union 从第一个开始查看顺序字节。如果我有一个 4x4 数组并且想要获得一整列,我需要跳过 16 个字节才能到达下一行。

例子:

Matrix = {{a, b, c, d},
          {e, f, g, h},
          {h, i, j, k},
          {l, m, n, o};

如果给出的数据使得一维数组为{a,b,c,d,e,..., o},我将如何获得最后一列 (d,h,k,o)?

那么我将如何获得字节:12->16、28->32、44->48、60->64?

返回最后一行的代码(不是我想要的)是:

//matrix.h
struct Matrix4 {
    union{
        float values[4][4];
        Vector4 column[4];
    };

    //matrix methods etc
}

//main.cpp
Matrix4 position = Matrix3::translation(Vector3(1,2,3));
Vector4 column = position.column[2];
std::cout << (column) << std::endl;

输出矩阵将是(不记得如何使用 LaTex 格式对不起):

| 1 0 0 1 |
| 0 1 0 2 |
| 0 0 1 3 |
| 0 0 0 1 |

所以我想要的专栏是:

|1|
|2|
|3|
|1|

工会给出:

|0|
|0|
|0|
|1|

(最后一行,不是列)。

将我的代码重构为使用一维数组来获取顺序字节会更容易,还是应该更改矩阵的构造方式?或者我可以在联合中处理其他事情吗?

编辑:我的问题与other question 不同,因为我已经创建了一个矩阵类并分配了内存。我的问题着眼于“联合”的功能及其限制,以及使用我定义的结构 'Vector4' 作为在矩阵中表示某些数据的一种方式。我正在研究跳过字节以接收不直接相互跟随的数据的可能性,而不是如何分配所述字节。

【问题讨论】:

  • 联合在任何时候只保留其中一个值。如果您想对同一内存进行 1d 和 2d 访问,请使用 std::vector 并写入 at(index)at(row,col) 访问器
  • 不要在 C++ 中使用 union 来给不同类型起别名,这是未定义的行为。
  • C++ Matrix Class的可能重复
  • 未定义行为:en.cppreference.com/w/cpp/language/ub union 是错误的工具,union 用于存储其中一个数据成员,而不是同时存储两个成员,也不使用另一个成员访问一个成员的数据。
  • 使用自定义iterators 来遍历列对于很多情况来说是一个很好的解决方案。

标签: c++ arrays multidimensional-array unions


【解决方案1】:

这是一种相当常见的模式,但却是无效的。它很受欢迎,因为它很简单,而且主流编译器传统上允许并支持它。

但是您根本无法以明确定义的方式以这种方式使用联合。联合不是一种将一种类型的数据重新解释为另一种类型的数据的方法。哎呀,reinterpret_cast 甚至不能让你到达那里:为了安全地做到这一点,你必须保持std::copy来回传输你的字节。唯一安全的情况是使用char[](或unsigned char[],或std::byte[]),它允许对任意内存块进行别名:因此,联合对于快速字节检查很有用。除此之外,它们仅在您一次不需要使用多个工会成员时才有用。

您真正应该做的是选择一个布局/格式,然后在顶部添加您自己的operator[](或其他功能)变体,以提供不同的界面和解释数据的方式呼叫站点。

做对了,不会有任何开销。

巧妙地做到这一点,您将能够以您需要的方式“即时”应用您的转换。提供一种代理类型,一种迭代器,它不仅以线性序列进行迭代,而且还以您需要的修改后的序列进行迭代。

这个逻辑可以全部封装在你的Matrix4类型中。

【讨论】:

  • 具有特殊规则的类型 B 允许另一个类型的对象的生命周期与 B 类型数组的对象的生命周期重叠,或者允许左值到右值B 类型表达式的转换,即使泛左值结果不是动态类型与 B 相关的对象,是 charunsigned charstd::byte
【解决方案2】:

您不能同时访问工会的两个不同成员。来自cppreference的例子:

#include <string>
#include <vector>

union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {} // needs to know which member is active, only possible in union-like class 
};          // the whole union occupies max(sizeof(string), sizeof(vector<int>))

int main()
{
    S s = {"Hello, world"};
    // at this point, reading from s.vec is undefined behavior
[...]

即在分配str 成员后,您将无法访问vec 成员。

【讨论】:

    【解决方案3】:

    联合仅用于为不同的对象保留一个内存区域。但在每一瞬间,只能有一个活着的物体。所以作为一般规则,如果你有一个有两个成员ab 的联合,如果你已经初始化了a 并且a 是联合的活动成员,你永远不应该尝试读取@ 的值987654325@.

    但是在ab 共享一个共同的初始序列的情况下,该规则有一个例外:在这种情况下,标准确保ab 的内存表示足够相似,因此您可以通过b 访问a 的值。

    下面的代码示例显示了通过非活动成员访问联合值的定义行为:

        struct Vector4 {
           float values[4];
           };
        //I propose you two kinds of matrix, as is usualy done in linear algebra
        //left_matrix are efficient for matrix multiplication if they are on the
        //left side of the * symbol.
        struct left_matrix{
           Vector4 rows[4];
           //for this simple example we index using call operator:
           float& operator()(int i,int j){
               return rows[i].values[j];
               }
           };
    
        struct right_matrix{
           Vector4 columns[4];
           float& operator()(int i, int j){
              return columns[j].values[i];//the actual memory transposition is here.
              }
            };
    
        right_matrix 
        transpose_left_matrix(const left_matrix& m){
            union{
              left_matrix lm;
              right_matrix rm;
              };
            lm = m; //lm is the active member
            return rm; //but we can access rm
            }
    

    【讨论】:

      【解决方案4】:

      因此,我特此决定添加一个全新的答案,其中不涉及我认为的未定义行为。主演力类型转换:

      #include <cstdlib>
      #include <iostream>
      #include <initializer_list>
      
      using namespace std;
      
      // Matrix4f dimension
      #define DIM_MAT4        (4)
      
      // Data structures
      typedef struct _Vector4f
      {
          float entries[DIM_MAT4];
      
          void
          println() const;
      
      } Vector4f;
      
      typedef struct _Matrix4f
      {
          _Matrix4f(initializer_list<float> fl) : _trans_cache(NULL), _renew_cache(true)
          {
              copy(fl.begin(), fl.end(), entries);
          }
      
          _Matrix4f() : entries{.0f, .0f, .0f, .0f,
                          .0f, .0f, .0f, .0f,
                          .0f, .0f, .0f, .0f,
                          .0f, .0f, .0f, .0f}, _trans_cache(NULL), _renew_cache(true) {}
      
          ~_Matrix4f() { delete _trans_cache; }
      
          float &
          operator() (int, int);
      
          const float &
          operator() (int, int) const;
      
          const Vector4f *
          getRow(int) const;
      
          const Vector4f *
          getCol(int) const;
      
          _Matrix4f *
          transpose() const;
      
          void
          println() const;
      
      private:
      
          float entries[DIM_MAT4 * DIM_MAT4];
      
          // cache the pointer to the transposed matrix
          mutable _Matrix4f *
          _trans_cache;
          mutable bool
          _renew_cache;
      
      } Matrix4f;
      
      Matrix4f *
      Matrix4f::transpose() const
      {
          if (not _renew_cache)
              return this->_trans_cache;
      
          Matrix4f * result = new Matrix4f;
      
          for (int k = 0; k < DIM_MAT4 * DIM_MAT4; k++)
          {
              int j = k % DIM_MAT4;
              int i = k / DIM_MAT4;
      
              result->entries[k] = this->entries[i + DIM_MAT4 * j];
          }
      
          _renew_cache = false;
          return this->_trans_cache = result;
      }
      
      float &
      Matrix4f::operator() (int rid, int cid)
      {
          _renew_cache = true;
          return this->entries[rid * DIM_MAT4 + cid];
      }
      
      const float &
      Matrix4f::operator() (int rid, int cid) const
      {
          return this->entries[rid * DIM_MAT4 + cid];
      }
      
      const Vector4f *
      Matrix4f::getRow(int rid) const
      {
          return reinterpret_cast<Vector4f *>(&(this->entries[rid * DIM_MAT4]));
      }
      
      const Vector4f *
      Matrix4f::getCol(int cid) const
      {
          return this->transpose()->getRow(cid);
      }
      
      void
      Matrix4f::println() const
      {
          this->getRow(0)->println();
          this->getRow(1)->println();
          this->getRow(2)->println();
          this->getRow(3)->println();
      
          cout << "Transposed: " << this->_trans_cache << endl;
      }
      
      void
      Vector4f::println() const
      {
          cout << '(' << (this->entries[0]) << ','
                  << (this->entries[1]) << ','
                  << (this->entries[2]) << ','
                  << (this->entries[3]) << ')' << endl;
      }
      
      // Unit test
      int main()
      {
          Matrix4f mat = {1.0f, 0.0f, 0.0f, 1.0f,
                          0.0f, 1.0f, 0.0f, 2.0f,
                          0.0f, 0.0f, 1.0f, 3.0f,
                          0.0f, 0.0f, 0.0f, 1.0f};
      
          mat.println();
      
          // The column of the current matrix
          // is the row of the transposed matrix
          Vector4f * col = mat.getCol(3);
      
          cout << "Vector:" << endl;
      
          col->println();
      
          cout << "Matrix(2,3) = " << mat(2, 3) << endl;
      
          return 0;
      }
      

      【讨论】:

      • 指针不能保证用null初始化,所以写一个构造函数(因为这个原因,它现在 UB)。另外,请尝试为您的结构编写访问器。如果这样做,则不会将_trans_cache 设置回null,从而导致transpose 返回第一次转置时状态的转置矩阵。您需要一个始终重置它的 setter 方法。也许你意识到了这一点,但是为什么要给出不考虑它的代码呢?
      • @Aziuth 好的,等我吃完晚饭再说。 :P
      • @Aziuth 我回来了。顺便说一句,在以前的版本中,我也没有清理解构器中分配的内存,这会导致内存泄漏。 :P
      • @Aziuth 我不明白你所说的“请尝试为你的结构写一个访问器”是什么意思。如果您有什么好的想法,请您直接修改我的答案,谢谢。
      • 对我谈到的内容进行了编辑,并在合适的地方插入了 const 关键字。
      【解决方案5】:

      以下答案适用于 G++ 编译器,如果您想重复访问同一矩阵的列,效率会更高。

      #include <iostream>
      
      using namespace std;
      
      typedef struct _Vector4f
      {
          float entries[4];
      } Vector4f;
      
      typedef struct _Matrix4f
      {
          union {
              float entries[16];
              Vector4f rows[4];
          };
      
          // cache the pointer to the transposed matrix
          struct _Matrix4f *
          _trans_cache;
      
          struct _Matrix4f *
          transpose();
      
      } Matrix4f;
      
      Matrix4f *
      Matrix4f::transpose()
      {
          if (this->_trans_cache)
              return this->_trans_cache;
      
          Matrix4f * result = new Matrix4f;
      
          for (int k = 0; k < 16; k++)
          {
              int j = k % 4;
              int i = k / 4;
      
              result->entries[k] = this->entries[i + 4 * j];
          }
      
          return this->_trans_cache = result;
      }
      
      int main()
      {
          Matrix4f mat = {1.0f, 0.0f, 0.0f, 1.0f,
                          0.0f, 1.0f, 0.0f, 2.0f,
                          0.0f, 0.0f, 1.0f, 3.0f,
                          0.0f, 0.0f, 0.0f, 1.0f};
          Vector4f col = mat.transpose()->rows[3];
      
          cout << (col.entries[0]) << ','
                  << (col.entries[1]) << ','
                  << (col.entries[2]) << ','
                  << (col.entries[3]) << end;
      
          return 0;
      }
      

      最后,我想让 OP 知道有许多库可以实现许多复杂的矩阵运算,例如 GNU Scientific Library。因此,几乎没有任何理由再次发明轮子。呵呵。 :P

      【讨论】:

      • 但这比首先存储列有什么好处呢?
      • “只要我们知道代码是否在某些平台上运行,未定义的行为就不是怪物。”不,不,不!这是一个非常危险的误解。 “它适用于 G++。” 它适用于 G++因为 G++ 定义了它,所以它在那个编译器中不是未定义的。不要将其与“它未定义但仍适用于 G++”混淆。未定义的行为总是不好的
      • @Oliv 不难回答,因为它是documented 和 G++ 支持的特性(即使用 G++ 时它不是未定义的)。
      • 如果 G++ 定义它就好了,但这仍然是不是标准 C++。如果您可以将便携性扔出窗外只是为了花哨,那就继续吧,恕我直言,价格太高了
      • @JonathanWakely: .....仔细想想,这似乎是你已经说过的^_^
      猜你喜欢
      • 1970-01-01
      • 2015-03-01
      • 1970-01-01
      • 2023-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-04
      • 1970-01-01
      相关资源
      最近更新 更多