Why does this not work?

S

Stephen Mayes

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (void)
{
static char * contents = "Line1\nLine2\nLine3\nLine4";
FILE * tmp;
char readbuf[256];
size_t len, n = 0;

puts (contents);
len = strlen (contents);

tmp = tmpfile ();

if (fwrite (contents, 1, len, tmp) != len)
puts ("Write failed.");
else if ((n = fread (readbuf, 1, len, tmp)) != len)
puts ("Read failed.");

fclose (tmp);

readbuf[n] = '\0';
puts (readbuf);

return EXIT_SUCCESS;
}
 
S

Stephen Mayes

if (fwrite (contents, 1, len, tmp) != len)
puts ("Write failed.");

fseek (tmp, 0, SEEK_SET);
if ((n = fread (readbuf, 1, len, tmp)) != len)
puts ("Read failed.");

As soon as I hit the send button, the light came on.

I'll be taking a nap now.
 
J

Jack Klein

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (void)
{
static char * contents = "Line1\nLine2\nLine3\nLine4";
FILE * tmp;
char readbuf[256];
size_t len, n = 0;

puts (contents);
len = strlen (contents);

tmp = tmpfile ();

if (fwrite (contents, 1, len, tmp) != len)
puts ("Write failed.");
else if ((n = fread (readbuf, 1, len, tmp)) != len)
puts ("Read failed.");

fclose (tmp);

readbuf[n] = '\0';
puts (readbuf);

return EXIT_SUCCESS;
}

What part of it does not work?

You don't check the value returned by tmpfile() for NULL, which it
could return. In that case, you invoke undefined behavior by calling
fwrite() on a null FILE *.

On the other hand, you need to look up the proper handling of files
opened for update, which is what tmpfile() does, if it succeeds.
There is no reason to expect a read directly following a write to such
a file will work, since it breaks the rules specified by the C
standard.
 
S

Stephen Mayes

Jack Klein said:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (void)
{
static char * contents = "Line1\nLine2\nLine3\nLine4";
FILE * tmp;
char readbuf[256];
size_t len, n = 0;

puts (contents);
len = strlen (contents);

tmp = tmpfile ();

if (fwrite (contents, 1, len, tmp) != len)
puts ("Write failed.");
else if ((n = fread (readbuf, 1, len, tmp)) != len)
puts ("Read failed.");

fclose (tmp);

readbuf[n] = '\0';
puts (readbuf);

return EXIT_SUCCESS;
}

What part of it does not work?
I expected it to print the contents of contents twice. I fixed it with the
fseek. (See me talking to myself.)
You don't check the value returned by tmpfile() for NULL, which it
could return. In that case, you invoke undefined behavior by calling
fwrite() on a null FILE *.
Your right. Thank you.
On the other hand, you need to look up the proper handling of files
opened for update, which is what tmpfile() does, if it succeeds.
There is no reason to expect a read directly following a write to such
a file will work, since it breaks the rules specified by the C
standard.
Would that result in UB?
With the original code on gcc I got the "ReadError." That's what I should
have expected without reseeking the pointer, but a couple of other compilers
seemed to read zeroes or junk from somewhere.
Where IS the file pointer and EOF immediately after the write?
Where do I look this up?
 
L

Lawrence Kirby

On Sun, 12 Jun 2005 20:43:35 -0400, Stephen Mayes wrote:

....
Would that result in UB?

Yes.

"When a file is opened with update mode ('+' as the second or third
character in the above list of mode argument values), both input and
output may be performed on the associated stream. However, output shall
not be directly followed by input without an intervening call to the
fflush function or to a file positioning function (fseek, fsetpos, or
rewind), and input shall not be directly followed by output without an
intervening call to a file positioning function, unless the input
operation encounters end-of-file."

Violation of a "shall" requirement as above results in undefined
behaviour. Note that tmpfile() opens the file in "wb+" mode i.e. an update
mode.
With the original code on gcc I got the "ReadError." That's what I should
have expected without reseeking the pointer, but a couple of other compilers
seemed to read zeroes or junk from somewhere.

