Objects First


Input and Output

A program is essentially useless if it can't read input to control its operations or output the results when it's finished. Some languages, eg Pascal, provide special I/O (input/output) statements. C, on the other hand, simply uses functions which we may view as methods on the input and output streams.

Standard Input and Output

Given suitable support from the operating system (in the form of special software modules called drivers), a C program can read data from or write data to a wide variety of devices. By convention, C programs running on conventional computers (ie those with keyboards, display screens, etc) have one device designated as standard input. This is usually the keyboard, but could in principle, be anything. (Research in this department is looking at voice input systems, where the standard input might be a microphone, and touch screens, where the standard input may be screen at which you point to make choices.) There is also standard output, which is usually the display screen, but again could be any suitable output device. A third stream, standard error, is usually provided for diagnostic and debugging output. It is usually the same as the standard output, but the provision of the separate stream enables this unneeded output (at least when the program is finally running correctly!) to be diverted to a device that is not the same as standard output. On most machines, these three streams are available to any program without any special initialising steps. Most other input or output streams - files, printers, communications channels, etc - require opening before use.

ANSI Standard Input/Output Library

The ANSI C standard defines a set of library functions which must accompany any standard-conforming compiler. In this section, we'll look at a selection of the most useful I/O functions in the standard library.

Streams and Binary Input/Output

There are two groups of functions in the ANSI C I/O library.
Streams The first group operates on streams, so called because they usually consist of continuous streams of characters, often without a clearly defined end. The characters that I'm typing on this keyboard are a typical stream. Similarly the stream of characters directed to an output window is a typical stream. These two streams have no clearly defined end: I use various separator characters to designate the end of groups of characters which are related - but the separator is itself a character and the stream continues immediately after the separator. Streams usually consist of text data: some of the library functions that operate on streams are designed to read variable length lines of text delimited by new-line or carriage return characters.
Binary I/O The second group provides raw binary I/O to a device. The functions in this group handle most other other types of I/O: they work efficiently when reading or writing fixed length blocks of data from files or other devices.

Stream I/O

The simplest functions which act on a stream simply put single characters on to it or get single characters from it:
#include <stdio.h>

int fputc( char c, FILE *f );
int fgetc( FILE *f );
These specifications, along with most in this section, are found in the standard header file stdio.h.
Observe the standard practice for C library documentation: the specification file for the library or class is included as part of the formal specification of the function as seen in this example. You add to your program the #include ... statement - it will serve to import all the function specifications into your code.
The FILE type is also defined in stdio.h.

The need to designate file handles as pointers, ie to write FILE *, is a historical hangover from some less than ideal decisions made by the original designers of C in 1970 or so. It would have been possible, and neater, to define FILE as a pointer itself (in exactly the same way that we have been defining classes in this course!). Unfortunately, Object Oriented techniques hadn't been widely recognised as useful as early as that. Now there is so much existing C code containing FILE * that it's too late to go back!

If you look in stdio.h, you will see a declaration like:
typedef struct io_buf {
    ....
    } FILE;
Inside the struct you will see all the attributes that the C stream functions need to interact with streams.
fputc outputs a single character, c, to the stream, f and fgetc reads a single character from a stream and returns it. Thus the following function copies characters from the standard input to the standard output:
#include <stdio.h>

void cat() {
      char c;
      while( TRUE ) {
           c = fgetc( stdin );
           fputc( c, stdout );
           }
      }
This function is fine for a program that runs forever on an infinite stream of data, but not too good for real streams. A better version is:
#include <stdio.h>

void cat() {
      char c;
      while( (c = fgetc(stdin)) != EOF ) {
           fputc( c, stdout );
           }
      }
First examine the assignment expression and verify that this works: the expression
(c = fgetc(stdin))
calls fgetc(stdin) and assigns the character read from stdin to c. The value of this expression is the value assigned to c. It is then checked to see whether it is EOF (defined in stdio.h). If it is, the loop terminates and the program finishes. If it's not, the character input is output by fputc to stdout.

But what's the value of EOF? If we look in the ASCII code tables, we will see that all 256 possible values of an eight-bit character are assigned some meaning. Look back and note that fgetc is an int function - not a char one as we might expect! char objects can take values from 0 to 255, leaving a rather large number of choices for EOF which do not conflict with the 256 possible char values. Thus when fgetc finds an end on the input stream (as it might if it was reading a file), it returns EOF (often assigned the value -1), rather than a value in (0,255) which could be interpreted as a char.

Shorthand Notations for stdin and stdout

fputc and fgetc will work on any device which can be read as a stream - a file, a communications line, etc. But C programmers are lazy, so instead of having to type fgetc( stdin ) all the time, there is a shorthand:
int getchar(void);
which is exactly equivalent to fgetc( stdin ). In fact, if you look in stdio.h, you will often find it defined in exactly that way:
#define	getchar()	fgetc(stdin)
which means that when you type getchar() in your program, the compiler substitutes fgetc(stdin). (The C pre-processor, which performs these substitution operations is discussed fully in a later section.) There is a similar shorthand for fputc :
int putchar(char c);
which is equivalent to fputc(c, stdin) and is also often implemented as a macro.

No space left!

