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.