我在comp.lang.c ++上发了一篇文章并得到了这个
但这仍然不是答案。
我对二进制读操作有点困惑。
我正在尝试使用流函数读取二进制文件。这是商业程序(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;
}
最佳, 你好。
答案 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)
当您自愿强制执行一个字节顺序时,有一些众所周知的时间:当数据打算在开始时未知的字节序之间进行交换时,例如通过网络。这就是为什么存在像ntohl
和htonl
这样的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
。