Arun
Arun

Reputation: 3408

File resize not reflected using std::filebuf

I have created an empty file and I am trying to resize it. My resize and creation code is pretty similar except for the opening mode. The initial creation works as expected. The code for resize executes fine, but the file resize is not reflected.

Here is my code:

void resize()
{
    std::filebuf fileBuffer;
    fileBuffer.open(filePath, ios::out | ios::binary | ios::app);
    if (fileBuffer.is_open())
    {
        fileBuffer.pubseekoff((2 * fileSize) - 1, ios::beg);
        auto x = fileBuffer.sputc(0);
        fileBuffer.close();
    }
}

int main()
{
    std::filebuf fileBuffer;
    fileBuffer.open(filePath, ios::out | ios::trunc | ios::binary);
    if (fileBuffer.is_open())
    {
        fileBuffer.pubseekoff(fileSize - 1, ios::beg);
        fileBuffer.sputc(0);
        fileBuffer.close();
    }
    resize(); // Doesn't work

    resizeWithData(); // Works
}

When I try to resize the file by pushing actual data into it as shown below, it works:

void resizeWithData()
{
    ofstream fstr(filePath, ios::out | ios::binary | ios::app);
    if (fstr.is_open())
    {
        const auto emptySec = make_unique<char[]>(fileSize);
        memset(emptySec.get(), 0, fileSize);
        fstr.write(emptySec.get(), fileSize);
        fstr.close();
    }
}

Why this difference? My actual scenario involves creating and resizing really large files(1 GB and more) and so I was hoping that I could avoid the memory allocation for empty data as shown in the above code(since its really slow) if possible

P.S. : I am working on windows 7 with VS2013 update 4

Upvotes: 3

Views: 513

Answers (2)

user657267
user657267

Reputation: 21000

The reason resize() fails is because you have opened the file in app mode. Every time you write to this stream the implementation will resync the write position to the end of the file. The call to pubseekoff itself, which calls seekoff, which is defined in terms of fseek, doesn't write anything, it just repositions the file position indicator.

As can be seen in the file you open in main, without the app flag sputc will write immediately to the current position, although this might not be strictly guaranteed.

Most of the basic_filebuf operations are defined in terms of the C file functions, which don't seem to mention what happens when fputc is called when the file position indicator is beyond the end of the file. POSIX however makes it clear that this will write zeroes up to the position, and you'll probably find that most implementations will support this definition.

Upvotes: 1

luk32
luk32

Reputation: 16070

The difference is, that in 1st example you truncate the file. If you change ios::app to ios::trunc in your resize the file will have desired size (at least on my system). Funnily enough, I couldn't find, in the standard, any statement how it should behave, just some statements that it behaves as equivalent calls in C-style api. And for C-style API I only found the following note on cpp reference:

POSIX allows seeking beyond the existing end of file. If an output is performed after this seek, any read from the gap will return zero bytes. Where supported by the filesystem, this creates a sparse file.

IMO, in the end your fileBuffer.pubseekoff(fileSize - 1, ios::beg); call might have as well failed.

However, allocating 1GB of zeroes just to dump them into the file is a waste. I would use a buffer with repeated write calls:

#include <fstream>
#include <iostream>
#include <array>

using std::ios;

std::string filePath("test.file");
size_t fileSize = 1024*1024;

void resize(const std::string& path, std::size_t newSize){
  const std::size_t buffSize = 1024*1024;
  std::array<char, buffSize> buff{};
  std::filebuf fileBuffer;

  fileBuffer.open(path, ios::out | ios::binary | ios::app);

  auto reminder = newSize - fileBuffer.pubseekoff(0, ios::cur);
  while( reminder > buffSize ){
    fileBuffer.sputn(buff.data(), buffSize);
    reminder -= buffSize;
  }
  fileBuffer.sputn(buff.data(), reminder);

  fileBuffer.close();
}

int main()
{
  std::filebuf fileBuffer;
  fileBuffer.open(filePath, ios::out | ios::binary | ios::trunc);
  if (fileBuffer.is_open())
  {
    fileBuffer.pubseekoff(fileSize - 1, ios::beg);
    fileBuffer.sputc(0);
    fileBuffer.close();
  }
  resize(filePath, 3500000);
}

Upvotes: 1

Related Questions