如何将big-endian结构转换为小端结构?

sco*_*ttm 18 c++ struct endianness

我有一个在unix机器上创建的二进制文件.这只是一堆接一个写的记录.记录的定义如下:

struct RECORD {
  UINT32 foo;
  UINT32 bar;
  CHAR fooword[11];
  CHAR barword[11];
  UNIT16 baz;
}
Run Code Online (Sandbox Code Playgroud)

我试图找出如何在Windows机器上阅读和解释这些数据.我有这样的事情:

fstream f;
f.open("file.bin", ios::in | ios::binary);

RECORD r;

f.read((char*)&detail, sizeof(RECORD));

cout << "fooword = " << r.fooword << endl;
Run Code Online (Sandbox Code Playgroud)

我得到了一堆数据,但这不是我期望的数据.我怀疑我的问题与机器的endian区别有关,所以我来问这个问题.

我知道多个字节将存储在windows中的little-endian和unix环境中的big-endian中,我明白了.对于两个字节,Windows上的0x1234在unix系统上将为0x3412.

endianness会影响整个结构的字节顺序,还会影响结构的每个成员的字节顺序?我将采用什么方法将在unix系统上创建的结构转换为在Windows系统上具有相同数据的结构?任何比几个字节的字节顺序更深入的链接也会很棒!

Jam*_*and 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);
}
Run Code Online (Sandbox Code Playgroud)

然后,一旦你加载了结构,只需交换每个元素:

SwapEndian(r.foo);
SwapEndian(r.bar);
SwapEndian(r.baz);
Run Code Online (Sandbox Code Playgroud)


kdg*_*ory 10

实际上,字节顺序是底层硬件的属性,而不是操作系统.

最好的解决方案是在编写数据时转换为标准 - Google用于"网络字节顺序",您应该找到执行此操作的方法.

编辑:这是链接:http://www.gnu.org/software/hello/manual/libc/Byte-Order.html

  • 我无法决定如何编写数据,这个过程已经存在了10年,并且它没有改变. (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;
Run Code Online (Sandbox Code Playgroud)

parse_uint32的声明如下所示:

uint32 parse_uint32(char* buffer)
{
  uint32 x;
  // ...
  return x;
}
Run Code Online (Sandbox Code Playgroud)

这是一个非常简单的抽象,在实践中也不需要额外更新指针:

uint32 parse_uint32(char*& buffer)
{
  uint32 x;
  // ...
  buffer += 4;
  return x;
}
Run Code Online (Sandbox Code Playgroud)

后一种形式允许更清晰的代码来解析缓冲区; 从输入解析时,指针会自动更新.

同样,memcpy可以有一个帮手,如:

void parse_copy(void* dest, char*& buffer, size_t size)
{
  memcpy(dest, buffer, size);
  buffer += size;
}
Run Code Online (Sandbox Code Playgroud)

这种安排的好处是你可以拥有命名空间"little_endian"和"big_endian",然后你可以在你的代码中执行此操作:

using little_endian;
// do your parsing for little_endian input stream here..
Run Code Online (Sandbox Code Playgroud)

但是,很容易为相同的代码切换endianess,很少需要的功能..文件格式通常具有固定的endianess无论如何.

不要用虚拟方法将其抽象为类; 只会增加开销,但如果愿意,请随意:

little_endian_reader reader(data, size);
uint32 x = reader.read_uint32();
uint32 y = reader.read_uint32();
Run Code Online (Sandbox Code Playgroud)

读者对象显然只是指针的薄包装.size参数用于错误检查(如果有).对于接口本身并不是强制要求的.

注意这里的endianess选择是如何在COMPILATION TIME完成的(因为我们创建了little_endian_reader对象),所以我们调用虚拟方法开销没有特别好的理由,所以我不会采用这种方法.;-)

在这个阶段,没有任何理由将"fileformat结构"保持原样,您可以根据自己的喜好组织数据,而不必将其读入任何特定的结构中; 毕竟,这只是数据.当您读取图像等文件时,您实际上并不需要标题...您应该拥有对所有文件类型都相同的图像容器,因此读取特定格式的代码应该只读取文件,解释并重新格式化数据并存储有效负载.=)

我的意思是,这看起来很复杂吗?

uint32 xsize = buffer.read<uint32>();
uint32 ysize = buffer.read<uint32>();
float aspect = buffer.read<float>();    
Run Code Online (Sandbox Code Playgroud)

代码看起来很不错,而且开销很低!如果编译代码的文件和体系结构的字节顺序相同,则内部循环可能如下所示:

uint32 value = *reinterpret_cast<uint32*>)(ptr); ptr += 4;
return value;
Run Code Online (Sandbox Code Playgroud)

在某些体系结构上这可能是非法的,因此优化可能是一个坏主意,并使用更慢但更强大的方法:

uint32 value = ptr[0] | (static_cast<uint32>(ptr[1]) << 8) | ...; ptr += 4;
return value;
Run Code Online (Sandbox Code Playgroud)

在x86上可以编译成bswap或mov,如果方法是内联的,则开销相当低; 编译器会将"移动"节点插入到中间代码中,没有别的,这是相当有效的.如果对齐是一个问题,那么完整的读取 - 移位或序列可能会生成,超出,但仍然不会太破旧.比较分支可以允许优化,如果测试地址LSB并且看是否可以使用快速或慢速版本的解析.但这意味着每次阅读都会对测试造成惩罚.可能不值得努力.

哦,是的,我们正在读HEADERS和东西,我不认为这是太多应用程序的瓶颈.如果某些编解码器正在做一些非常紧密的内环,再次,读入一个临时缓冲区并从那里进行解码是很好的建议.同样的原则..在处理大量数据时,没有人从文件中按字节读取.好吧,实际上,我经常看到那种代码并且通常回复"你为什么这样做"是文件系统阻止读取并且字节来自内存无论如何,是真的,但它们通过深度调用堆栈这是获得几个字节的高开销!

仍然,编写解析器代码一次并使用数万次 - >史诗般的胜利.

从文件直接读取结构:不要做它们!