不要直接从文件中读入结构!打包可能会有所不同,您必须摆弄 pragma pack 或类似的编译器特定结构。太不靠谱了。许多程序员侥幸逃脱,因为他们的代码没有在大量架构和系统中编译,但这并不意味着可以这样做!
一个很好的替代方法是将标头读取到缓冲区中并从三个中解析,以避免原子操作中的 I/O 开销,例如读取无符号 32 位整数!
char buffer[32];
char* temp = buffer;
f.read(buffer, 32);
RECORD rec;
rec.foo = parse_uint32(temp); temp += 4;
rec.bar = parse_uint32(temp); temp += 4;
memcpy(&rec.fooword, temp, 11); temp += 11;
memcpy(%red.barword, temp, 11); temp += 11;
rec.baz = parse_uint16(temp); temp += 2;
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();
阅读器对象显然只是指针周围的薄包装。size 参数将用于错误检查(如果有)。对于接口本身来说并不是强制性的。
注意这里的字节序选择是如何在编译时完成的(因为我们创建了 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 上,如果方法是内联的,则开销相当低;编译器会将“移动”节点插入中间代码,仅此而已,这是相当有效的。如果对齐是一个问题,则可能会生成完整的读取移位或序列,但仍然不会太破旧。如果测试地址 LSB 并查看是否可以使用快速或慢速版本的解析,比较分支可以允许优化。但这意味着每次读取都会受到测试的惩罚。可能不值得努力。
哦,对了,我们正在阅读 HEADERS 之类的东西,我认为这不是太多应用程序的瓶颈。如果某些编解码器正在执行一些非常紧密的内部循环,那么再次读取临时缓冲区并从那里解码是明智的。相同的原理.. 在处理大量数据时,没有人从文件中读取一个字节。好吧,实际上,我经常看到这种代码,通常对“你为什么这样做”的回答是文件系统会阻塞读取,并且字节无论如何都来自内存,没错,但它们经过了一个很深的调用堆栈这是获取几个字节的高开销!
尽管如此,编写一次解析器代码并使用无数次 -> 史诗般的胜利。
从文件中直接读取结构:不要这样做!