我正在用C ++编写一个解析器来解析一个定义明确的二进制文件。我已经声明了所有必需的结构。而且由于我只对特定字段感兴趣,因此在我的结构中,我通过创建大小等于已跳过字节的char数组来跳过了不需要的字段。所以我只是读取char数组中的文件,并将char指针转换为我的struct指针。现在的问题是该二进制文件中的所有数据字段都按大端顺序排列,因此在类型转换之后,我需要更改所有结构字段的字节序。一种方法是针对每个字段手动执行此操作。但是有许多结构包含很多字段,因此手动执行将非常麻烦。那么实现此目标的最佳方法是什么。而且由于我将解析非常大的此类文件(例如在TB中),因此我需要一种快速的方法来实现这一点。
编辑:我已经使用了属性(打包),因此无需担心填充问题。
答案 0 :(得分:1)
如果您可以无错地进行错位访问,并且您不介意编译器或平台特定的技巧来控制填充,则可以使用。 (我假设您 对此表示同意,因为您提到__attribute__((packed))
)。
在这种情况下,最好的方法是为原始数据类型编写值包装器,并在首先声明结构时使用这些包装器而不是原始类型。请记住,值包装程序必须是平凡的/类似于POD的,这样才能起作用。如果您拥有POSIX平台,则可以使用ntohs/ntohl
进行字节序转换,无论您自己编写什么,它都可能会得到更好的优化。
如果未对齐的访问在您的平台上是非法的或速度缓慢,则需要反序列化。由于我们还没有反射,因此可以使用相同的值包装器(加上Ignore<N>
占位符来完成此操作,该占位符会跳过您不感兴趣的字段的N个字节),并在元组而不是结构中声明它们-您可以遍历元组中的成员,并告诉每个成员对消息进行反序列化。
答案 1 :(得分:0)
如果您可以列出需要字节序转换的字段的偏移量(以字节为单位,相对于文件顶部)以及这些字段的大小,则可以进行所有直接在char数组上进行带有单个for循环的endian转换。例如。像这样的东西(伪代码):
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()
方法,这样就不必在内存中存储很大的向量)
答案 2 :(得分:0)
一种实现方法是将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
。
这将用int
替换结构中的所有FlippedInt32
字段,该字段的大小相同,但返回翻转的字节。
如果您可以修改自己的结构,则不需要预处理器部分。只需将整数替换为字节翻转类即可。