从二进制文件读取数据时将大端序转换为小端序

18

我一直在寻找如何将大端序转换为小端序的方法,但是没有找到能解决我的问题的好方法。看起来有很多方法可以进行此转换。无论如何,以下代码在大端序系统上工作正常。但是,我应该如何编写一个转换函数,使其在小端序系统上也能正常工作呢?

这只是一项额外的作业,因为学校的系统正在运行大端序系统。只是我觉得很好奇,想让它在我家里的计算机上也能正常工作。

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
   ifstream file;

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

   if(!file)
      cerr << "Not able to read" << endl;
   else
   {
      cout << "Opened" << endl;

      int i_var;
      double d_var;

      while(!file.eof())
      {
         file.read( reinterpret_cast<char*>(&i_var) , sizeof(int) );
         file.read( reinterpret_cast<char*>(&d_var) , sizeof(double) );
         cout << i_var << " " << d_var << endl;
      }
   }
   return 0;
}

已解决

大端序和小端序只是字节的反向排列。我编写的这个函数似乎满足我的需求。我在这里添加它,以防将来有人需要。不过这只适用于双精度浮点数,如果要处理整型数据,可以使用torak建议的函数或者修改这段代码,使其仅交换4个字节。

double swap(double d)
{
   double a;
   unsigned char *dst = (unsigned char *)&a;
   unsigned char *src = (unsigned char *)&d;

   dst[0] = src[7];
   dst[1] = src[6];
   dst[2] = src[5];
   dst[3] = src[4];
   dst[4] = src[3];
   dst[5] = src[2];
   dst[6] = src[1];
   dst[7] = src[0];

   return a;
}

1
即使您解决了字节序问题,浮点格式在不同平台上可能会有所不同。您不能将浮点值二进制保存在一个平台上,然后期望在另一个平台上加载它们。这是您必须以二进制方式处理的要求吗? - sbi
好吧,这并不是要求,因为作业必须在学校的计算机上完成和展示。只是出于好奇心,我想知道如何在 Windows 计算机上读取这个二进制文件。 - starcorn
1
@sbi - 目前有哪些平台实现了非IEEE-754的浮点数? - Kos
@Kos:没什么,我相信你不需要使用它们。 - Yakov Galka
5个回答

23

您可以使用一个模板来进行字节序转换,以便为数据类型进行通用化:

#include <algorithm>

template <class T>
void endswap(T *objp)
{
  unsigned char *memp = reinterpret_cast<unsigned char*>(objp);
  std::reverse(memp, memp + sizeof(T));
}

那么你的代码最终会变成这样:
file.read( reinterpret_cast<char*>(&i_var) , sizeof(int) );
endswap( &i_var );
file.read( reinterpret_cast<char*>(&d_var) , sizeof(double) );  
endswap( &d_var );
cout << i_var << " " << d_var << endl;  

1
我有类似的东西,但是使用对象引用而不是指针。 - sbi

4

Linux提供了endian.h,其中包含高效的大小端转换例程,支持64位。它还会自动适应您的系统的字节序。32位函数定义如下:

uint32_t htobe32(uint32_t host_32bits);           // host to big-endian encoding
uint32_t htole32(uint32_t host_32bits);           // host to lil-endian encoding
uint32_t be32toh(uint32_t big_endian_32bits);     // big-endian to host encoding
uint32_t le32toh(uint32_t little_endian_32bits);  // lil-endian to host encoding

还有类似的16位和64位函数。

所以你只需要这样说

 x = le32toh(x);

将以小端编码的32位整数转换为主机CPU编码。这对于读取小端数据非常有用。

 x = htole32(x);

将从主机编码转换为32位小端。这对于编写小端数据非常有用。

在BSD系统上,请注意等效的头文件为sys/endian.h


1
同样要小心,因为BSD头文件适用于汇编,这意味着您不能将其包含在C/C++程序的任何汇编文件中。这让我很困惑,因为在阅读了Linux的 endian.h头文件后,我默认假设所有endian.h头文件都只能在预处理器中使用,但实际上并非如此。我在这里说出来是为了避免其他人掉入同样的陷阱。 - Thomas

4

您可能对ntohl函数族感兴趣。它们旨在将数据从网络字节顺序转换为主机字节顺序。网络字节顺序是大端字节序,因此在大端系统上它们不会做任何事情,而在小端系统上编译的相同代码将执行适当的字节交换。


有没有类似于double的东西? - starcorn
1
浮点数的表示方式比整数更加复杂(也更加多样化),我从未尝试过,所以不敢百分之百确定。然而,假设两台机器上的浮点数表示方式匹配(除了字节序),Bryan链接的文章表明这应该是可能的。 - torak

2

值得一提的是,微软在VS中也支持这些内联函数:

  • htond
  • htonf
  • htonl
  • htonll
  • htons

1
不仅在VS中,您还可以在GCC中包含winsock.h,其中包含所有上述功能。 - Edward Severinsen

2
假设你将要继续前进,保留一些辅助函数的小型库文件非常方便。其中两个函数应该是4字节值和2字节值的字节序交换函数。如果需要一些实例(包括代码),请查看这篇文章
一旦你有了交换函数,任何时候读入错误字节序的值时,都要调用相应的交换函数。在这里人们有时会被绊倒,因为单字节值不需要进行字节序交换,所以如果你正在从文件中读入表示字母字符串的字符流,那么它应该已经准备好了。只有当你读入多个字节的值(如整数值)时才需要进行交换。

其实我有点困惑,因为有很多不同的交换方式。在那篇文章中,它介绍了短整型、长整型和浮点型的交换方式。这些交换方式中的任何一种都适用于我正在处理的数据类型吗? - starcorn
解决方案取决于您的数据值大小。如果您有一个2字节值,则使用ShortSwap(),如果您有一个4字节值,则使用LongSwap()。在那个例子中的FloatSwap()有点无意义,除了在float存储和long存储之间存在逻辑差异之外... LongSwap仍将在4字节float上正确工作。您在顶部发布的解决方案实际上对于8字节值是相同的。 - Bryan
链接现在是404! - Victor Paléologue
回到很久以前:https://web.archive.org/web/20071113102719/http://www.gamedev.net:80/reference/articles/article2091.asp - Robert Hijmans

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接