Reading Data with scanf

Textbook Background

The C standard library allows us to read formatted data in much the same way we display it. The textbook provides background:

Introduction to scanf

The scanf function (in the stdio.h library) directs the machine to read values for one or more variables from the keyboard. For each desired value, scanf requires

Notes:

When a user enters information into a program, the user types a sequence of characters. Sometimes this information is intended to be a string of characters, such as a name or an address. In other applications, a sequence of characters, such as 123.45, should be interpreted as a number.

When characters are to be considered as numbers, input can follow either of two basic forms:

The library function scanf is commonly used for the latter approach. Using scanf involves several elements. The basics of this work are illustrated in the following code segment:

double a, b;
int numAssigned;

numAssigned = scanf ("%lf", &a);
numAssigned = scanf ("%lf", &b);

As illustrated in this segment,

scanf allows the two reading operations above to be combined within a single statement as follows:

double a, b;
int numAssigned;

numAssigned = scanf ("%lf%lf", &a, &b);

With this example, typing the input

123.4 56.7
gives numAssigned the value 2, since both a and b would be assigned to 123.4 and 56.7, respectively.

Beyond the identification of variables and formats for reading, the scanf can specify other characters that must be part of the input. For example, suppose a program is supposed to read hours and minutes in the format hour:minutes:seconds, such as 12:34:56 or 5:8:27. In this setting, the user is supposed to enter the colon character between integer numbers. The following code segment would perform such a read operation:

int hr, min, sec;
int numAssigned;

numAssigned = scanf ("%d:%d:%d", &hr, &min, &sec);

To verify the input format was correct, we would check numAssigned == 3.

Two Safety-Critical Notes

On Reading Strings

As both textbook readings point out, the %s conversion specification requires a variable of type char* to accompany it. As we should now know well, a pointer to a character may refer to a sequence of available memory addresses, but it does not convey any information about how much memory is available. Thus, only using %s puts your program at risk of causing serious damage through a buffer overflow, because it does not tell scanf how much data it may safely read.

Thus, you should never use the %s conversion specification without an accompanying space limit. While we strongly prefer to use the #define preprocessor directive to establish buffer sizes in a single, clear manner, this dogma does not work well with a conversion string, which must be hard coded.

One way around this is to use a closely coupled pair of #define directives, as in the following example.

#include <stdio.h>

/* Length and format string for character buffers (plus a null terminator) */
#define MAXSTR   127
#define SCNSTR "%127s"

int
main (void)
{
  int foo, bar;
  char buf[MAXSTR+1]; /* Allocate an extra space for the null terminator */
  int numAssigned;
  
                      /* C concatenates string literals */
  numAssigned = scanf ("%d " SCNSTR " %d", &foo, buf, &bar);

  if (numAssigned != 3) {
    printf ("Invalid input\n");
    return 1; /* Signal an error to caller */
  }
  
  printf ("%d %s %d\n",foo,buf,bar);

  return 0;
} // main

This approach also is potentially poor practice, because a subsequent programmer may change only one of the two, but it's less likely to be missed than if a hard-coded value is strewn throughout the program source code.

It turns out we can use the preprocessor's function macro "stringification" to keep these values consistent. In our program's prefatory material, we can define the following.

/* Length of a mutable character buffer (string) */
#define MAXSTR 127

/* Stringify a literal without macro expansion */
#define STR(EXPR) #EXPR

/* Construct a size-limited scanf format string for a character buffer */
#define SCANSTR(LEN) "%" STR(LEN) "s"

Then, because the C compiler concatenates all these string literals together, we can invoke scanf using the preprocessor macro.

numAssigned = scanf ("%d " SCANSTR(MAXSTR) " %d", &foo, buf, &bar);

On Return Values

Note that scanf returns the number of input items assigned, which could be fewer than expected (even zero). Thus, it is generally useful (and highly recommended) to check the return value to make sure that your program received the amount of input it expected and take appropriate action if it did not.

Examples

We may discuss some of the following examples in class