在 c++ 中使用联合时是否可以跳过字节?

Is it possible to skip bytes when using union, in c++?

本文关键字:字节 是否 c++      更新时间:2023-10-16

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

根据我的理解,联合查看第一个的顺序字节。如果我有一个 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|

(最后一行,而不是列(。

重构我的代码以使用 1D 数组来获取顺序字节会更容易,还是应该更改矩阵的构造方式? 或者我可以与其他一些联合的事情来处理这个问题吗?

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

这是一个相当常见的模式,但无效的模式。它之所以受欢迎,是因为它很容易,而且主流编译器传统上允许和支持它。

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

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

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

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

此逻辑都可以封装在Matrix4类型中。

不能同时访问联合的两个不同成员。来自 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成员。

联合仅用于为不同的对象保留内存区域。但每一刻,只能有一个活的物体。因此,作为一般规则,如果您有一个具有两个成员的联合ab,如果您已经初始化了a并且a是联合的活动成员,则永远不应该尝试读取b的值。

但是,在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
}

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

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

以下答案适用于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科学库。因此,几乎没有任何理由再次发明轮子。呵呵。:P