在C++中更改整个结构的字节序

Change endianness of entire struct in C++

本文关键字:结构 字节 C++      更新时间:2023-10-16

我正在用C++编写一个解析器来解析一个定义良好的二进制文件。我已经声明了所有必需的结构。而且由于我只对特定字段感兴趣,因此在我的结构中,我通过创建大小等于跳过的字节的 char 数组来跳过非必需字段。所以我只是读取 char 数组中的文件并将 char 指针强制转换为我的结构指针。现在的问题是该二进制文件中的所有数据字段都是大端序,因此在类型转换之后,我需要更改所有结构字段的字节序。一种方法是为每个字段手动执行此操作。但是有很多字段的各种结构,所以手动操作会非常麻烦。那么实现这一目标的最佳方法是什么。而且由于我将解析非常大的此类文件(例如TB的文件(,因此我需要一种快速的方法来执行此操作。

编辑:我有使用属性(打包(,所以无需担心填充。

如果你可以进行未对齐的访问而不会受到惩罚,并且你不介意编译器或平台特定的技巧来控制填充,这是可以工作的。(我假设你对此问题,因为你提到了__attribute__((packed))(。

在这种情况下,最好的方法是为原始数据类型编写值包装器,并在首先声明结构时使用这些包装器而不是原始类型。请记住,值包装器必须是琐碎的/类似 POD 的才能正常工作。如果你有一个POSIX平台,你可以使用ntohs/ntohl进行字节序转换,那么无论你自己写什么,它都可能得到更好的优化。

如果平台上未对齐的访问是非法的或速度较慢,则需要改为反序列化。由于我们还没有反射,您可以使用相同的值包装器(加上一个Ignore<N>占位符,跳过您不感兴趣的字段的 N 个字节(来执行此操作,并在元组而不是结构中声明它们 - 您可以迭代元组中的成员并告诉每个成员从消息中反序列化自己。

一种方法是将 C 预处理器与C++运算符结合使用。像这样编写几个C++类:

#include "immintrin.h"
class FlippedInt32
{
int value;
public:
inline operator int() const
{
return _bswap( value );
}
};
class FlippedInt64
{
__int64 value;
public:
inline operator __int64() const
{
return _bswap64( value );
}
};

然后

#define int FlippedInt32

在包含定义这些结构的标头之前。#undef#include后立即

.这将用FlippedInt32替换结构中的所有int字段,它具有相同的大小,但返回翻转的字节。

如果它是您自己的结构,您可以修改,则不需要预处理器部分。只需将整数替换为字节翻转类即可。

如果你能列出需要字节序转换的字段的偏移量(以字节为单位,相对于文件顶部(,以及这些字段的大小,那么你可以直接在 char 数组上使用单个 for 循环进行所有字节序转换。 例如,像这样的东西(伪代码(:

struct EndianRecord {
size_t offsetFromTop;
size_t fieldSizeInByes;
};
std::vector<EndianRecord> todoList;
// [populate the todo list here...]
char * rawData = [pointer to the raw data]
for (size_t i=0; i<todoList.size(); i++)
{
const EndianRecord & er = todoList[i];
ByteSwap(&rawData[er.offsetFromTop], er.fieldSizeBytes);
}
struct MyPackedStruct * data = (struct MyPackedStruct *) rawData;
// Now you can just read the member variables
// as usual because you know they are already
// in the correct endian-format.

。当然,困难的部分是想出正确的todoList,但由于文件格式是明确定义的,因此应该可以通过算法生成它(或者更好的是,使用例如可以调用的GetNextEndianRecord()方法将其创建为生成器,这样您就不必在内存中存储非常大的向量(