不要直接从文件中读取结构体!因为打包可能会有所不同,你需要使用pragma pack或类似的编译器特定结构进行调整。这太不可靠了。很多程序员之所以能够摆脱这个问题是因为他们的代码并没有在广泛的架构和系统中编译,但这并不意味着这样做是正确的!
一个好的替代方法是将头部等内容读入缓冲区,并从中解析,以避免像读取无符号32位整数这样的原子操作中的I/O开销!
char buffer[32]
char* temp = buffer
f.read(buffer, 32)
RECORD rec
rec.foo = parse_uint32(temp)
rec.bar = parse_uint32(temp)
memcpy(&rec.fooword, temp, 11)
memcpy(%red.barword, temp, 11)
rec.baz = parse_uint16(temp)
解析 uint32 的声明应该像这样:
parse_uint32 的声明如下:
uint32 parse_uint32(char* buffer)
{
uint32 x;
return x;
}
这是一个非常简单的抽象,实际上更新指针并不会造成任何额外的费用:
uint32 parse_uint32(char*& buffer)
{
uint32 x;
buffer += 4;
return x;
}
后一种形式可以更清晰地解析缓冲区的代码;当您从输入中解析时,指针会自动更新。
同样地,memcpy可以有一个辅助函数,类似于:
void parse_copy(void* dest, char*& buffer, size_t size)
{
memcpy(dest, buffer, size);
buffer += size;
}
这种安排的美妙之处在于,您可以拥有命名空间"little_endian"和"big_endian",然后您可以在代码中这样做:
using little_endian
// do your parsing for little_endian input stream here..
很容易为相同的代码切换字节序,但这是一个很少需要的功能。文件格式通常已经有固定的字节序。
不要将此抽象为具有虚拟方法的类;这只会增加开销,但如果倾向于这样做,请随意:
little_endian_reader reader(data, size);
uint32 x = reader.read_uint32();
uint32 y = reader.read_uint32();
读取器对象显然只是指针的薄包装。大小参数只用于错误检查,接口本身并不强制要求。请注意,这里的字节序选择在编译时完成(因为我们创建了little_endian_reader对象),因此我们出于没有特别好的原因调用了虚方法开销,所以我不会采用这种方法。;-)
在这个阶段,没有什么实际理由保留“文件格式结构”本身,您可以按照自己的喜好组织数据,甚至根本不需要将其读入任何特定的结构中;毕竟,它只是数据。当您读取像图像之类的文件时,您实际上不需要头信息..您应该有一个相同的图像容器,适用于所有文件类型,因此读取特定格式的代码应该只需读取文件,解释和重新格式化数据,并存储有效负载。=)
我的意思是,这看起来复杂吗?
uint32 xsize = buffer.read<uint32>()
uint32 ysize = buffer.read<uint32>()
float aspect = buffer.read<float>()
代码看起来很好,而且开销很低!如果文件和编译架构的字节顺序相同,内部循环会像这样:
uint32 value = *reinterpret_cast<uint32*>)(ptr); ptr += 4;
return value;
在某些架构上这可能是非法的,因此这种优化可能不是一个好主意,可以使用更慢但更稳健的方法:
uint32 value = ptr[0] | (static_cast<uint32>(ptr[1]) << 8) | ...; ptr += 4;
return value;
在一个可以编译成bswap或mov的x86上,如果该方法被内联,则编译器会将“move”节点插入中间代码,除此之外不会有其他任何操作,这是相当高效的。如果对齐是个问题,则可能会生成完整的读取-移位-或序列,但仍然不算太差。比较分支可以允许优化,如果测试地址LSB并查看是否可以使用解析的快速或慢速版本。但这意味着在每次读取时都要进行测试的惩罚。也许不值得这样做。
哦,对了,我们正在读取头文件和其他东西,我认为这不是太多应用程序的瓶颈。如果某些编解码器正在执行一些非常紧密的内部循环,则再次建议将其读入临时缓冲区并从那里解码。同样的原则..处理大量数据时,没有人会从文件中逐字节读取。嗯,实际上,我经常看到那种代码,对于“为什么这样做”的通常回答是文件系统执行块读取,并且字节来自内存,这是正确的,但它通过深度调用堆栈,这对于获取几个字节而言开销很大!
仍然,编写一次解析器代码并使用无数次->史诗级胜利。
不要从文件中直接读取结构体:不要这样做,伙计们!