Table of Contents
Odd as it may sound, the C language actually has no built-in I/O statements. The designers of the C language were careful not to include any features that could be implemented as subprograms, and I/O fell into this category.
C I/O is performed by functions in the C standard libraries, collections of subprograms that are written in C. The standard libraries include a large number of functions that perform I/O, manipulate character strings, perform common math functions, etc.
The C interfaces to the low level kernel I/O routines are
provided by functions like open()
,
close()
, read()
and write()
. The read() and write() functions
simply read and write blocks of bytes to or from an I/O
device. This is the lowest level and
most direct and efficient way to perform
input and output in bulk. It is the best approach for programs
like cp, which do not need to inspect
every byte being transferred.
Stream I/O is another layer of software
on top of the low level functions that allows us to
conveniently and efficiently read or write one
character at a time. When we write a character to a stream, it
is simply placed into a memory buffer (array), which is much
faster than actually writing it to an I/O device.
When that buffer is full, the whole buffer is dumped to the
I/O device using a low-level write()
. Writing
large blocks of data to a device like a disk is much more
efficient than writing one character at a time, so this type of
buffering greatly increases efficiency. Likewise, a low-level
read()
is used to read a block of data from a device
into a memory buffer, from which stream I/O functions can quickly
fetch one character at a time.
Recall from Table 3.9, “Standard Streams” that all Unix
processes have three standard I/O streams from the moment they
are born.
The C standard libraries provide a set of convenience functions
specifically for reading
from the standard input stream and writing to the standard output
stream. The standard error stream is accessed using the more
general functions used for any other stream.
Names for the standard streams are defined in the
header file stdio.h
, which we can
incorporate into the program using:
#include <stdio.h>
Table 18.1. Standard Stream Names
C Stream Name | Stream |
---|---|
stdin | Standard Input |
stdout | Standard Output |
stderr | Standard Error |
The most basic C stream I/O functions are getc()
and
putc()
, which read and write a single character.
int ch; ch = getc(stdin); putc(ch, stdout);
getc()
function returns an integer and the
putc()
function takes
an integer as the first argument. More on this in
Chapter 17, Data Types.
Other stream I/O functions that input or output strings
and numbers are built on top of
getc()
and putc()
. You can also write
additional stream I/O functions of your
own using getc()
and putc()
.
The getchar()
and putchar()
functions
are provided for convenience. They read and
write a character from the standard input and standard output.
int ch; ch = getchar(); // Same as getc(stdin); putchar(ch); // Same as putc(ch, stdout);
The puts()
and gets()
functions read
and write simple strings, which in C are arrays of characters,
with a null byte (ISO character code 0, '\0') marking the end of
the content.
Arrays are discussed in Chapter 24, Arrays. For now, we will only use simple examples necessary for understanding basic I/O.
The gets()
function is dangerous, since
it may input a string longer than the array provided as an
argument.
This could lead to corruption of other variables in the program.
Hence, other functions such as the more general
fgets()
should be used
instead, even when reading from stdin.
fgets(string, MAX_STRING_LEN, stdin);
Note that fgets()
retains the newline ('\n')
character at the
end of the string. If this is not desired, it must be removed
manually after calling fgets()
, or some other
function should be used for input.
A more sophisticated interface is offered by the POSIX standard
getline()
function, which requires knowledge of
dynamic memory allocation (covered later).
#include <stdio.h> #include <sysexits.h> #define MAX_NAME_LEN 100 int main() { // Add 1 for null byte so that MAX_NAME_LEN means what it says char name[MAX_NAME_LEN + 1]; fputs("Please input your name: ", stdout); fgets(name, MAX_NAME_LEN + 1, stdin); fputs(name, stdout); return EX_OK; }
The puts()
function appends a newline character
to the string, so the next output will be on a new line. If
this is not desired, one can use fputs()
instead.
This would be the case for strings read by
fgets()
, which already contain the newline that
was read from input.
fputs(string, stdout);
The printf()
and scanf()
functions
can be used for more complex input and output. Both printf()
and scanf()
require the first argument to be a
format string, optionally followed by
additional arguments that must match the format string.
The format string is similar to that used by the
printf command, but the C function has
many more options for handling various data types.
For each argument after the format string, the format string must contain a format specifier that matches the type of the argument.
int fahrenheit = 76; printf("The temperature is %d (%d Celsius)\n", fahrenheit, (fahrenheit - 32) * 5 / 9);
Some of the most common format specifiers for printf are
outlined in
Table 18.2, “Format specifiers for printf()”. For more
information, see the printf(3)
man page, i.e.
run man 3 printf to get the section
3 man page about the function instead of the section 1 man
page about the Unix command.
Table 18.2. Format specifiers for printf()
Type | Number Format | Format specifier |
---|---|---|
char, short, int | printable character | %c |
string (character array) | printable characters | %s |
char, short, int | decimal | %d |
char, short, int | octal | %o |
char, short, int | hexadecimal | %x |
unsigned char, unsigned short, unsigned int | decimal | %u |
size_t (used for array subscripts) | decimal | %zu |
float, double | decimal | %f |
float, double | scientific notation | %e |
float, double | double or scientific notation | %g |
long double | decimal | %Lf |
Prefixing any numeric format specifier with a lower case L ('l')
corresponds to prefixing the type with 'long'. For example,
"%ld" is for long int
, "%lu" for
"unsigned long int", "%lld" for long long int
,
"%lf" for long double
.
It may appear that the printf format specifiers don't always
match the argument type, but this is because char
,
short
, and float
arguments
are promoted to int
or double
when passed to a function. Because of this,
"%f" is used for both float
and double
,
and "%d" and "%c" can be used for char
,
short
, and int
.
The scanf()
function reads formatted text input and
converts the
values to the proper binary format based on the format specifier
matching each argument.
int temperature; double pressure; scanf("%d %lf", &temperature, &pressure);
The "%d" format specifier tells scanf to read the sequence
of characters
as a decimal integer and convert it to the binary format of
an int
. The "%lf" tells scanf to read the next
sequence of characters as a decimal real number and convert it
to the binary format of a double
(not a
long double
as it would mean to printf
.
The binary
values are then stored in the variables (memory locations)
called temperature and pressure.
Note that each argument after the format string is prefixed with an ampersand (&). This is because in C, all arguments in function calls are passed by value, meaning that the function gets a copy of the value, rather than accessing the caller's copy. This is explained in detail in Chapter 21, Subprograms. For example, consider the following printf() call:
printf("The area is %f.\n", area);
The printf()
function only gets a copy of the
value of area
. It does not have access to the
variable area
, because it does not know what
memory address area
represents. Hence, it is
impossible for printf()
to modify the value of
area
.
However, an input function like scanf()
needs to modify the variables passed to it. We allow it to
do so by passing it the memory address
of each variable, rather than just a copy of the value.
Recall that a variable is simply a name for a memory location where some value is stored. An ampersand (&) preceding a variable name represents the address of the variable, whereas the variable name alone would represent the value contained at that address.
By passing the address of a variable to scanf()
,
we give it the ability to put something in that memory location.
Some of the most common format specifiers for scanf()
are outlined in
Table 18.3, “Format specifiers for scanf()”.
Table 18.3. Format specifiers for scanf()
Type | Number Format | Format specifier |
---|---|---|
char | printable character | %c |
char | decimal | %hhd |
short | decimal | %hd |
int | decimal | %d |
int | octal | %o |
int | hexadecimal | %x |
int | hexadecimal if input begins with "0x", octal if it begins with "0", otherwise decimal | %i |
long int | decimal | %ld |
long long int | decimal | %lld |
float | decimal | %f |
double | decimal | %lf |
long double | decimal | %Lf |
printf()
, scanf()
format
specifiers do not match multiple types. This is because the
arguments to scanf()
are addresses, not values,
and are never promoted. Hence, the format specifiers must
match the type of the variable exactly.
gets()
, scanf()
may input a
string too large for the character array provided, so it
is considered a dangerous input function for strings. It
should only be used to input numeric data or single characters.
Don't use printf()
or scanf()
where
a simpler function such as putchar()
,
getchar()
, puts()
, or
fgets()
will do. Using a function that scans
the format string for format specifiers that are not there
is a waste of CPU time.
printf("This is silly, just use puts() or fputs().\n");
The printf()
and scanf()
functions do
not directly support complex
data types. Instead, it
is left to the programmer to decide how complex numbers are
represented in input and output as two separate
float
or double
values. Functions
such as creal()
and cimag()
can
be used to extract the real and imaginary parts of a complex
number for output in printf()
as shown in
the section called “Example of C input and output”.
#include <stdio.h> #include <sysexits.h> #include <complex.h> int main(int argc,char *argv[]) { double a, b, c; complex double root1, root2, two_a, discriminant_sqrt; printf("Please enter the coefficients a, b, and c: "); scanf("%lf %lf %lf", &a, &b, &c); /* * Precompute terms that will be used more than once. * Cast RESULT of all-double expression to complex so that only one * promotion occurs. * Convert 2a to complex now to avoid multiple promotions when * computing the roots. */ discriminant_sqrt = csqrt((complex double)(b * b - 4.0 * a * c)); two_a = 2.0 * a; root1 = (-b + discriminant_sqrt) / two_a; root2 = (-b - discriminant_sqrt) / two_a; printf("The roots of %fx^2 + %fx + %f are:\n", a, b, c); printf("%g + %gi\n", creal(root1), cimag(root1)); printf("%g + %gi\n", creal(root2), cimag(root2)); return EX_OK; }
Output from the program above:
Please enter the coefficients a, b, and c: 1 2 3 The roots of 1.000000x^2 + 2.000000x + 3.000000 are: -1 + 1.41421i -1 + -1.41421i
The puts()
, and printf()
functions are
special cases that implicitly use stdout.
To print to stderr, we simply use the more general functions
putc()
and fputs()
. These are the
same functions we would use to print to any other file stream that
our program opened, as discussed in Chapter 26, File I/O.
fputs("Hello, world!\n", stdout); // Same as puts("Hello, world!"); fputs("Sorry, radius must be non-negative.\n", stderr); fprintf(stderr, "Sorry, radius %f is invalid. It must be non-negative.\n", radius);
What is low-level I/O in C?
What is stream I/O in C? Explain.
What are the names of the standard input, standard output, and standard error streams in C?
Show a variable definition for a variable called ch
,
a C statement that reads a single character into it from
the standard input, and another statement that prints the
character to the standard output.
Write a C program that asks the user their name, reads a line of text no longer than MAX_NAME_LEN from the standard input, and prints "Hello, " followed by the name to the standard output.
Write a C program that asks the user for the radius of a circle and the prints the area of the circle.
What is the radius? 10 The area is 314.159265.