CMPSC 311, Introduction to Systems Programming

The C Standard I/O Library



Reading



#include <stdio.h>

See <wchar.h> for wide-character versions.

In C11, see <uchar.h>, and various sections of the Standard, for Unicode characters and strings.  There is support for UTF-8, 16-bit Unicode, and 32-bit Unicode, mapped to the more flexible wide-character type.



Logical data streams
Types
Macros



Functions

File access functions
Formatted input/output functions
Character input/output functions
Direct input/output functions (block I/O)
File positioning functions
Error-handling functions
Operations on files


Sample usage, from a simple command shell

#include <stdio.h>

FILE *infile;
char *infile_name;

if (... should take input from stdin ...)
  { infile = stdin; infile_name = "[stdin]"; }
else
  { infile_name = argv[... something ...]; /* also use strdup()? */
    infile = fopen(infile_name, "r");      /* read-only */
    if (infile == NULL)
      {
        fprintf(stderr, "%s: cannot open file %s: %s\n",
          program_name, infile_name, strerror(errno));
        exit(EXIT_FAILURE); /* or something less brutal */
      }
  }

char buffer[128];

while (fgets(buffer, sizeof(buffer), infile) != NULL)
  {
    /* Note that buffer has a newline at the end if the input line
     *   was short enough to fit.
     * NULL indicates end-of-file or error.
     * Is line-too-long an error?
     */
    if (verbose) printf("%s: have: %s", program_name, buffer);

    /* do the work */
  }

/* No more input.  Was it an input error or just end-of-file? */

if (feof(infile))
  { printf("%s: end of input\n", program_name); }

if (ferror(infile))
  { printf("%s: error reading input\n", program_name); }


if (fclose(infile) != 0)

  {
    fprintf(stderr, "%s: cannot close file %s: %s\n",
      program_name, infile_name, strerror(errno));
    exit(EXIT_FAILURE); /* or something less brutal */
  }




Sample design, read one or more files, line-by-line


int read_file(char *file);
int read_line(FILE *fileptr);
  // return 0 for success, -1 for failure



if (read_file(input_file_name) == -1)
  {
    printf("read_file() failed: %s\n", input_file_name);
  }



int read_file(char *file)
{
  if (file == NULL)
    {
     
printf("read_file() failed: NULL arg\n");
      return -1;
    }

  FILE *input = fopen(file, "r");

  if (input == NULL)
    {
      printf("read_file() failed: %s not opened: %s\n",
        file, strerror(errno));
      return -1;
    }
 
  while (read_line(input) == 0) ;
 
  fclose(input);

  return 0;
}



int read_line(FILE *fileptr)
{
  if (fileptr == NULL)
    { return -1; }

  char buffer[1024];

 
if (fgets(buffer, sizeof(buffer), fileptr) == NULL)
    {
      return -1;  // EOF or error
    }

  ... do something with buffer

  return 0;
}



What should you do if there is an input line longer than 1024 characters?



Excerpt from OpenSolaris, <stdio.h>, the stripped-down stand-alone subset of the C library, where everything is really unbuffered
typedef struct {
unsigned char _flag; /* state of the stream */
int _file; /* file descriptor */
ssize_t _len; /* total len of file */
ssize_t _offset; /* offset within the file */
char _name[256]; /* name of the file (for debugging) */
} FILE;



Excerpt from OpenSolaris, <stdio.h>  (iob = I/O Buffer)
#define	BUFSIZ	1024
#define _SBFSIZ 8

extern struct _iobuf {
int _cnt;
unsigned char *_ptr;
unsigned char *_base;
int _bufsiz;
short _flag;
char _file; /* should be short */
} _iob[];

#define _IOFBF 0
#define _IOREAD 01
#define _IOWRT 02
#define _IONBF 04
#define _IOMYBUF 010
#define _IOEOF 020
#define _IOERR 040
#define _IOSTRG 0100
#define _IOLBF 0200
#define _IORW 0400

#define NULL 0
#define FILE struct _iobuf
#define EOF (-1)

#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])
#define	getc(p)		(--(p)->_cnt >= 0 ? ((int)*(p)->_ptr++) : _filbuf(p))