With undefined behaviour anything the compiler does is correct.
Where IS the file pointer and EOF immediately after the write?

After the write the file pointer is just after the data written which also
happens to be the end of the file. Your fseek() call resolves 2 problems,
the file position the data is read from subsequently and the requirement
above.
Where do I look this up?

A good C book or the standard.

Lawrence
 
C

Chris Torek

Violation of a "shall" requirement as above results in undefined
behaviour. Note that tmpfile() opens the file in "wb+" mode i.e. an update
mode.

All quite correct, of course. One side note, though, the "shall ...
as above" refers to an occurrence of the word "shall" that is *not*
in a "constraints" section of the C standard. If the "shall-violation"
refers to a word that *is* in a "constraints" section, the standard
requires the compiler to produce "at least one diagnostic" as
well.

In other words, something like:

fwrite(...);
fread(...);

does not require the compiler to warn you that you did something wrong.
Other ("more obvious") mistakes require a warning or error (or any
form of diagnostic, however the compiler defines its "diagnostic").
With undefined behaviour anything the compiler does is correct.
Indeed.
After the write the file pointer is just after the data written which also
happens to be the end of the file. Your fseek() call resolves 2 problems,
the file position the data is read from subsequently and the requirement
above.

Also all quite correct -- but I suspect Stephen Mayes was wondering
instead why he got the actual behavior (out of the infinite possible
undefined behaviors) that he saw on the "couple of other compilers".

This is not a compiler issue so much as a library issue, although the
C standard does not distinguish between these. In particular, if you
were to obtain a GCC binary for one of those "other" systems, and
compiled your code with GCC there, it would probably still have the
same odd behavior. The behavior itself arises from what I consider
a "penny-wise, pound-foolish" optimization in a lot of <stdio.h>
implementations.

The Standard's constraints on you (the programmer) allow me (the
implementor) to get away with something like this:

/* I am the implementor so I use __-prefixed names that you,
the user, must avoid. This way I avoid your names and you
avoid mine, and we never step on each other's toes. */
typedef struct __sFILE FILE;
struct __sFILE {
... various fields ...
unsigned char *__buf; /* the stdio buffer */
unsigned char *__p; /* a pointer into the buffer */
int __n; /* a buffer counter */
... more fields ...
};

#define getc(fp) \
(--(fp)->__n >= 0 ? *(fp)->__p++ : __sreadc(fp))
#define putc(c, fp) \
(--(fp)->__n >= 0 ? *(fp)->__p++ = (c) : __swritec(fp, c))

Note how I have "cleverly" used the same two fields, __n and __p,
to count the number of characters I can stick into the buffer (if
the file is being written) or read out of the buffer (if the file
is being read). I can do this because when you call fseek() or
rewind() (or hit EOF or do any of the other things that reset the
file from "read mode" or "write mode" to "ready to use either mode"),
I can zero out fp->__n. Then only my __sreadc() and __swritec()
functions have to check whether to put the file into "read mode"
(__sreadc()) or "write mode" (__swritec()).

The thing that is really dumb about this is that, for the price of
a single extra "int" per __sFILE structure, I could have put in
TWO counters:

unsigned char *__buf; /* the stdio buffer */
unsigned char *__p; /* a pointer into the buffer */
int __r; /* a "reading buffer" counter */
int __w; /* a "writing buffer" counter */

and now I can have getc() use --fp->__r, while putc() uses --fp->__w.
I simply guarantee that at least one of the two counters is always
zero, and suddenly my stdio can switch from read-mode to write-mode
"automatically", and not return total garbage when you call getc()
on a file that is in write-mode.

And of course, this is just what I did in my stdio for 4.xBSD. The
system you were using that had gcc, and gave you a read error/EOF,
may have been using my stdio (or else whoever wrote the stdio for
that system saw the same obvious thing).

The C standard has the "seek between mode changes" requirement to
accomodate the obnoxious stdio versions that share a single counter.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,164
Messages
2,570,898
Members
47,439
Latest member
shasuze

Latest Threads

Top