我有一个在unix机器上创建的二进制文件。这只是一堆接一个写的记录。记录的定义如下:
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
}
我试图弄清楚如何在Windows机器上阅读和解释这些数据。我有这样的事情:
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
cout << "fooword = " << r.fooword << endl;
我收到了大量数据,但这不是我期望的数据。我怀疑我的问题与机器的字节差异有关,所以我来问这个问题。
我知道多个字节将存储在windows中的little-endian和unix环境中的big-endian中,我明白了。对于两个字节,Windows上的0x1234在unix系统上将为0x3412。
endianness会影响整个结构的字节顺序,还会影响结构的每个成员的字节顺序?我将采用什么方法将在unix系统上创建的结构转换为在Windows系统上具有相同数据的结构?任何比几个字节的字节顺序更深入的链接也会很棒!
答案 0 :(得分:13)
与endian一样,您需要了解两个平台之间的填充差异。特别是如果您有奇数长度的char数组和16位值,您可能会在某些元素之间找到不同数量的填充字节。
编辑:如果结构是没有打包的,那么它应该相当简单。像这样(未经测试的)代码应该做的工作:
// Functions to swap the endian of 16 and 32 bit values
inline void SwapEndian(UINT16 &val)
{
val = (val<<8) | (val>>8);
}
inline void SwapEndian(UINT32 &val)
{
val = (val<<24) | ((val<<8) & 0x00ff0000) |
((val>>8) & 0x0000ff00) | (val>>24);
}
然后,一旦你加载了结构,只需交换每个元素:
SwapEndian(r.foo);
SwapEndian(r.bar);
SwapEndian(r.baz);
答案 1 :(得分:10)
实际上,字节顺序是底层硬件的属性,而不是操作系统。
最好的解决方案是在编写数据时转换为标准 - Google用于“网络字节顺序”,您应该找到执行此操作的方法。
编辑:这是链接:http://www.gnu.org/software/hello/manual/libc/Byte-Order.html
答案 2 :(得分:5)
不要直接从文件中读取结构!打包可能不同,你必须摆弄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..
很容易切换相同代码的endianess,但很少需要的功能..文件格式通常都有固定的endianess。
请勿使用虚拟方法将其抽象为类;只会增加开销,但如果愿意,请随意:
little_endian_reader reader(data, size);
uint32 x = reader.read_uint32();
uint32 y = reader.read_uint32();
读者对象显然只是指针周围的薄包装。 size参数用于错误检查(如果有)。对于接口本身并不是必须的。
注意这里的endianess选择是如何在COMPILATION TIME完成的(因为我们创建了little_endian_reader对象),所以我们调用虚拟方法开销没有特别好的理由,所以我不会采用这种方法。 ; - )
在这个阶段没有真正的理由让“fileformat struct”保持原样,你可以根据自己的喜好组织数据,而不必将其读入任何特定的结构中;毕竟,这只是数据。当您读取图像等文件时,您实际上并不需要标题...您应该拥有对所有文件类型都相同的图像容器,因此读取特定格式的代码应该只读取文件,解释并重新格式化数据和存储有效载荷。 =)
我的意思是,这看起来很复杂吗?
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和东西,我不认为这是太多应用程序的瓶颈。如果某些编解码器正在做一些非常紧密的内环,再次,读入一个临时缓冲区并从那里进行解码是很好的建议。同样的原则..在处理大量数据时,没有人从文件中按字节读取。好吧,实际上,我经常看到那种代码并且通常回复“你为什么这样做”是文件系统阻止读取并且字节来自内存无论如何,是真的,但它们通过深度调用堆栈这是获得几个字节的高开销!
仍然,编写一次解析器代码并使用数万次 - &gt;史诗般的胜利。
直接从文件中读取结构:不要做任何事情!
答案 3 :(得分:3)
它独立地影响每个成员,而不是整个struct
。此外,它不会影响数组之类的东西。例如,它只是以相反的顺序存储int
s中的字节。
PS。也就是说,可能会有一台具有奇怪字节序的机器。我刚才所说的适用于大多数二手机器(x86,ARM,PowerPC,SPARC)。
答案 4 :(得分:1)
您必须单独更正多个字节的每个成员的字节顺序。字符串不需要转换(fooword和barword),因为它们可以看作是字节序列。
但是,您必须处理另一个问题:结构中成员的对应关系。基本上,您必须检查unix和windows代码上的sizeof(RECORD)是否相同。编译器通常提供编译指示来定义所需的对象(例如,#pragma pack)。
答案 5 :(得分:1)
您还必须考虑两个编译器之间的对齐差异。允许每个编译器在最适合该体系结构的结构中的成员之间插入填充。所以你真的需要知道:
这就是为什么大多数程序(我已经看到(需要平台中立))将数据序列化为文本流,可以通过标准的iostream轻松读取。
答案 6 :(得分:1)
我喜欢为每个需要交换的数据类型实现SwapBytes方法,如下所示:
inline u_int ByteSwap(u_int in)
{
u_int out;
char *indata = (char *)∈
char *outdata = (char *)&out;
outdata[0] = indata[3] ;
outdata[3] = indata[0] ;
outdata[1] = indata[2] ;
outdata[2] = indata[1] ;
return out;
}
inline u_short ByteSwap(u_short in)
{
u_short out;
char *indata = (char *)∈
char *outdata = (char *)&out;
outdata[0] = indata[1] ;
outdata[1] = indata[0] ;
return out;
}
然后我将一个函数添加到需要交换的结构中,如下所示:
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UNIT16 baz;
void SwapBytes()
{
foo = ByteSwap(foo);
bar = ByteSwap(bar);
baz = ByteSwap(baz);
}
}
然后,您可以修改读取(或写入)结构的代码,如下所示:
fstream f;
f.open("file.bin", ios::in | ios::binary);
RECORD r;
f.read((char*)&detail, sizeof(RECORD));
r.SwapBytes();
cout << "fooword = " << r.fooword << endl;
要支持不同的平台,您只需要具有每个ByteSwap重载的特定于平台的实现。
答案 7 :(得分:0)
这样的事情应该有效:
#include <algorithm>
struct RECORD {
UINT32 foo;
UINT32 bar;
CHAR fooword[11];
CHAR barword[11];
UINT16 baz;
}
void ReverseBytes( void *start, int size )
{
char *beg = start;
char *end = beg + size;
std::reverse( beg, end );
}
int main() {
fstream f;
f.open( "file.bin", ios::in | ios::binary );
// for each entry {
RECORD r;
f.read( (char *)&r, sizeof( RECORD ) );
ReverseBytes( r.foo, sizeof( UINT32 ) );
ReverseBytes( r.bar, sizeof( UINT32 ) );
ReverseBytes( r.baz, sizeof( UINT16 )
// }
return 0;
}