#define putc(x, p) (--(p)->_cnt >= 0 ?\
(int)(*(p)->_ptr++ = (unsigned char)(x)) :\
(((p)->_flag & _IOLBF) && -(p)->_cnt < (p)->_bufsiz ?\
((*(p)->_ptr = (unsigned char)(x)) != '\n' ?\
(int)(*(p)->_ptr++) :\
_flsbuf(*(unsigned char *)(p)->_ptr, p)) :\
_flsbuf((unsigned char)(x), p)))
Elsewhere,
/* The routine _flsbuf may or may not actually flush the output buffer.  If
* the file is line-buffered, the fact that iop->_cnt has run below zero
* is meaningless: it is always kept below zero so that invocations of putc
* will consistently give control to _flsbuf, even if the buffer is far from
* full. _flsbuf, on seeing the "line-buffered" flag, determines whether the
* buffer is actually full by comparing iop->_ptr to the end of the buffer
* iop->_base + iop->_bufsiz. If it is full, or if an output line is
* completed (with a newline), the buffer is flushed. (Note: the character
* argument to _flsbuf is not flushed with the current buffer if the buffer
* is actually full -- it goes into the buffer after flushing.)
*/

extern int _flsbuf(unsigned char c, FILE *iop);

Translated to "readable C",
int putc(int x, FILE *p)
{
unsigned char xx = (unsigned char)(x);

if (--p->_cnt >= 0)
{ return (int)(*p->_ptr++ = xx); }
else
{
if ((p->_flag & _IOLBF) && -p->_cnt < p->_bufsiz)
{
if ((*p->_ptr = xx) != '\n')
{ return (int)(*p->_ptr++); }
else
{ return _flsbuf(*(unsigned char *)p->_ptr, p); }
}
else
{ return _flsbuf(xx, p); }
}
}



Excerpt from Mac OS X, <stdio.h>

/* stdio buffers */
struct __sbuf {
        unsigned char   *_base;
        int             _size;
};

/* hold a buncha junk that would grow the ABI */
struct __sFILEX;

typedef struct __sFILE {
  unsigned char *_p;      /* current position in (some) buffer */
  int     _r;             /* read space left for getc() */
  int     _w;             /* write space left for putc() */
  short   _flags;         /* flags, below; this FILE is free if 0 */
  short   _file;          /* fileno, if Unix descriptor, else -1 */
  struct  __sbuf _bf;     /* the buffer (at least 1 byte, if !NULL) */
  int     _lbfsize;       /* 0 or -_bf._size, for inline putc */

  /* operations */
  void    *_cookie;       /* cookie passed to io functions */
  int     (*_close)(void *);
  int     (*_read) (void *, char *, int);
  fpos_t  (*_seek) (void *, fpos_t, int);
  int     (*_write)(void *, const char *, int);

  /* separate buffer for long sequences of ungetc() */
  struct  __sbuf _ub;     /* ungetc buffer */
  struct __sFILEX *_extra; /* additions to FILE to not break ABI */
  int     _ur;            /* saved _r when _r is counting ungetc data */

  /* tricks to meet minimum requirements even when malloc() fails */
  unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */
  unsigned char _nbuf[1]; /* guarantee a getc() buffer */

  /* separate buffer for fgetln() when line crosses buffer boundary */
  struct  __sbuf _lb;     /* buffer for fgetln() */

  /* Unix stdio files get aligned to block boundaries on fseek() */
  int     _blksize;       /* stat.st_blksize (may be != _bf._size) */
  fpos_t  _offset;        /* current lseek offset (see WARNING) */
} FILE;



This should now make sense to you:

/*  Functions internal to the implementation. */
int  __srget(FILE *);
int  __swbuf(int, FILE *);

#define __sgetc(p) (--(p)->_r < 0 ? __srget(p) : (int)(*(p)->_p++))

static __inline int __sputc(int _c, FILE *_p)
{
  if (--_p->_w >= 0 || (_p->_w >= _p->_lbfsize && (char)_c != '\n'))
    return (*_p->_p++ = _c);
  else
    return (__swbuf(_c, _p));
}




Last revised, 18 Feb. 2013