Skip to main content
\(\newcommand{\doubler}[1]{2#1} \newcommand{\binary}{\texttt} \newcommand{\hex}{\texttt} \newcommand{\octal}{\texttt} \newcommand{\prog}{\texttt} \newcommand{\lt}{ < } \newcommand{\gt}{ > } \newcommand{\amp}{ & } \)

Section2.8write and read Functions

In Section 2.5 we used the printf and scanf functions from the C Standard Library to convert between C data types and single characters written on the screen or read from the keyboard. In this section, we introduce the two system call functions, write and read. We will use the write function to send bytes to the screen and the read function to get bytes from the keyboard.

When these low-level functions are used, it is the programmer's responsibility to convert between the individual characters and the C/C++ data type storage formats. Although this clearly requires more programming effort, we will use them instead of printf and scanf for most of the programs in this book in order to better illustrate data storage formats.

We start with the C program in Listing 2.8.1, which shows how to display the character 'A' on the screen.

/*
 * oneChar.c
 * Writes a single character on the screen.
 * Bob Plantz - 23 Nov 2015
 */

#include <unistd.h>

int main(void)
{
   char aLetter = 'A';
   write(STDOUT_FILENO, &aLetter, 1);  // STDOUT_FILENO is
                                        // defined in unistd.h
   return 0;
}
Listing2.8.1Use write to write one character to the screen.

This program allocates one byte of memory as a char variable and names it “aLetter.” This byte is initialized to the bit pattern \(\hex{0x41}\) ('A' from Table 2.7.1). The write function is invoked to display the character on the screen. The arguments to write are:

  1. STDOUT_FILENO is defined in the system header file, unistd.h. It is the GNU/Linux file descriptor for standard out (usually the screen). GNU/Linux sees all devices as files. When a program is started the operating system opens a path to standard out and assigns it as file descriptor number 1.  1 

  2. &aLetter is a memory address. The sequence of one-byte bit patterns starting at this address will be sent to standard out by the write function.

  3. 1 (one) is the number of bytes that will be sent (to standard out) as a result of this call to write.

The program returns a \(0\) to the operating system.

Now let's consider a program that echoes each character entered from the keyboard.

/* echoChar1.c
 * Echoes a character entered by the user.
 * Bob Plantz - 26 July 2016
 */

#include <unistd.h>

int main(void)
{
  char aLetter;

  write(STDOUT_FILENO, "Enter one character: ", 21); // prompt user
  read(STDIN_FILENO, &aLetter, 1);                   // one character
  write(STDOUT_FILENO, "You entered: ", 13);         // message
  write(STDOUT_FILENO, &aLetter, 1);                 // echo character
      
  return 0;
}
Listing2.8.2Echoing single characters entered on the keyboard. (C)

As in the program in Listing 2.8.1 we allocate one char variable to hold the character. In the program in Listing 2.8.2 we need to read the character that results from the user typing on the keyboard. This is accomplished by calling the read function. Since we wish to read from the keyboard, we will read from STDIN_FILENO.

The read function will store the keyboard character in memory, so we need to pass it an address of the place to store it. This is done with the “address of” operator, &aLetter.

A run of this program gave:

pi@rpi3:~/chp02 $ ./echoChar1
Enter one character: a
You entered: api@rpi3:~/chp02 $
pi@rpi3:~/chp02 $ 

which probably looks like the program is not working correctly to you.

Look more carefully at the program behavior. It illustrates some important issues when using the read function. First, how many keys did the user hit? There were actually two keystrokes, the ‘a’ key and the return key. In fact, the program waits until the user hits the return key. The user could have used the delete key to change the character before hitting the return key.

Next, the program correctly echoes the first key hit then terminates. Upon program termination the shell prompt, bob$, is displayed. But the return character is still in the input buffer, and the shell program reads it. The result is the same as if the user had simply pressed the return key in response to the shell prompt.

Here is another run where I entered three characters before hitting the return key:

pi@rpi3:~/chp02 $ ./echoChar1
Enter one character: abc
You entered: api@rpi3:~/chp02 $ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
pi@rpi3:~/chp02 $ 

Again, the program correctly echoes the first character, but the two characters bc remain in the input line buffer. When echoChar1 terminates the shell program reads the remaining characters from the line buffer and interprets them as a command. In this case, bc is a program, so the shell executes that program. (If you wish to learn about bc, use “man bc”.)

An important point of the program in Listing 2.8.2 is to illustrate the simplistic behavior of the write and read functions. They work at a very low level. It is your responsibility to design your program to interpret each byte that is written to the screen or read from the keyboard.

This example illustrates two issues that a programmer must consider when storing data in memory in addition to its location(s):

  1. How many bits are required to store the data?

    In order to answer this we need to know how many different values are allowed for the particular data item. Study the two examples above—two bits and three bits—and you can see that adding a bit doubles the number of possible values. Also, notice that we might not use all the possible bit patterns.

  2. What is the code for storing the data?

    Most of the data we deal with in everyday life is not expressed in terms of zeros and ones. In order to store it in computer memory, the programmer must decide upon a code of zeros and ones to use. In the above (three bit) example we used \(\binary{000}\) to represent a letter grade of ‘A’, \(\binary{001}\) to represent ‘B’, etc.

Subsection2.8.1Exercises

1

Modify the program in Listing 2.8.2 so that it prompts the user to enter an entire line, reads the line, then echoes the entire line. Read only one byte at a time from the keyboard.

Hint Solution
2

Modify the program in Exercise 2.8.1.1 so that after it reads the line typed on the keyboard, it replaces the ‘\n’ character with a NUL character. Now you have stored the input as a C-style string, and you can echo it with:

printf("You entered:\n%s\n", aString);
Hint Solution
3

Write a C program that prompts the user to enter a line of text on the keyboard then echoes the entire line. The program should continue echoing each line until the user responds to the prompt by not entering any text and hitting the return key. Your program should have two functions, writeStr and readLn, in addition to the main function. The text string itself should be stored in a char array in main. Both functions should operate on NUL-terminated text strings.

  • writeStr takes one argument, a pointer to the string to be displayed, and it returns the number of characters actually displayed. It uses the write system call function to write characters to the screen.

  • readLn takes two arguments, one that points to the char array where the characters are to be stored and one that specifies the maximum number of characters to store in the char array. Additional keystrokes entered by the user should be read from the OS input buffer and discarded. readLn should return the number of characters actually stored in the char array. It should not store the newline character ‘\n’. It uses the read system call function to read characters from the keyboard.

Solution