>I can see that it checks whether the end of the file as been reached.
It does indeed.
>Would (feof(fptr)) return 1 if the end of the file had been
>reached and 0 if the end of the file hadn't been reached?
Close, it returns a non-zero value if the end of file has been reached. Or rather, true if the end of file has been reached and false otherwise, as C treats a non-zero value as true and a zero value as false.
> FILE * fptr;
>(I thought) means, store the adress of FILE in fptr.
FILE is an opaque type. Typically it's a structure that contains all kinds of information about a file so that the standard I/O functions can properly work with it and still tell you how things went. For example, here's one implementation for FILE:
Code:
struct _buffer {
HANDLE _fd; /* Character source for the buffer */
struct _deque _buf; /* Queue of buffered characters */
struct _deque _unget; /* Stack of pushed back characters */
char _peek; /* Unread but not yet buffered character */
unsigned long _flag; /* State of the buffer */
char *_tmp; /* The name of the file (if it's temporary) */
};
typedef struct _buffer FILE;
And feof might be implemented as such:
Code:
int feof ( FILE *stream )
{
return stream->_flag & _OPEN && stream->_flag & _EOF;
}
At the risk of confusing you with too much detail, the input functions will work with a buffer of characters. That buffer is filled up using system functions. For example, the rest of the library that works with the above FILE structure eventually gets down to this function for input:
Code:
/*
Return the 0 on success, non-zero on error or EOF
*/
int _intern_fill ( FILE *in )
{
const HANDLE fd = in->_fd;
const size_t size = in->_buf._size;
size_t read_size = size;
char *temp = NULL;
if ( ( temp = malloc ( read_size ) ) != NULL ) {
char *pread = temp;
size_t nread = 0;
/* "Read" any peeked characters first */
if ( in->_flag & _PEEK ) {
in->_flag &= ~_PEEK;
*temp = in->_peek;
pread = temp + 1;
--read_size;
}
/* Load the raw stream characters into our temporary buffer */
if ( ReadFile ( fd, pread, read_size, (LPDWORD)&nread, NULL ) != 0 ) {
if ( pread != temp ) {
/* We "read" a peeked character, so bump the nread count */
++nread;
}
if ( nread != 0 ) {
/* Prep the buffer for streaming */
char *buf = in->_buf._base;
size_t i = 0;
if ( in->_flag & _TEXT )
nread = _compact_newlines ( in, temp, nread );
/* Set up a new owned buffer if necessary */
if ( buf == NULL && in->_flag & _OWNED )
buf = malloc ( size );
if ( buf != NULL ) {
/* Reset the buffer for fresh loading */
_deque_init ( in->_buf, buf, size );
/* Load characters into the buffer */
while ( i < nread && i < size )
_deque_pushf ( in->_buf, temp[i++] );
}
else {
/* Allocation failed or a non-owned buffer was NULL */
in->_flag |= _ERR;
}
}
else {
/* The stream is good, but we hit end-of-file immediately */
in->_flag |= _EOF;
}
}
else
in->_flag |= _ERR;
}
else
in->_flag |= _ERR;
/* Don't forget to clean up the mess */
free ( temp );
return in->_flag & ( _ERR | _EOF );
}
Notice how it sets the flag field for errors and end-of-file. It's this flag that tells feof (and ferror) whether the stream is in an invalid state or not. The actual machinery is somewhat complicated by the buffering mechanism, but it all boils down to this: FILE is an opaque type that holds enough information necessary to work with streams. When you perform I/O on a stream, the C runtime makes calls to the system to do the actual work. The system functions give the C runtime enough information to pass back to you through the standard C interface, which completes the abstraction.