【问题标题】:Surprising results with std::fstreamstd::fstream 的惊人结果
【发布时间】:2018-11-03 04:41:02
【问题描述】:

我编写了一个简短的程序来生成均匀间隔的随机数字并将它们保存到通用文本文件中。如果我要求它准确生成 786432 位(每六位空格),输出将显示为随机的中文和日文字符。为什么? 我将标准库类用于文件 I/O 和 64 位 Xorshift 作为我的 PRNG。

程序(MSVC下编译):

#include <iostream>
#include <fstream>
#include <algorithm>

// From https://en.wikipedia.org/wiki/Xorshift
uint64_t xorsh64star(uint64_t* state)
{
    uint64_t x = *state;
    x ^= x >> 12;
    x ^= x << 25;
    x ^= x >> 27;
    state[0] = x;
    return x * 0x2545F4914F6CDD1D;
}

int main()
{
    uint64_t nDigits = 0;
    uint64_t wordLen = 1;
    std::cout << "How many digits?\n";
    std::cin >> nDigits;
    std::cout << "How many digits/word?\n";
    std::cin >> wordLen;
    std::fstream* txt = new std::fstream("randTxt.txt", std::ios::out);
    std::cout << "writing...";
    uint64_t charCtr = 0;
    uint64_t xorshState = 1103515245U; // GLIB C init constant, from https://www.shadertoy.com/view/XlXcW4
    for (uint64_t i = 0; i < nDigits; i += 1)
    {
        uint64_t rnd = xorsh64star(&xorshState) % uint64_t(9);
        *txt << rnd;
        charCtr += 1;
        if (!(charCtr % wordLen) && charCtr != 1)
        {
            *txt << ' ';
            charCtr += 1;
        }
    }
    std::cout << "finished! :D";
    return 0;
}

输出 786431 位:

输出 786432 位:

输出 786433 位:

【问题讨论】:

  • 为什么要使用new 来创建fstream?这很不寻常。您也没有使用delete,因此文件流可能没有很好地刷新和关闭。
  • 我运行了您的程序(感谢您提供完整的运行示例!)并尝试了 786431、786432 和 786433(每个单词 6 位数字......)我所以想重复你的输出!但是,唉,在我使用 C++ 14 和 Apple LLVM 版本 10.0.0 (clang-1000.11.45.5) 的 Mac 上,我在所有三个尝试中都得到了常规的十进制数字。
  • 我在我的机器上重现了这个问题。该文件在 Windows notepad.exe 中看起来是乱码。当我在任何其他文本编辑器(Notepad++、wordpad.exe 或 Visual Studio 编辑器)中打开它时,它看起来还不错。
  • @Blastfurnace 不知道我是怎么错过的,谢谢!在我的学位中,我们被教导为所有重要的事情假设new。我在这里这样做是因为我不知道字符会在哪里缓冲(因为有人可以轻松输出千兆字节的文本......)。虽然我可能很傻。 @RayToal 释放流为我修补它;它一定是剩余的数据弄乱了后续的输出,而 clang + mac c++ 运行时的架构可以阻止这种情况的发生。
  • 是的!感谢您的修复:)

标签: c++ visual-c++ text io fstream


【解决方案1】:

这里是修复。我不确定是什么导致了最初的问题,但是一旦将if 语句更改为:

if (!(charCtr % wordLen) && charCtr != 1
{
    txt << ' ';
//  charCtr += 1;    // This makes each word after the first 1 digit shorter.
}

最终的 .txt 文件现在可以正确显示,这解决了您的记事本查看问题并且您的所有单词现在都有 6 位数字,而不仅仅是第一个。

最初,我通过在 Win 10 64bit 上使用 MSVS17 编译您的代码重现了同样的问题:

【讨论】:

  • 哇,这是一个令人惊讶的修复。不过谢谢!这听起来肯定与如何将空格推入文件和记事本预期的文本格式有关,但我不知道具体是什么。
  • (感谢您也修补了字符偏移,我认为那里看起来有些东西)
【解决方案2】:

下面的答案很有帮助,但实际上并没有纠正报告的问题。该问题仅在 Windows notepad.exe 编辑器中出现。它在一个非常具体的实例中错误地显示了文件。无论如何,我希望有人发现下面的答案很有用。


使用new 创建文件流看起来很不寻常,在此代码中没有必要。这样做也意味着您需要使用delete 来正确刷新、关闭和销毁流对象。

替换这个:

std::fstream* txt = new std::fstream("randTxt.txt", std::ios::out);

与:

std::fstream txt("randTxt.txt", std::ios::out);

你的写作看起来像:

txt << rnd;

当流对象超出范围时,它会很好地关闭文件并释放它持有的所有资源。

【讨论】:

  • 所以...这有点尴尬,但我刚刚再次测试,得到了与第一次相同的输出。我想我在没有new 之前测试时输入了错误的位数:|
  • 老实说,我认为这与使用 notepad.exe 有关。我看到了同样的东西,但是在任何其他文本编辑器中打开文本文件时看起来都很好。尝试使用 Visual Studio 编辑器打开 randTxt.txt。
  • 哇,你说得对。我从没想过记事本会遇到这样的特定字符数:O.
  • 好吧,我想这个答案在技术上是正确的,但没有回答这个问题。无赖。
  • 哈哈,是的。不过,唯一“正确”的做法是提交错误报告,如果它那么重要,那就别想了。 “不要把 fstream 放在堆上”通常比“你发现了记事本错误”更有用。
猜你喜欢
  • 2017-05-23
  • 1970-01-01
  • 2018-01-09
  • 2022-06-11
  • 2013-06-29
  • 1970-01-01
  • 1970-01-01
  • 2015-03-04
  • 1970-01-01
相关资源
最近更新 更多