将二进制文件缓冲区的块读入不同的类型
Posted
技术标签:
【中文标题】将二进制文件缓冲区的块读入不同的类型【英文标题】:Read blocks of a binary file buffer into different types 【发布时间】:2013-01-29 19:59:37 【问题描述】:我正在尝试将二进制文件读入内存,然后像这样使用它:
struct myStruct
std::string mystring; // is 40 bytes long
uint myint1; // is 4 bytes long
;
typedef unsigned char byte;
byte *filedata = ReadFile(filename); // reads file into memory, closes the file
myStruct aStruct;
aStruct.mystring = filedata.????
我需要一种方法来访问带有偏移量的二进制文件,并在该偏移量处获得一定的长度。
如果我将二进制文件数据存储在 std::string 中,这很容易,但我认为使用它来存储二进制数据并不是很好的做事方式。 (filedata.substr(offset, len))
合理广泛的 (IMO) 搜索没有找到任何相关信息,有什么想法吗?如果您认为有必要,我愿意更改存储类型(例如更改为 std::vector)。
【问题讨论】:
如果你有一个字节*到内存中数据的头部,你为什么不沿着长度走,一边走一边复制数据?只要您增加指针并知道要走多远,一切都很好且容易。 但是我怎样才能得到一个特定的长度,而不仅仅是指针当前位置的字节? @LordAro 听起来你知道字符串的长度是 40 字节长,后跟一个 4 字节整数。还要注意以这种方式执行的字节序。 您需要尊重字节指针并存储在您使用的任何数据中。struct1.string = *(bytePtr + sizeof(char)*40); struct1.int1 = *(bytePtr + (sizeof(char)*40 + sizeof(int));
。同样,请注意字节顺序,最好将数据序列化。
看看boost::serialize
并在网上搜索“c++ serialize”。
【参考方案1】:
如果你不打算使用序列化库,那么我建议为每个类添加序列化支持:
struct My_Struct
std::string my_string;
unsigned int my_int;
void Load_From_Buffer(unsigned char const *& p_buffer)
my_string = std::string(p_buffer);
p_buffer += my_string.length() + 1; // +1 to account for the terminating nul character.
my_int = *((unsigned int *) p_buffer);
p_buffer += sizeof(my_int);
;
unsigned char * const buffer = ReadFile(filename);
unsigned char * p_buffer = buffer;
My_Struct my_variable;
my_variable.Load_From_Buffer(p_buffer);
其他一些有用的接口方法:
unsigned int Size_On_Stream(void) const; // Returns the size the object would occupy in the stream.
void Store_To_Buffer(unsigned char *& p_buffer); // Stores object to buffer, increments pointer.
使用模板可以扩展序列化功能:
void Load_From_Buffer(std::string& s, unsigned char *& p_buffer)
s = std::string((char *)p_buffer);
p_buffer += s.length() + 1;
void template<classtype T> Load_From_Buffer(T& object, unsigned char *& p_buffer)
object.Load_From_Buffer(p_buffer);
编辑1:不直接写结构的原因
在 C 和 C++ 中,结构的大小可能不等于其成员大小的总和。 允许编译器在成员之间插入 填充 或未使用的空间,以便成员在地址上对齐。
例如,32 位处理器喜欢在 4 字节边界上获取内容。在结构中包含一个char
后跟一个int
将使int
位于相对地址1 上,这不是4 的倍数。编译器将填充该结构,以便@ 987654327@ 与相对地址 4 对齐。
结构可能包含指针或包含指针的项。
例如,std::string
类型的大小可能为 40,尽管字符串可能包含 3 个字符或 300 个字符。它有一个指向实际数据的指针。
Endianess。 对于多字节整数,一些处理器如最高有效字节 (MSB),即大端优先(人类读取数字的方式)或最低有效字节优先,即小端优先。小端格式比大端格式需要更少的电路来读取。
编辑 2:变体记录
在输出数组和容器之类的东西时,您必须决定是要输出完整的容器(包括未使用的插槽)还是只输出容器中的项目。仅输出容器中的项目将使用 variant record 技术。
输出变体记录的两种技术:数量后跟项目或项目后跟哨兵。后者是 C 风格字符串的编写方式,哨兵是一个空字符。
另一种技术是输出项目的数量,然后是项目。因此,如果我有 6 个数字,0、1、2、3、4、5,输出将是: 6 // 项目数 0 1 2 3 4 5
在上面的 Load_From_Buffer 方法中,我会创建一个临时的来保存数量,将其写出来,然后从容器中取出每个项目。
【讨论】:
这看起来不错,1 个问题:为什么需要缓冲区 和 p_buffer? (我不太擅长 C++ :L) 编辑:忽略这个,它是指向数组的指针 如果您将buffer
传递给方法,方法将增加它,您将丢失原始缓冲区的开始。始终最好使用指向缓冲区的附加指针。
@LordAro:提醒:如果您喜欢这个答案,请点击复选标记。
完成,谢谢 :) 哦:“在 C 和 C++ 中,结构的大小可能不等于其成员大小的总和。”
出于兴趣(如果你还在的话),这种方法也可以应用于向量吗?我无法找到数组的大小...【参考方案2】:
您可以为您的结构重载 std::ostream 输出运算符和 std::istream 输入运算符,如下所示:
struct Record
std::string name;
int value;
;
std::istream& operator>>(std::istream& in, Record& record)
char name[40] = 0 ;
int32_t value(0);
in.read(name, 40);
in.read(reinterpret_cast<char*>(&value), 4);
record.name.assign(name, 40);
record.value = value;
return in;
std::ostream& operator<<(std::ostream& out, const Record& record)
std::string name(record.name);
name.resize(40, '\0');
out.write(name.c_str(), 40);
out.write(reinterpret_cast<const char*>(&record.value), 4);
return out;
int main(int argc, char **argv)
const char* filename("records");
Record r[] = "zero", 0 , "one", 1 , "two", 2;
int n(sizeof(r)/sizeof(r[0]));
std::ofstream out(filename, std::ios::binary);
for (int i = 0; i < n; ++i)
out << r[i];
out.close();
std::ifstream in(filename, std::ios::binary);
std::vector<Record> rIn;
Record record;
while (in >> record)
rIn.push_back(record);
for (std::vector<Record>::iterator i = rIn.begin(); i != rIn.end(); ++i)
std::cout << "name: " << i->name << ", value: " << i->value
<< std::endl;
return 0;
【讨论】:
以上是关于将二进制文件缓冲区的块读入不同的类型的主要内容,如果未能解决你的问题,请参考以下文章