Discussion:
Writing to file without truncation using ofstream
(too old to reply)
Paul
2007-10-29 13:09:01 UTC
Permalink
I am trying to write code that would open a file, seek to the end, and
replace the last character in the file. The problem is that only
std::ios_base::app supplied in the mode argument appears to be able to
preserve the file contents, all others truncating it, and std::ios_base::app
assumes that writing will start at the end wherever you seek to after opening
the file.

This is what I was trying to make work (I know at this point that the file
exists and is not empty):

std::ofstream f("ABI_TEST.CSV", std::ios_base::ate);
f.seekp(-1, std::ios_base::end);
f << '\0';

I tried to use std::fstream supplying 'std::ios_base::out |
std::ios_base::ate' for the mode but only with std::ios_base::app will the
file not be truncated. As far as I know, std::ios_base::trunc should not be
applied by default and std::ios_base::ate should work for files, too.

Thank you.
David Wilkinson
2007-10-29 15:20:41 UTC
Permalink
Post by Paul
I am trying to write code that would open a file, seek to the end, and
replace the last character in the file. The problem is that only
std::ios_base::app supplied in the mode argument appears to be able to
preserve the file contents, all others truncating it, and std::ios_base::app
assumes that writing will start at the end wherever you seek to after opening
the file.
This is what I was trying to make work (I know at this point that the file
std::ofstream f("ABI_TEST.CSV", std::ios_base::ate);
f.seekp(-1, std::ios_base::end);
f << '\0';
I tried to use std::fstream supplying 'std::ios_base::out |
std::ios_base::ate' for the mode but only with std::ios_base::app will the
file not be truncated. As far as I know, std::ios_base::trunc should not be
applied by default and std::ios_base::ate should work for files, too.
Paul:

std::ios_base::out | std::ios_base::ate should work. You say the file is
being truncated? This should not happen.
--
David Wilkinson
Visual C++ MVP
Paul
2007-10-29 16:09:00 UTC
Permalink
Post by David Wilkinson
std::ios_base::out | std::ios_base::ate should work. You say the file is
being truncated? This should not happen.
--
David Wilkinson
Visual C++ MVP
Yes, David, this is what I observe.

After this code

int _tmain(int /*argc*/, _TCHAR* /*argv[]*/)
{
std::fstream f("ABI_TEST.CSV", std::ios_base::out | std::ios_base::ate);
return 0;
}

runs, or this

int _tmain(int /*argc*/, _TCHAR* /*argv[]*/)
{
std::ofstream f("ABI_TEST.CSV", std::ios_base::ate);
return 0;
}

ABI_TEST.CSV becomes 0 size and empty.

I use Visual Studio 2005, Win32, Debug.

Paul
Charles Wang[MSFT]
2007-10-30 06:25:08 UTC
Permalink
Hi Paul,
I remember that the book "C++ Primer" talked about this issue. Generally
for ofstream, there are two open modes: ios_base::out (output mode) and
ios_base::app (append mode). However both of the two modes did not help for
your issue. If you use ios_base::out as the output mode, the existing data
of the file will be cleaned; while ios_base::app just allows you to append
characters to the file.

Strangely enough, if I specify ios_base::in together with ios_base::ate,
the last character can be replaced correctly. For example:
std::ofstream f("C:\\ABI_TEST.CSV",ios_base::in|ios_base::ate);
f.seekp(-1, std::ios_base::end);
f << '\0';
f.close();

Currently I have not been able to explain why ios_base::in can make it
work. I need to consult the product team for this issue.
The process may need a little long time. I appreciate your patience.

If you have any other questions or concerns, please feel free to let me
know. Have a nice day!

Best regards,
Charles Wang
Microsoft Online Community Support
=====================================================
When responding to posts, please "Reply to Group" via
your newsreader so that others may learn and benefit
from this issue.
======================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
======================================================
Paul
2007-10-30 10:09:00 UTC
Permalink
Post by Charles Wang[MSFT]
Strangely enough, if I specify ios_base::in together with ios_base::ate,
std::ofstream f("C:\\ABI_TEST.CSV",ios_base::in|ios_base::ate);
f.seekp(-1, std::ios_base::end);
f << '\0';
f.close();
Currently I have not been able to explain why ios_base::in can make it
work. I need to consult the product team for this issue.
The process may need a little long time. I appreciate your patience.
Thank you, Charles.

std::ios_base::in for an std::ofstream indeed appears strange. Otherwise, as
far as I know, the default for std::ofstream is 'std::ios_base::out |
std::ios_base::trunc' but it looks like there is no way to override this
'trunc' by supplying only 'std::ios_base::out', and this affects both
std::ofstream and std::fstream (the latter has no default
std::ios_base::trunc but this is how it behaves unless std::ios_base::in is
also present).

Paul
Charles Wang[MSFT]
2007-11-01 02:15:12 UTC
Permalink
Hi Paul,
I just get the response from the Dev team. Please refer to the following
explanation:
<quote>
iostreams map to stdio in a fairly reasonable manner. Let's see what the C
Standard has to say:
1. "Opening a file with append mode ('a' as the first character in the mode
argument) causes all subsequent writes to the file to be forced to the then
current end-of-file, regardless of intervening calls to the fseek
function." (C99+TC1+TC2 7.19.5.3/5).
(ios_base::app maps to 'a'; see C++03 27.8.1.3/2.)
So, using ios_base::app won't let you stomp over existing bytes.

