Books / Introduction to C Programming Language / Chapter 11
Input and output
Input and output from C programs is typically done through the standard I/O library, whose functions etc. are declared in stdio.h
. A detailed descriptions of the functions in this library is given in Appendix B of Kernighan and Ritchie. We’ll talk about some of the more useful functions and about how input-output (I/O) works on Unix-like operating systems in general.
Character streams
The standard I/O library works on character streams, objects that act like long sequences of incoming or outgoing characters. What a stream is connected to is often not apparent to a program that uses it; an output stream might go to a terminal, to a file, or even to another program (appearing there as an input stream).
Three standard streams are available to all programs: these are stdin
(standard input), stdout
(standard output), and stderr
(standard error). Standard I/O functions that do not take a stream as an argument will generally either read from stdin
or write to stdout
. The stderr
stream is used for error messages. It is kept separate from stdout
so that you can see these messages even if you redirect output to a file:
$ ls no-such-file > /tmp/output
ls: no-such-file: No such file or directory
Reading and writing single characters
To read a single character from stdin
, use getchar
:
int c;
c = getchar();
The getchar
routine will return the special value EOF
(usually -1; short for end of file) if there are no more characters to read, which can happen when you hit the end of a file or when the user types the end-of-file key control-D to the terminal. Note that the return value of getchar
is declared to be an int
since EOF
lies outside the normal character range.
To write a single character to stdout
, use putchar
:
putchar('!');
Even though putchar
can only write single bytes, it takes an int
as an argument. Any value outside the range 0..255 will be truncated to its last byte, as in the usual conversion from int
to unsigned char
.
Both getchar
and putchar
are wrappers for more general routines getc
and putc
that allow you to specify which stream you are using. To illustrate getc
and putc
, here’s how we might define getchar
and putchar
if they didn’t exist already:
int
getchar2(void)
{
return getc(stdin);
}
int
putchar2(int c)
{
return putc(c, stdout);
}
Note that putc
, putchar2
as defined above, and the original putchar
all return an int
rather than void
; this is so that they can signal whether the write succeeded. If the write succeeded, putchar
or putc
will return the value written. If the write failed (say because the disk was full), then putc
or putchar
will return EOF
.
Here’s another example of using putc
to make a new function putcerr
that writes a character to stderr
:
int
putcerr(int c)
{
return putc(c, stderr);
}
A rather odd feature of the C standard I/O library is that if you don’t like the character you just got, you can put it back using the ungetc
function. The limitations on ungetc
are that (a) you can only push one character back, and (b) that character can’t be EOF
. The ungetc
function is provided because it makes certain high-level input tasks easier; for example, if you want to parse a number written as a sequence of digits, you need to be able to read characters until you hit the first non-digit. But if the non-digit is going to be used elsewhere in your program, you don’t want to eat it. The solution is to put it back using ungetc
.
Here’s a function that uses ungetc
to peek at the next character on stdin
without consuming it:
/* return the next character from stdin without consuming it */
int
peekchar(void)
{
int c;
c = getchar();
if(c != EOF) ungetc(c, stdin); /* puts it back */
return c;
}
Formatted I/O
Reading and writing data one character at a time can be painful. The C standard I/O library provides several convenient routines for reading and writing formatted data. The most commonly used one is printf
, which takes as arguments a format string followed by zero or more values that are filled in to the format string according to patterns appearing in it.
Here are some typical printf
statements:
printf("Hello\n"); /* print "Hello" followed by a newline */
printf("%c", c); /* equivalent to putchar(c) */
printf("%d", n); /* print n (an int) formatted in decimal */
printf("%u", n); /* print n (an unsigned int) formatted in decimal */
printf("%o", n); /* print n (an unsigned int) formatted in octal */
printf("%x", n); /* print n (an unsigned int) formatted in hexadecimal */
printf("%f", x); /* print x (a float or double) */
/* print total (an int) and average (a double) on two lines with labels */
printf("Total: %d\nAverage: %f\n", total, average);
For a full list of formatting codes see Table B-1 in Kernighan and Ritchie, or run man 3 printf
.
The inverse of printf
is scanf
. The scanf
function reads formatted data from stdin
according to the format string passed as its first argument and stuffs the results into variables whose addresses are given by the later arguments. This requires prefixing each such argument with the &
operator, which takes the address of a variable.
Format strings for scanf
are close enough to format strings for printf
that you can usually copy them over directly. However, because scanf
arguments don’t go through argument promotion (where all small integer types are converted to int
and float
s are converted to double
), you have to be much more careful about specifying the type of the argument correctly. For example, while printf("%f", x)
will work whether x
is a float
or a double
, scanf("%f", &x)
will work only if x
is a float
, which means that scanf("%lf", &x)
is needed if x
is in fact a double
.
Some examples:
scanf("%c", &c); /* like c = getchar(); c must be a char; will NOT put EOF in c */
scanf("%d", &n); /* read an int formatted in decimal */
scanf("%u", &n); /* read an unsigned int formatted in decimal */
scanf("%o", &n); /* read an unsigned int formatted in octal */
scanf("%x", &n); /* read an unsigned int formatted in hexadecimal */
scanf("%f", &x); /* read a float */
scanf("%lf", &x); /* read a double */
/* read total (an int) and average (a float) on two lines with labels */
/* (will also work if input is missing newlines or uses other whitespace, see below) */
scanf("Total: %d\nAverage: %f\n", &total, &average);
For a full list of formatting codes, run man 3 scanf
.
The scanf
routine usually eats whitespace (spaces, tabs, newlines, etc.) in its input whenever it sees a conversion specification or a whitespace character in its format string. The one exception is that a %c
conversion specifier will not eat whitespace and will instead return the next character whether it is whitespace or not. Non-whitespace characters that are not part of conversion specifications must match exactly. To detect if scanf
parsed everything successfully, look at its return value; it returns the number of values it filled in, or EOF
if it hits end-of-file before filling in any values.
The printf
and scanf
routines are wrappers for fprintf
and fscanf
, which take a stream as their first argument, e.g.:
fprintf(stderr, "BUILDING ON FIRE, %d%% BURNT!!!\n", percentage);
This sends the output to the standard error output handle stderr
. Note the use of “%%” to print a single percent in the output.
Rolling your own I/O routines
Since we can write our own functions in C, if we don’t like what the standard routines do, we can build our own on top of them. For example, here’s a function that reads in integer values without leading minus signs and returns the result. It uses the peekchar
routine we defined above, as well as the isdigit
routine declared in ctype.h
.
/* read an integer written in decimal notation from stdin until the first
* non-digit and return it. Returns 0 if there are no digits. */
int
readNumber(void)
{
int accumulator; /* the number so far */
int c; /* next character */
accumulator = 0;
while((c = peekchar()) != EOF && isdigit(c)) {
c = getchar(); /* consume it */
accumulator *= 10; /* shift previous digits over */
accumulator += (c - '0'); /* add decimal value of new digit */
}
return accumulator;
}
Here’s another implementation that does almost the same thing:
int
readNumber2(void)
{
int n;
if(scanf("%u", &n) == 1) {
return n;
} else {
return 0;
}
}
The difference is that readNumber2
will consume any whitespace before the first digit, which may or may not be what we want.
Recursive descent parsing
More complex routines can be used to parse more complex input. For example, here’s a routine that uses readNumber
to parse simple arithmetic expressions, where each expression is either a number or of the form (
expression+
expression)
or (
expression*
expression)
. The return value is the value of the expression after adding together or multiplying all of its subexpressions. (A complete program including this routine and the others defined earlier that it uses can be found examples/IO/calc.c.
#define EXPRESSION_ERROR (-1)
/* read an expression from stdin and return its value */
/* returns EXPRESSION_ERROR on error */
int
readExpression(void)
{
int e1; /* value of first sub-expression */
int e2; /* value of second sub-expression */
int c;
int op; /* operation: '+' or '*' */
c = peekchar();
if(c == '(') {
c = getchar();
e1 = readExpression();
op = getchar();
e2 = readExpression();
c = getchar(); /* this had better be ')' */
if(c != ')') return EXPRESSION_ERROR;
/* else */
switch(op) {
case '*':
return e1*e2;
break;
case '+':
return e1+e2;
break;
default:
return EXPRESSION_ERROR;
break;
}
} else if(isdigit(c)) {
return readNumber();
} else {
return EXPRESSION_ERROR;
}
}
Because this routine calls itself recursively as it works its way down through the input, it is an example of a recursive descent parser. Parsers for more complicated languages like C are usually not written by hand like this, but are instead constructed mechanically using a parser generator.
File I/O
Reading and writing files is done by creating new streams attached to the files. The function that does this is fopen
. It takes two arguments: a filename, and a flag that controls whether the file is opened for reading or writing. The return value of fopen
has type FILE *
and can be used in putc
, getc
, fprintf
, etc. just like stdin
, stdout
, or stderr
. When you are done using a stream, you should close it using fclose
.
Here’s a program that reads a list of numbers from a file whose name is given as argv[1]
and prints their sum:
#include <stdio.h>
#include <stdlib.h>
/*
* Sum integers in a file.
*
* 2018-01-24 Includes bug fixes contributed by Zhe Hua.
*/
int
main(int argc, char **argv)
{
FILE *f;
int x;
int sum;
if(argc != 2) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
exit(1);
}
f = fopen(argv[1], "r");
if(f == 0) {
/* perror is a standard C library routine */
/* that prints a message about the last failed library routine */
/* prepended by its argument */
perror(argv[1]);
exit(2);
}
/* else everything is ok */
sum = 0;
while(fscanf(f, "%d", &x) == 1) {
sum += x;
}
printf("%d\n", sum);
/* not strictly necessary but it's polite */
fclose(f);
return 0;
}
To write to a file, open it with fopen(filename, "w")
. Note that as soon as you call fopen
with the "w"
flag, any previous contents of the file are erased. If you want to append to the end of an existing file, use "a"
instead. You can also add +
onto the flag if you want to read and write the same file (this will probably involve using fseek
).
Some operating systems (Windows) make a distinction between text and binary files. For text files, use the same arguments as above. For binary files, add a b
, e.g. fopen(filename, "wb")
to write a binary file.
/* leave a greeting in the current directory */
#include <stdio.h>
#include <stdlib.h>
#define FILENAME "hello.txt"
#define MESSAGE "hello world"
int
main(int argc, char **argv)
{
FILE *f;
f = fopen(FILENAME, "w");
if(f == 0) {
perror(FILENAME);
exit(1);
}
/* unlike puts, fputs doesn't add a newline */
fputs(MESSAGE, f);
putc('\n', f);
fclose(f);
return 0;
}