我有一个在 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 上以小端存储,在 UNIX 环境中以大端存储,我明白了。对于两个字节,Windows 上的 0x1234 将是 Unix 系统上的 0x3412。

字节顺序是否影响整个结构体或结构体每个单独成员的字节顺序?我将采取什么方法将在 UNIX 系统上创建的结构转换为在 Windows 系统上具有相同数据的结构?任何比几个字节的字节顺序更深入的链接也很棒!

有帮助吗?

解决方案

除了端序,你需要了解的两个平台之间填充的差异。特别是如果有奇数长度字符数组和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);

其他提示

实际上,字节序是底层硬件的特性,而不是OS。

最好的解决方法是写入数据时,转换为标准的 - 谷歌为“网络字节顺序”,你应该找到方法来做到这一点。

编辑:这里的链接: HTTP:// WWW。 gnu.org/software/hello/manual/libc/Byte-Order.html

不要直接从文件读取结构!打包可能会有所不同,您必须摆弄 pragma pack 或类似的编译器特定构造。太不靠谱了。许多程序员都逃避了这一点,因为他们的代码没有在广泛的架构和系统中编译,但这并不意味着这样做是可以的!

一个好的替代方法是将标头(无论如何)读入缓冲区并从三个中进行解析,以避免原子操作(例如读取无符号 32 位整数)中的 I/O 开销!

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();

读取器对象显然只是指针的薄包装。大小参数将用于错误检查(如果有)。对于接口本身来说并不是强制性的。

请注意这里的字节序选择是如何在编译时完成的(因为我们创建了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 并查看是否可以使用快速或慢速版本的解析,则比较分支可以允许优化。但这意味着每次阅读的测试都会受到惩罚。可能不值得付出努力。

哦,对了,我们正在阅读标头之类的东西,我认为这不是太多应用程序的瓶颈。如果某个编解码器正在执行一些非常严格的内循环,那么再次建议读入临时缓冲区并从那里进行解码。原理一样..在处理大量数据时,没有人会从文件中逐字节读取。好吧,实际上,我经常看到这种代码,对“为什么这样做”的通常答复是文件系统确实会阻止读取,而且字节无论如何都来自内存,这是真的,但它们会经历一个深层调用堆栈获取几个字节的开销很高!

尽管如此,编写一次解析器代码并使用无数次 -> 史诗般的胜利。

直接从文件读入结构体:伙计们,不要这样做!

它影响每个成员独立地,而不是整个struct。此外,它不会影响之类的东西阵列。举例来说,它只是使字节存储在相反的顺序一个ints。

PS。这就是说,有可能是怪异字节序的机器。我刚才说的适用于最常用的机器(X86,ARM,PowerPC的,SPARC)。

您能纠正多于一个字节的每个成员的字节序,单独地。串不需要被转换(fooword和barword),因为它们可以被看作是字节序列。

但是,你必须照顾另一个问题:成员aligmenent在你的结构。基本上,你必须检查的sizeof(RECORD)是在UNIX和Windows的代码相同。编译器通常提供的pragma定义所需的aligment(例如,包的#pragma)。

您还必须考虑两个编译器之间的对齐差异。每个编译器都可以在最适合该体系结构的结构中的成员之间插入填充。所以你真的需要知道:

  • UNIX prog 如何写入文件
  • 如果它是对象的二进制副本,则结构的确切布局。
  • 如果它是二进制副本,那么源架构的字节顺序是什么。

这就是为什么大多数程序(我见过的(需要与平台无关))将数据序列化为可以由标准 iostream 轻松读取的文本流。

我喜欢以实现用于需要交换,这样每个数据类型的SwapBytes方法:

inline u_int ByteSwap(u_int in)
{
    u_int out;
    char *indata = (char *)&in;
    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 *)&in;
    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超载的。

像这样应该工作:

#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;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top