C++ 解析二进制文件。什么是现代方式?
我有一个二进制文件,有一些我知道的布局。例如,让格式如下:C++ 解析二进制文件。什么是现代方式?,c++,casting,binary,C++,Casting,Binary,我有一个二进制文件,有一些我知道的布局。例如,让格式如下: 2字节(无符号短)-字符串的长度 5字节(5个字符)-字符串-某些id名称 4字节(无符号整数)-跨步 24字节(6 x浮点-每3个浮点的2步)-浮点数据 该文件应如下所示(为了可读性,我添加了空格): 这里5-是2个字节:0x05 0x00。“你好”-5个字节,以此类推 现在我想读这个文件。目前我这样做: 将文件加载到ifstream 将此流读取到字符缓冲区[2] 将其强制转换为无符号short:无符号short len{*((
- 2字节(无符号短)-字符串的长度
- 5字节(5个字符)-字符串-某些id名称
- 4字节(无符号整数)-跨步
- 24字节(6 x浮点-每3个浮点的2步)-浮点数据
- 将文件加载到ifstream
- 将此流读取到
字符缓冲区[2]
- 将其强制转换为无符号short:
无符号short len{*((无符号short*)缓冲区)}代码>。现在我有了一根绳子的长度
- 读取一个流到
,并从此向量创建一个向量
。现在我有了字符串idstd::string
- 同样的方法读取接下来的4个字节并将它们转换为unsigned int。现在我有了一个跨步
- 虽然文件末尾读取浮动的方式不同,但为每个浮动创建一个
并强制转换char bufferFloat[4]
*((float*)bufferFloat)
无符号短字符
或浮点
或字符串
等而不创建char[x]
吗?如果没有,正确的方式是什么(我阅读了我正在使用的样式-是旧样式)
附言:当我写一个问题时,我脑海中出现了更清晰的解释——如何在char[x]
中从任意位置强制转换任意数量的字节
更新:我忘了明确指出,编译时的字符串和浮点数据长度不知道,是变量。
< P> C方式,这将在C++中工作,将声明一个结构:#pragma pack(1)
struct contents {
// data members;
};
注意
- 您需要使用pragma使编译器按照数据在结构中的外观对齐数据李>
- 此技术仅适用于
std::vector<char> buf(sizeof(contents));
file.read(buf.data(), buf.size());
contents *stuff = reinterpret_cast<contents *>(buf.data());
std::vector buf(sizeof(contents));
file.read(buf.data(),buf.size());
contents*stuff=reinterpret_cast(buf.data());
现在,如果您的数据大小是可变的,那么您可以将其分成几个块。要从缓冲区读取单个二进制对象,可以使用reader函数:
template<typename T>
const char *read_object(const char *buffer, T& target) {
target = *reinterpret_cast<const T*>(buffer);
return buffer + sizeof(T);
}
模板
常量字符*读取对象(常量字符*缓冲区,T和目标){
目标=*重新解释强制转换(缓冲区);
返回缓冲区+sizeof(T);
}
主要优点是这样的阅读器可以专门用于更高级的C++对象:
template<typename CT>
const char *read_object(const char *buffer, std::vector<CT>& target) {
size_t size = target.size();
CT const *buf_start = reinterpret_cast<const CT*>(buffer);
std::copy(buf_start, buf_start + size, target.begin());
return buffer + size * sizeof(CT);
}
模板
常量字符*读取对象(常量字符*缓冲区,标准::向量和目标){
size_t size=target.size();
CT常数*buf\U start=重新解释强制转换(缓冲区);
std::copy(buf_start,buf_start+size,target.begin());
返回缓冲区+大小*sizeof(CT);
}
现在在主解析器中:
int n_floats;
iter = read_object(iter, n_floats);
std::vector<float> my_floats(n_floats);
iter = read_object(iter, my_floats);
int n_浮动;
iter=读取对象(iter,n_浮动);
std::向量my_浮动(n_浮动);
iter=读取对象(iter,我的对象浮动);
注意:正如Tony D所观察到的,即使您可以通过
#pragma
指令和手动填充(如果需要)正确对齐,您仍然可能会遇到处理器对齐不兼容的情况,表现为(最佳情况)性能问题或(最坏情况)陷阱信号。只有当您可以控制文件的格式时,此方法才可能有意义。您最好声明一个结构(使用1字节填充-如何-取决于编译器)。用那个结构写,用同样的结构读。在结构中只放置POD,因此没有<代码> STD::String 等,只使用文件I/O或其他进程间通信结构——使用正常<代码>结构> <代码>或<代码>类< /代码>,以供C++程序进一步使用。数据文件被打包成FORTRAN输出。路线都错了。我成功地使用了预处理器技巧,自动完成了您正在手动执行的操作:将原始数据从字节缓冲区解包到结构。其思想是在包含文件中描述数据:
BEGIN_STRUCT(foo)
UNSIGNED_SHORT(length)
STRING_FIELD(length, label)
UNSIGNED_INT(stride)
FLOAT_ARRAY(3 * stride)
END_STRUCT(foo)
现在,您可以定义这些宏来生成所需的代码,比如说结构声明,包括上面的内容,取消定义并再次定义宏来生成解包函数,然后是另一个包含,等等
注意:我第一次在gcc中看到这种技术用于抽象语法树相关的代码生成
如果CPP不够强大(或者这种预处理器滥用不适合您),请替换一个小的lex/yacc程序(或者选择您最喜欢的工具)
对我来说,在生成代码方面,而不是手工编写代码,至少在低级别的基础代码中这样做是多么令人惊讶。p> 由于您的所有数据都是可变的,因此您可以分别读取这两个块,并且仍然使用强制转换:
struct id_contents
{
uint16_t len;
char id[];
} __attribute__((packed)); // assuming gcc, ymmv
struct data_contents
{
uint32_t stride;
float data[];
} __attribute__((packed)); // assuming gcc, ymmv
class my_row
{
const id_contents* id_;
const data_contents* data_;
size_t len;
public:
my_row(const char* buffer) {
id_= reinterpret_cast<const id_contents*>(buffer);
size_ = sizeof(*id_) + id_->len;
data_ = reinterpret_cast<const data_contents*>(buffer + size_);
size_ += sizeof(*data_) +
data_->stride * sizeof(float); // or however many, 3*float?
}
size_t size() const { return size_; }
};
目前我这样做:
- 将文件加载到ifstream
- 将此流读取到字符缓冲区[2]
- 将其强制转换为
:unsigned short
unsigned short len{*((unsigned short*)buffer)}代码>。现在我有了一根绳子的长度
最后一个风险是SIGBUS(如果您的字符数组恰好从奇数地址开始,并且您的CPU只能读取在偶数地址对齐的16位值)、性能(一些CPU将读取未对齐的值,但速度较慢;其他CPU,如现代x86,则速度良好)和/或问题。我建议读这两个字符,然后你可以说<代码>(x(0)< P>如果不是学习目的,如果你有选择二进制格式的自由,你最好考虑使用诸如<强> <强>之类的东西,它将处理你的序列化并允许与其他平台和语言交互。 如果您不能使用第三方API,您可以查看
QDataStream
以获得灵感
- 我个人是这样做的:
// some code which loads the file in memory
#pragma pack(push, 1)
struct someFile { int a, b, c; char d[0xEF]; };
#pragma pack(pop)
someFile* f = (someFile*) (file_in_memory);
int filePropertyA = f->a;
非常有效
const char* buffer = getPointerToDataSomehow();
my_row data1(buffer);
buffer += data1.size();
my_row data2(buffer);
buffer += data2.size();
// etc.
std::string s(the_size, ' ');
if (input_fstream.read(&s[0], s.size()) &&
input_stream.gcount() == s.size())
...use s...
struct Data
{
uint32_t x;
float y[6];
};
Data data;
if (input_stream.read((char*)&data, sizeof data) &&
input_stream.gcount() == sizeof data)
...use x and y...
// some code which loads the file in memory
#pragma pack(push, 1)
struct someFile { int a, b, c; char d[0xEF]; };
#pragma pack(pop)
someFile* f = (someFile*) (file_in_memory);
int filePropertyA = f->a;
using Buffer = std::pair<unsigned char const*, size_t>;
template <typename OffsetReader>
class UInt16LEReader: private OffsetReader {
public:
UInt16LEReader() {}
explicit UInt16LEReader(OffsetReader const or): OffsetReader(or) {}
uint16_t read(Buffer const& buffer) const {
OffsetReader const& or = *this;
size_t const offset = or.read(buffer);
assert(offset <= buffer.second && "Incorrect offset");
assert(offset + 2 <= buffer.second && "Too short buffer");
unsigned char const* begin = buffer.first + offset;
// http://commandcenter.blogspot.fr/2012/04/byte-order-fallacy.html
return (uint16_t(begin[0]) << 0)
+ (uint16_t(begin[1]) << 8);
}
}; // class UInt16LEReader
// Declined for UInt[8|16|32][LE|BE]...
template <size_t O>
class FixedOffsetReader {
public:
size_t read(Buffer const&) const { return O; }
}; // class FixedOffsetReader
// http://en.wikipedia.org/wiki/Zip_%28file_format%29#File_headers
class LocalFileHeader {
public:
template <size_t O>
using UInt32 = UInt32LEReader<FixedOffsetReader<O>>;
template <size_t O>
using UInt16 = UInt16LEReader<FixedOffsetReader<O>>;
UInt32< 0> signature;
UInt16< 4> versionNeededToExtract;
UInt16< 6> generalPurposeBitFlag;
UInt16< 8> compressionMethod;
UInt16<10> fileLastModificationTime;
UInt16<12> fileLastModificationDate;
UInt32<14> crc32;
UInt32<18> compressedSize;
UInt32<22> uncompressedSize;
using FileNameLength = UInt16<26>;
using ExtraFieldLength = UInt16<28>;
using FileName = StringReader<FixedOffsetReader<30>, FileNameLength>;
using ExtraField = StringReader<
CombinedAdd<FixedOffsetReader<30>, FileNameLength>,
ExtraFieldLength
>;
FileName filename;
ExtraField extraField;
}; // class LocalFileHeader