This is an implementation detail that will vary from compiler to compiler. However, one popular implementation whose source code is free to download is GNU libc, so let’s look at that.
The libc package defines fgetc()
as part of libio, as an alias to an internal function:
weak_alias (_IO_getc, fgetc)
(Recall that the standard says functions such as fgetc
must be macros that can be undefined to give you the name of a function, whose address can be taken.) The definition of _IO_getc
is:
int
_IO_getc (FILE *fp)
{
int result;
CHECK_FILE (fp, EOF);
if (!_IO_need_lock (fp))
return _IO_getc_unlocked (fp);
_IO_acquire_lock (fp);
result = _IO_getc_unlocked (fp);
_IO_release_lock (fp);
return result;
}
So this is a wrapper for _IO_getc_unlocked()
, which turns out to be a macro defined in libio.h
:
#define _IO_getc_unlocked(_fp) \
(_IO_BE ((_fp)->_IO_read_ptr >= (_fp)->_IO_read_end, 0) \
? __uflow (_fp) : *(unsigned char *) (_fp)->_IO_read_ptr++)
So, what this implementation does is define a field of the FILE
structure that it calls _IO_read_ptr
. The underlying macro that fgetc()
is a wrapper for dereferences this pointer and then increments it.
Other libraries may do something different, but the basic idea will be the same. Call some lower-level function to read from the file (such as read()
on POSIX), or if it’s important to be portable across different OSes, a wrapper that hides the differences. Maintain some kind of seek pointer or offset in the FILE
structure that you reference when you pass a FILE*
, update, dereference and increment.