字节交换和C ++ / C.

时间:2010-11-30 08:50:01

标签: c++ c binaryfiles

我在comp.lang.c ++上发了一篇文章并得到了这个

http://groups.google.com/group/comp.lang.c++/browse_thread/thread/afc946826945bdb1/90850f6a0e0edd2d#90850f6a0e0edd2d

但这仍然不是答案。

我对二进制读操作有点困惑。

我正在尝试使用流函数读取二进制文件。这是商业程序(ANSYS)的结果文件,我知道文件的结构,至少从手册中知道。

文件结构为记录,程序用fortran编写。所以结构就像

记录长度(int) 虚拟整数 数据(可以是int,double) 虚拟整数

第一个记录是100个整数块,其中这对应于上述表示中的数据。

如果我开始读取文件并读取第一个值,即记录长度(整数),我必须交换字节以获得正确的值100.

我不明白为什么我要交换字节,因为这个文件是在同一台机器上生成的,它们应该使用相同的特定于系统的例程,所以这不应该是一个问题,但它似乎不是案件。还有其他事情正在发生。我无法理解这一点。软件是否可以强制交换字节,我很难理解原因?

感谢任何评论。

这是一个天真的测试用例

int main () {
  ifstream myfile;
  char intBuffer[4];
  myfile.open ("truss.rst", ios::binary);
  myfile.read(intBuffer, sizeof(int));
  //cout << *((int*)intBuffer) << endl;
  // if I do not use this portion-
  // I do not get what I want
  char *cptr, tmp;
  tmp = intBuffer[0];
  intBuffer[0] = intBuffer[3];
  intBuffer[3] = tmp;
  tmp = intBuffer[1];
  intBuffer[1] = intBuffer[2];
  intBuffer[2] = tmp;
  // -----------------------------
  cout << *((int*)intBuffer) << endl;

  myfile.close();
  return 0;
}

最佳, 你好。

8 个答案:

答案 0 :(得分:6)

这不仅取决于您正在使用的机器。如果Fortran基础结构以big endian而不是little endian写入整数,那么无论操作系统是什么,你都必须处理它。

我建议您使用ntohl()ntohs()功能,这比您的交换程序更清晰。

答案 1 :(得分:4)

无论格式如何,它显然会在各台机器上保持一致(如果你无法在另一台机器上打开文件,那将会很有趣。)

因此,必须以格式定义字节排序和数据类型大小,并且当您想要读取此类格式时,需要使用这些字节顺序和数据类型大小。

答案 2 :(得分:3)

软件采用特定的字节顺序使二进制文件更具可移植性并不少见,即使该软件尚未支持其他平台,也可能永远不支持。同样,软件可能会使用专为可移植性而设计的序列化库。像ntohl()这样的例程可以帮助您恢复所需的顺序。

答案 3 :(得分:2)

为了支持小/大端架构(字节顺序不同),也许软件会执行这种“奇怪”操作。

结论:

  • 在两台不同的机器上(小/大端)如果在文件中插入二进制信息,输入相同,则文件可能不同。

答案 4 :(得分:1)

某些文件格式要求字节顺序以单一方式通常为大端,因为它是网络顺序所以在小端x86s上这些文件在写入时将其内部字节交换,并在读取时交换回来

答案 5 :(得分:1)

这是endian problem。 Intel CPU使用小端。 “网络字节顺序”/ SPARC / Motorola使用大端。许多传统的便携式应用程序以大端方式保存文件以实现互操作性。

答案 6 :(得分:1)

当您自愿强制执行一个字节顺序时,有一些众所周知的时间:当数据打算在开始时未知的字节序之间进行交换时,例如通过网络。这就是为什么存在像ntohlhtonl这样的C原语:如果网络字节序与机器字节序相同,则它们什么也不做,否则它们交换字节。如果文件应该在机器之间交换,那么这里可能会有类似的东西。

但真正的问题是:数据块中是否存在相同的字节交换。如果不是,确实有一些奇怪的东西,0可能只是填充,而不是格式的所有部分。如果字节交换也发生在数据块中,则可能是故意进行的。

最便携的解决方案当然是逐字节读取文件并手动汇编数据,因此您可以处理大于uint32_t的整数。

还准备好在阅读双打时遇到麻烦,因为字节排序也可能被交换,并且它们不容易手工组装。

下面的代码应该作为您想要更改字节序的任何类型的模板,包括double。

#include <stdio.h>
#include <arpa/inet.h>
#include <stdint.h>

template <class builtin>
builtin ntoh(const builtin input) {
    if ((int)ntohs(1) != 1){
        union {
            char buffer[sizeof(builtin)];
            builtin data;
        } in, out;
        in.data = input;
        for (int i = 0 ; i < sizeof(builtin); i++){
            out.buffer[i] = in.buffer[sizeof(builtin) - i - 1];
        }
        return out.data;
    }
    return input;
}

main(){
    printf ("78563412 expected, got: output= %x\n", ntoh<uint32_t>(0x12345678));
}

它不会提供最佳性能look here以获得更好的本机类型性能。

答案 7 :(得分:-1)

htonl(主机到网络长)和htons(主机到网络短)将从你所在的平台变为big-endian。那是因为在那些日子里,大多数网络主机都运行了一种使用本机big-endian的UNIX。

无论您的平台如何,ntohl和ntohs都会将大端转换为原生端。如果你在一个大端平台上,这些将是一个无操作。

除了字节顺序之外,另一个潜在的可移植性问题是short和long的大小。 ntohl将读取4个字节并转换为32位整数。因此,目标int至少为32位来保存它,它不需要完全是那个长度。 ntohs读取2个字节并转换为16位的短整数。请注意,如果您的本机平台长时间使用超过32位或简称为16位,则必须管理“符号”问题(如果它们是有符号整数)(因为ntohl的实际类型是无符号的)。

现在包括Linux在内的更多机器使用带有小端符号的英特尔处理器,现在更频繁地使用它作为“默认”格式并使大端格式发生变化。在这种情况下,您可能希望编写自己的宏来转换为little-endian(在已经很小的平台上,它们将是无操作的。)

对于实际反转字节,你可以使用std :: reverse,顺便说一句,你需要两个指针,一个指向第一个字节,另一个指向最后一个字节后的一个指针。

你也可以实现“字节交换”,然后你的右指针应该在最后一个字节上,而不是一个接一个。你像这样的byteswap:

void byteswap( unsigned char & byte1, unsigned char & byte2 )
{
   byte1 ^= byte2;
   byte2 ^= byte1;
   byte1 ^= byte2;
}

要在C(而不是C ++)中实现,您可以使用指针而不是引用作为参数。

在您给出的实际示例中,该文件似乎按其规范以32位big-endian(即网络)字节顺序存储,因此您可以在此处使用ntohl,但是ntohl将unsigned int作为参数。因此,请将您的代码更正为:

uint32_t count = 0;
myfile.open ("truss.rst", ios::binary);
myfile.read(reinterpret_cast<char*>(&count), sizeof(uint32_t)); 
   // ideally validate that the read succeeded
count = ntohl( count );

我认为iostream中的一个弱点是你必须做那个演员。谁写过它从来都不喜欢二进制i / o的概念。当然,如果您使用C而不是C ++编写,则可以使用FILE*fread