2. ios_base::ate doesn't change the file open mode; all that it does is an
immediate repositioning. "If the open operation succeeds and (mode &
ios_base::ate) != 0, positions the file to the end ("as if" by calling
std::fseek(file, 0, SEEK_END))." If you perform an absolute seek
(ios_base::beg or ios_base::end; a relative seek is ios_base::cur), then
you're making ios_base::ate have no effect.

3. If you use ios_base::out by itself, you're asking for 'w' mode (C++03
27.8.1.3/2 again). 'w' mode is "truncate to zero length or create text
file for writing" (C99+TC1+TC2 7.19.5.3/3). This will wipe out any
existing file.

4. Asking for ios_base::in | ios_base::out is asking for "r+" mode (C++03
27.8.1.3/2 again), which is "open text file for update (reading and
writing)" (C99+TC1+TC2 7.19.5.3/3 again). This allows you to preserve the
current contents of the file and stomp over bytes of your choosing.

Aside: You may already know this, but here's how iostreams modes work:

fstream is opened with ios_base::in | ios_base::out by default (C++03
27.8.1.11). If you specify an explicit mode, fstream uses it unchanged
(C++03 27.8.1.12/2).
ifstream is opened with ios_base::in by default (C++03 27.8.1.5). If you
specify an explicit mode, ifstream adds ios_base::in (C++03 27.8.1.6/2).
ofstream is opened with ios_base::out by default (C++03 27.8.1.8). If you
specify an explicit mode, ofstream adds ios_base::out (C++03 27.8.1.9/2).

Therefore, your customer can use fstream with ios_base::in | ios_base::out
| ios_base::binary (or no explicit mode for text I/O) and then seek to the
desired position before writing. Or they can use ofstream with
ios_base::in | ios_base::binary (or just ios_base::in for text I/O) and
then seek to the desired position before writing. I suggest the latter,
using an ofstream, unless they actually need to read from the file too.
</quote>

This explains why ios_base::in worked here. If you have any other questions
or concerns, please feel free to let me know. It is my pleasure to be of
assistance.

Best regards,
Charles Wang
Microsoft Online Community Support
=====================================================
When responding to posts, please "Reply to Group" via
your newsreader so that others may learn and benefit
from this issue.
======================================================
This posting is provided "AS IS" with no warranties, and confers no rights.
======================================================

Igor Tandetnik
2007-10-29 17:17:23 UTC
Permalink
Post by Paul
I am trying to write code that would open a file, seek to the end, and
replace the last character in the file. The problem is that only
std::ios_base::app supplied in the mode argument appears to be able to
preserve the file contents, all others truncating it
(in | out) shouldn't truncate. It's equivalent to fopen("r+").
--
With best wishes,
Igor Tandetnik

With sufficient thrust, pigs fly just fine. However, this is not
necessarily a good idea. It is hard to be sure where they are going to
land, and it could be dangerous sitting under them as they fly
overhead. -- RFC 1925
Paul
2007-10-30 10:04:01 UTC
Permalink
Post by Igor Tandetnik
Post by Paul
I am trying to write code that would open a file, seek to the end, and
replace the last character in the file. The problem is that only
std::ios_base::app supplied in the mode argument appears to be able to
preserve the file contents, all others truncating it
(in | out) shouldn't truncate. It's equivalent to fopen("r+").
--
With best wishes,
Igor Tandetnik
Thank you, Igor. This (and the equivalent std::fstream ("file_name"), i.e.
the default combination for the mode parameter) indeed appears to be the only
possibility, since after your post I have tried both

std::ofstream f("ABI_TEST.CSV", std::ios_base::out);

and

std::fstream f("ABI_TEST.CSV", std::ios_base::out);

but they both truncate the file.

Paul
Loading...