使用std::ios_base::binary的意义是什么?

16

我在Windows下读取Linux文件时遇到了问题。以下是有关该问题的讨论: 在Unix下创建的文件在Windows上使用fstream::seekg

通过指定std::ios_base::binary来打开文本文件以解决此问题。

但是这种模式的实际意义是什么呢?如果指定,则仍然可以将文件视为文本文件(用mystream << "Hello World" << std::endl写入并使用std::getline读取)。

在Windows下,我注意到的唯一区别是,如果未指定std::ios_base::binary,则mystream << "Hello World" << std::endl会使用:

  • 0x0D 0x0A作为行分隔符(EOL和回车符)
  • 如果指定了std::ios_base::binary,则0x0A作为行分隔符(EOL only)

Notepad在打开使用std::ios_base::binary生成的文件时不会智能地显示行。像vi或Wordpad这样的编辑器确实会显示它们。

在使用和不使用std::ios_base::binary生成的文件之间,真正的区别只有这个吗?文档说将流视为二进制而不是文本。,这最终意味着什么?

如果我不关心在Notepad中打开文件并希望始终使用fstream::seekg,那么始终设置std::ios_base::binary是否安全?


我也会在字符串中测试\0字符。在二进制中,它们可能只是输出,而对于非二进制,则可能被解释为字符串终止符。 - Gábor Bakos
如果只涉及Unix和Windows系统,那么唯一的区别就是行尾和0x1A字符。在Windows上,0x1A被视为文件结束符(至少在输入时)。@GáborBakos - James Kanze
3个回答

13
二进制和文本模式的区别是实现定义的,但只涉及最低层级:它们不会改变像<<>>(用于插入和提取文本数据)这样的内容的含义。此外,在文本模式下输出除了一些不可打印字符(如'\n')之外的所有内容都是未定义行为。
对于最常见的操作系统:在Unix下,没有区别;两者相同。在Windows下,'\n'在内部将被映射为两个字符序列CR、LF(0x0D、0x0A),而在读取时0x1A将被解释为文件结束符。然而,在更奇特的(并且大多已经消亡的)操作系统中,它们可能表示为完全不同的文件类型,并且如果以二进制模式写入,则可能无法以文本模式读取文件,反之亦然。或者你可能会看到不同的东西:行末额外的空格,或者在二进制模式下没有'\n'
关于始终设置std::ios_base::binary:对于可移植文件,我的策略是决定我想要它们格式化的方式,并设置二进制,输出我想要的内容。这通常是CR、LF,而不仅仅是LF,因为这是网络标准。另一方面,大多数Windows程序只使用LF也没有问题,但我遇到过一些Unix程序对CR、LF有问题;这就说明应该系统地只使用LF(这也更容易)。以这种方式进行操作意味着无论在Unix还是在Windows下运行,都会得到相同的结果。

我理解得对吗,设置std::ios_base::binary或不设置对于文件读取没有区别(除了修复上述错误),而对于文件写入,根据平台的不同可能会导致差异? - jpo38
@jpo38 不是的。在二进制和文本之间的选择会影响读写两个方面:在Windows下,当读取时,CR、LF将被映射为LF,而0x1A会导致读取停止。在某些奇特的系统上,如果文件是以文本形式编写的,则以二进制模式打开可能会失败,反之亦然。 - James Kanze
@JamesKanze -- 我认为其他人已经评论过了(至少在MacOS上),如果你设置了二进制模式,流运算符会忽略它:这意味着如果你使用“>>”(提取格式化运算符)从流中读取二进制数据,即使你没有预期到这一点,你也会看到CR、LF的扩展/转换!我追踪了由于使用“>>”引入的复杂二进制文件格式错误。这些问题很容易通过使用普通的read()函数来解决。 - SMGreenfield

2

我发现(在浪费两个小时的工作时间试图理解发生了什么之后)指定std :: ios_base :: binary确实会有很大的区别。

std::vector<char> data{ 0x01, 0x02, 0x0A, 0x0B };
{
    std::fstream tfat;
    tfat.open( "binary", std::ios_base::out | std::ios_base::binary );
    tfat.write( &(data[0]), data.size() );
    tfat.close();
}
{
    std::fstream tfat;
    tfat.open( "not_binary", std::ios_base::out );
    tfat.write( &(data[0]), data.size() );
    tfat.close();
}

然后,“binary”文件包含4个字节:0x01,0x02,0x0A,0x0B。但是,“not_binary”文件包含5个字节:0x01,0x02,0x0D,0x0A,0x0B
在写入4个字节时,插入了0x0D(\r)在0x0A(\n)之前。我期望最终文件中有4个字节,但实际上有5个字节。
因此,这使我意识到为什么即使不使用<<运算符,写入数据到文件时必须使用std::ios_base::binary

我在我的Linux机器上尝试了一下,但无法重现结果。两个文件都包含了预期的4个字节,没有多余的字节。我理解这与Windows系统上的特定文本格式有关,但如果你忘记使用ios::binary,输出结果可能会变成随机的东西,这是非常可怕的事情。 - DarioP
@DarioP:我确认在测试时我使用的是Windows操作系统。 - jpo38

0

文本流和二进制流的含义是与平台相关且有些不可预测。

但就流行的平台而言,很容易理解:在Linux和MacOS X上没有区别。在Windows上,唯一的区别是内部的\n被转换为外部流中的\r\n


在Windows下,0x1A会被视为文本模式下的文件结尾。 - James Kanze

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