Note that fputc is not void - it returns a value. An error will occur if there is no space left on a disc, if the communications line has been disconnected or for some other reason connected with the output device. If an error is encountered, fputc returns -1 and sets the global error number: errno. See the end of this section for a full discussion of how to use the errno variable to determine precisely what went wrong.

String input and output

Reading and writing strings is accomplished with fgets and fputs:
char *fgets( char *s, int n, FILE *stream );
/* stdin abbreviation */
char *gets( char *s );
int fputs( char *s, FILE *stream );
/* stdout abbreviation */
int puts( char *s );
These routines work with null-terminated strings, with some additional quirks. fgets reads characters from stream until it finds a null character, a newline or an EOF or until it has read n characters. The string read is placed in s which, naturally, must be either a char array with at least n elements or a pointer to at least n bytes allocated by malloc. As with all string functions, care must be exercised: fgets will store a null character at the end of the string stored in s (unlike some of the other string routines) but it will not read all of a string which has more than n-1 characters.

gets is prohibited!

Note that the abbreviation, gets, doesn't have a limit on the number of characters read - and so is positively dangerous! fgets should always be used in its place and using gets in this course will earn a well-deserved fail for the offending program!

fputs, as you might expect, writes the string, s to the stream until it encounters the NULL at the end of the string. It will return an error (-1) if there is insufficient space or the stream experiences another error.

fprintf, printf and sprintf

We've already used printf: which is an abbreviation for fprintf(stdout,format, ..);: its operation is more fully discussed elsewhere.

fscanf, scanf, sscanf

These three functions are the input equivalents of fprintf. I find them very difficult to use in practice - especially when used to decode user input - no matter how convenient they may seem for simple test programs. Thus I recommend other ways of dealing with formatted input (eg fgets followed by calls to strtok, strtod, strtol, etc.). This will be more fully discussed elsewhere.

However, when reading from text files generated by other C programs - so that the format of the lines in the file is carefully controlled, then fscanf, etc become a little more useful. This is because you're dealing with well-known input and don't have to worry about a user's concept of what might be 'reasonable' input for the program! Users tend to have a very broad definition of 'reasonable' which is often at considerable variance to fscanf's idea of reasonable!

Opening files

If you want to read or write to a file (other than stdin or stdout), then you must open it first. Calling the fopen function associates a file with a FILE * variable - the file's handle. Once the file is open, you operate on it via the handle. Thus the fopen call is essentially the same as on object constructor - and you can think of it in the same way. The handle is the file: calls to FILE methods, fgetc, fputc, .., operate on the file. The formal specifications are:
FILE *fopen( char *filename, char *mode );
int fclose( FILE *stream );

int feof( FILE *stream );
long int ftell( FILE *stream );
You open a file by passing its name to fopen:
#include <stdio.h>

..
FILE *f;
char *filename = "filex.dat";

f = fopen( filename, "r" );
if ( f != NULL ) {
   /* File successfully opened */
   ....   /* Various operations on the file */
   fclose( f );
   }
else {
   fprintf( stderr, "Error opening file %s\n", filename );
   }
The second argument to fopen is the mode in which you want to open the file: it is a string (formally the type is char *) - even though the most used values are single characters!
Operation on fileValue for mode
read"r"
write a new file"w"
append to an existing file"a"
reading and writing,
starting at beginning
"r+"
reading and writing,
truncate existing file
"w+"
reading and writing,
append to existing file
"a+"
fopen returns NULL if it is unsuccessful - Refer to your text for a full list of the scenarios in which fopen will fail: note that they depend on whether you're trying to read or write to a file.

Where am I?

feof returns TRUE if the stream is at its end.

ftell returns your position in the file - measured in bytes from the beginning. Thus when you open a file at the beginning, ftell returns 0. After you have read n bytes, it will return n.

There is also a function fseek which enables you to move backwards in a file. It will let you move forwards also, but you can do that by reading the file! Use of fseek is not usually consistent with the type of file (text containing lines of variable length) for which the stream I/O functions are best suited - and some types of streams (eg those coming from communications devices, your keyboard, etc) have strictly limited capacity to be re-wound. Calling fseek to ask the OS to wind back the keyboard device by 106 characters isn't likely to be possible - a typical OS will buffer stream input until it is consumed and no longer! However it has use on files on rare occasions: it behaves like lseek - look it up in your text if you ever need it!

errno

Defined in <errno.h>, errno is an integer variable which is set to a positive error code by many I/O functions when an error occurs. A (usually, but unfortunately not always!) meaningful string explaining the error can be obtained by invoking the function
   void perror( char *s );
which will print out the string s followed by an explanation of the error. s should be used to "label" the error message so that you can quickly find out where in your program the problem occurred.

Key terms

drivers
Special software modules which form an interface between users' programs and input and output devices. They are usually part of the operating system, although many systems permit drivers to be dynamically loaded in some way. Although they invariably require a small amount of assembler code, drivers are commonly written in C nowadays. C is particularly suited to this task: its weak typing - a danger for an applications programmer actually makes life easier for writers of drivers.
streams
Continuous streams of input to or output from a computer program. Streams generally have variable length records (or may be unbroken streams of bytes which are not logically broken up into records at all) with little regular structure, eg lines of text in a document, characters typed at a keyboard, etc.

Continue on to Binary Input and Output
Back to the Table of Contents
©
John Morris, 1998