Chapter 18. Basic Terminal Input/Output (I/O)

Table of Contents

18.. C Terminal Input/Output (I/O)
C Language Support
C Standard Streams
C Character I/O
C String I/O
C Formatted I/O
Example of C input and output
Using stderr with C
Practice
18.. Fortran Terminal Input/Output (I/O)
Write and Print
Read
The One Statement = One Line Rule
Standard Units
Example of Fortran input and output
Fortran Formatted I/O
Practice
18.. Using Standard Error
Practice

C Terminal Input/Output (I/O)

C Language Support

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.

C Standard Streams

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 NameStream
stdinStandard Input
stdoutStandard Output
stderrStandard Error

C Character I/O

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);
            

Note

The C language treats characters the same as integers. Hence, the 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);
            
C String I/O

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.

Caution

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);
            
C Formatted I/O

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()

TypeNumber FormatFormat specifier
char, short, intprintable character%c
string (character array)printable characters%s
char, short, intdecimal%d
char, short, intoctal%o
char, short, inthexadecimal%x
unsigned char, unsigned short, unsigned intdecimal%u
size_t (used for array subscripts)decimal%zu
float, doubledecimal%f
float, doublescientific notation%e
float, doubledouble or scientific notation%g
long doubledecimal%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.

Note

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()

TypeNumber FormatFormat specifier
charprintable character%c
chardecimal%hhd
shortdecimal%hd
intdecimal%d
intoctal%o
inthexadecimal%x
inthexadecimal if input begins with "0x", octal if it begins with "0", otherwise decimal%i
long intdecimal%ld
long long intdecimal%lld
floatdecimal%f
doubledecimal%lf
long doubledecimal%Lf

Note

Unlike 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.

Caution

Like 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.

Caution

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”.

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
            
Using stderr with C

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);
            
Practice

Note

Be sure to thoroughly review the instructions in Section 2, “Practice Problem Instructions” before doing the practice problems below.
  1. What is low-level I/O in C?

  2. What is stream I/O in C? Explain.

  3. What are the names of the standard input, standard output, and standard error streams in C?

  4. 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.

  5. 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.

  6. 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.