从 .bin 文件中读取二进制数据到 C++ 中的结构
Reading binary data from a .bin file into structs in C++
我有一组 .bin 文件,其中包含正式指定格式的数据。我确切地知道每个字段有多少字节,例如名称 = 40 字节,版本号 = 2 字节等。
我也知道它们存储在文件中的确切顺序(例如名称,然后是版本号....)。
到目前为止,我可以将文件中的数据加载到 std::vector<unsigned char>
列表中,然后遍历该数据并根据预期字节数读取字段。
问题是这个方法很长而且容易出错,如果我弄错了任何字段(有很多不同的字段)。
我看过并与人们讨论过结构打包、指针转换和位域。我似乎无法让他们一起工作。
如何将数据读入我的缓冲区,然后 'overlay' 我的结构在缓冲区中?然后所有字段将根据分配的位字段填充,我在结构中给了每个值。
位字段的问题是我不能接受字符串。
建议或示例代码将不胜感激。如果你只想发表评论,我可以给你代码来展示我目前所拥有的以及我正在努力实现的目标。
#include <vector>
int main()
{
//File data loaded by function call
std::vector<unsigned char> fileData;
//How do I cast fileData to be a dataFields type?
}
struct dataFields
{
int ID : 8;
// Cannot use bit field for string type?
std::string name;
int versionNumber : 16;
int someOtherValue : 8;
}
由于工作原因,我无法给出我正在处理的确切代码,但我觉得这总结了我在一个简单的庄园中试图做得相当好的事情。
不,您确实不能为 std::string
使用位模式,因为它只包含一些指针。
我在项目中使用的常用方法是为每种记录类型设置 POD 结构。
然后负责{de}序列化的最低层只在PODs和字节之间转换。任何 C++ 逻辑,如 std::string
或可变长度 std::vector
都在更高级别处理。
#include <array>
#include <type_traits>
#include <cstdint>
#include <cstring>
struct Record{
std::uint8_t ID;
std::array<char,40> name;
std::uint16_t versionNumber;
std::uint8_t someOtherValue;
};
static_assert(sizeof(Record)==46);
static_assert(offsetof(Record,name)==1);
在我的世界里,我尝试让 Record
尊重每个元素对 sizeof(E)
的标准对齐。如果需要,您可以添加压缩修饰符。在位域之前优先使用 <cstdint>
中的类型。
我建议在每个 Record
之后放置一堆 static_assert
,以验证其布局。否则有一天会有人出现并试图“清理”代码,破坏一切。它还很好地记录了 reader.
的协议
一个缺点是这不容易支持将可变长度成员放在中间或有多个成员,但我从来不需要这样做,保持数据包简单。
另外,我只是决定协议的固定字节顺序。如果有人需要其他东西,他们有责任传递正确编码的 Record
s 进行序列化。
序列化助手:
template<typename T>
T read_value(const unsigned char*& ptr){
static_assert(std::is_standard_layout_v<T>);
T value;
std::memcpy(&value,ptr,sizeof(T));
ptr+=sizeof(T);
return value;
}
template<typename T>
void write_value(unsigned char*& ptr, const T& value){
static_assert(std::is_standard_layout_v<T>);
std::memcpy(ptr,&value,sizeof(T));
ptr+=sizeof(T);
}
负责{反}序列化的最低层看起来像这样:
void deserialize_stream(const unsigned char* bytes){\
// Output is bunch of POD types.
auto record1 = read_value<Record>(bytes);
auto record2 = read_value<Record>(bytes);
}
void serialize_stream(unsigned char* bytes){
// Input is a list of POD types to serialize.
Record record1{1,"Foo",12,42};
Record record2{2,"Bar",14,28};
write_value(bytes,record1);
write_value(bytes,record2);
}
例子
int main() {
// Just a example, CHECK SIZE in real world.
std::array<unsigned char,1024> buffer;
serialize_stream(buffer.data());
deserialize_stream(buffer.data());
}
如果这部分不受 time/storage 效率限制,请考虑使用序列化库来执行此操作。这些库可以将您的对象序列化为 XML 或 JSON 并轻松反序列化。您无需担心字节序或 POD 问题。
我有一组 .bin 文件,其中包含正式指定格式的数据。我确切地知道每个字段有多少字节,例如名称 = 40 字节,版本号 = 2 字节等。 我也知道它们存储在文件中的确切顺序(例如名称,然后是版本号....)。
到目前为止,我可以将文件中的数据加载到 std::vector<unsigned char>
列表中,然后遍历该数据并根据预期字节数读取字段。
问题是这个方法很长而且容易出错,如果我弄错了任何字段(有很多不同的字段)。
我看过并与人们讨论过结构打包、指针转换和位域。我似乎无法让他们一起工作。
如何将数据读入我的缓冲区,然后 'overlay' 我的结构在缓冲区中?然后所有字段将根据分配的位字段填充,我在结构中给了每个值。
位字段的问题是我不能接受字符串。
建议或示例代码将不胜感激。如果你只想发表评论,我可以给你代码来展示我目前所拥有的以及我正在努力实现的目标。
#include <vector>
int main()
{
//File data loaded by function call
std::vector<unsigned char> fileData;
//How do I cast fileData to be a dataFields type?
}
struct dataFields
{
int ID : 8;
// Cannot use bit field for string type?
std::string name;
int versionNumber : 16;
int someOtherValue : 8;
}
由于工作原因,我无法给出我正在处理的确切代码,但我觉得这总结了我在一个简单的庄园中试图做得相当好的事情。
不,您确实不能为 std::string
使用位模式,因为它只包含一些指针。
我在项目中使用的常用方法是为每种记录类型设置 POD 结构。
然后负责{de}序列化的最低层只在PODs和字节之间转换。任何 C++ 逻辑,如 std::string
或可变长度 std::vector
都在更高级别处理。
#include <array>
#include <type_traits>
#include <cstdint>
#include <cstring>
struct Record{
std::uint8_t ID;
std::array<char,40> name;
std::uint16_t versionNumber;
std::uint8_t someOtherValue;
};
static_assert(sizeof(Record)==46);
static_assert(offsetof(Record,name)==1);
在我的世界里,我尝试让 Record
尊重每个元素对 sizeof(E)
的标准对齐。如果需要,您可以添加压缩修饰符。在位域之前优先使用 <cstdint>
中的类型。
我建议在每个 Record
之后放置一堆 static_assert
,以验证其布局。否则有一天会有人出现并试图“清理”代码,破坏一切。它还很好地记录了 reader.
一个缺点是这不容易支持将可变长度成员放在中间或有多个成员,但我从来不需要这样做,保持数据包简单。
另外,我只是决定协议的固定字节顺序。如果有人需要其他东西,他们有责任传递正确编码的 Record
s 进行序列化。
序列化助手:
template<typename T>
T read_value(const unsigned char*& ptr){
static_assert(std::is_standard_layout_v<T>);
T value;
std::memcpy(&value,ptr,sizeof(T));
ptr+=sizeof(T);
return value;
}
template<typename T>
void write_value(unsigned char*& ptr, const T& value){
static_assert(std::is_standard_layout_v<T>);
std::memcpy(ptr,&value,sizeof(T));
ptr+=sizeof(T);
}
负责{反}序列化的最低层看起来像这样:
void deserialize_stream(const unsigned char* bytes){\
// Output is bunch of POD types.
auto record1 = read_value<Record>(bytes);
auto record2 = read_value<Record>(bytes);
}
void serialize_stream(unsigned char* bytes){
// Input is a list of POD types to serialize.
Record record1{1,"Foo",12,42};
Record record2{2,"Bar",14,28};
write_value(bytes,record1);
write_value(bytes,record2);
}
例子
int main() {
// Just a example, CHECK SIZE in real world.
std::array<unsigned char,1024> buffer;
serialize_stream(buffer.data());
deserialize_stream(buffer.data());
}
如果这部分不受 time/storage 效率限制,请考虑使用序列化库来执行此操作。这些库可以将您的对象序列化为 XML 或 JSON 并轻松反序列化。您无需担心字节序或 POD 问题。