Matt
Matt

Reputation: 175

Reading a file using fstream

When I try to read a file to a buffer, it always appends random characters to the end of the buffer.

char* thefile;
    std::streampos size;

    std::fstream file(_file, std::ios::in | std::ios::ate);
    if (file.is_open())
    {
        size = file.tellg();
        std::cout << "size: " << size;
        thefile = new char[size]{0};

        file.seekg(0, std::ios::beg);
        file.read(thefile, size);
        std::cout << thefile;
    }

    int x = 0;

While my original text in my file is: "hello" The output becomes: "helloýýýý««««««««þîþîþ"

Could anyone help me as to what is happening here? Thanks

Upvotes: 1

Views: 4676

Answers (3)

Christophe
Christophe

Reputation: 73530

If the file is not opened with ios::binary mode, you cannot assume that the position returned by tellg() will give you the number of chars that you will read. Text mode operation may perform some transformations on the flow (f.ex: on windows, it will convert "\r\n" in the file in "\n", so you might find out a size of 2 but read only 1)

Anyway, read() doesn't add a null terminator.

Finally, you must allocate one more character than the size that you expect due to the null terminator that you have to add. Otherwise you risk a buffer overflow when you add it.

You should verify how many chars were really read with gcount(), and set a null terminator to your string accordingly.

   thefile = new char[size + 1]{0};  // one more for the trailing null  
   file.seekg(0, std::ios::beg);
   if (file.read(thefile, size))
      thefile[size]=0;               // successfull read:  all size chars were read
   else thefile[file.gcount()]=0;   // or less chars were read due to text mode

Upvotes: 1

Robinson
Robinson

Reputation: 10132

Here's a better way of reading your collection:

#include <vector>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <cstdint>
#include <iterator>

template<class T>
void Write(std::string const & path, T const & value, std::ios_base::openmode mode)
{               
    if (auto stream = std::ofstream(path, mode))
    {
        Write(stream, value);

        stream.close();
    }
    else
    {
        throw std::runtime_error("failed to create/open stream");
    }       
}

template<class T>
void Write(std::ostream & stream, T const & value)
{
    std::copy(value.begin(), value.end(), std::ostreambuf_iterator<char>(stream));

    if (!stream)
    {
        throw std::runtime_error("failed to write");
    }
}

template<class T>
void Read(std::istream & stream, T & output)
{
    auto eof = std::istreambuf_iterator<char>();

    output = T(std::istreambuf_iterator<char>(stream), eof);

    if(!stream)
    {
        throw std::runtime_error("failed to read stream");
    }
}

template<class T>
void Read(std::string const & path, T & output)
{               
    if (auto stream = std::ifstream(path, std::ios::in | std::ios::binary))
    {
        Read(stream, output);

        stream.close();
    }
    else
    {
        throw std::runtime_error("failed to create stream");
    }           
}


int main(void)
{
    // Write and read back text.

    {
        auto const s = std::string("I'm going to write this string to a file");

        Write("temp.txt", s, std::ios_base::trunc | std::ios_base::out);

        auto t = std::string();

        Read("temp.txt", t);
    }

    // Write and read back a set of ints.

    {
        auto const v1 = std::vector<int>() = { 10, 20, 30, 40, 50 };

        Write("temp.txt", v1, std::ios_base::trunc | std::ios_base::out | std::ios_base::binary);

        auto v2 = std::vector<int>();

        Read("temp.txt", v2);
    }

    return 0;
}

Pass in an iterable container rather than using "new".

Upvotes: -1

a_guest
a_guest

Reputation: 36309

From the C++ docs: http://cplusplus.com/reference/istream/istream/read

"This function simply copies a block of data, without checking its contents nor appending a null character at the end."

So your string misses the trailing null character which indicates the end of the string. In this case cout will just continue printing characters from what is beyond thefile in memory.

Add a '\0' at the end of your string.

Upvotes: 4

Related Questions