Next Chapter Return to Table of Contents Previous Chapter

CHAPTER 5: STANDARD FILE AND TERMINAL I/O

In this chapter we examine the facilities for I/O on the "standard files": standard input, standard output, and the standard error file. Among these facilities are also the functions sprintf and sscanf for formatted operations to and from strings. Finally, we will look at methods for performing I/O with terminal screens and keyboards.

5.1 Formatted Output

5.2 Standard Files

5.3 Reading Lines of Input

5.4 Formatted Input

5.5 Direct Terminal I/O

5.1 Formatted Output

Topics assumed from previous book: Basic printf formats [2.8], file redirection [3.22].

You are presumably familiar with the basic formats of printf: %c (character), %s (string), %d (decimal integer), %u (decimal unsigned integer), %o (octal integer), %x (hexadecimal integer), %f ("fixed-point" representation of floating-point number), and %e ("E-notation" floating-point number). The modifier l (for "long") can precede d, u, o, or x, to indicate a long int argument.

Format specifiers are actually somewhat more complicated than the simple examples above; the full syntax looks like this:

% flag width .precision conversion

In the simpler forms of printf (such as in K&R), the flag may consist of a minus sign, indicating left-adjustment of the output. The width is a string of digits indicating the minimum output width. (In K&R and in UNIX Version 7, a width beginning with a zero digit indicated that extra output places were to be filled with zeros, not blanks. This feature disappeared in later UNIX manuals, but not always from the library code itself!) The precision (for floating-point formats) is the number of decimal places; for strings, it is the maximum number of characters to print. And the conversion characters were covered in the previous paragraph.

One other format completes the capabilities of basic printf: the g format. When g is specified, printf tries the output both in e and f for mats and prints whichever takes less space. This can be useful for outputs of widely differing magnitude.

Over the years, other features have been found useful and incorporated in newer libraries. New flags are provided: A plus sign (+) produces either a plus (+) or a minus (-) sign on the printed output. A blank produces either a blank or a minus sign. And a # flag produces an "alternate form." For example, %#o causes the (octal) printed result to have a high-order zero, and %#x causes the (hex) printed result to begin with 0x.

One innovation most useful for reliability is that the width and/or precision can be replaced by an asterisk, in which case the appropriate number will be taken from the argument list. For example,

printf("%*.*f\n", fld_width, dec_places, x);

will print the (floating) number x with dec_places number of decimal places and fld_width total field width. Previously, the hard-coded numbers appearing in format strings were likely to be missed when modifying programs with new output parameters.

Some cosmetic changes were also provided: the conversion characters X, E, and G cause the appropriate alphabetic characters to appear in upper case, to the delight of former FORTRAN programmers who never adapted to 1.86272E5 being printed as 1.86272e5. (For a detailed comparison of the K&R, /usr/group, and ANSI C libraries, see the appendix [5-1].)

All of the printf formatting is available for formatted output into a string, using the sprintf function. For example, the invocation

sprintf(s, %3s %3s %2d %2d:%02d:%02d %4d",

weekday, mo, day, hh, mm, ss, yr);

could leave s looking like this:

"Fri Nov 11 15:47:04 1982"

Exercise 5-1. Write a function

char *fround(x, s, nsignif, width, precis)

double x;

char s[];

int nsignif;

int width;

int precis;

which will round x to nsignif significant digits and then "print" the result into the string s in %f format with width and precision specified by width and precis. The function should return the (address) value of s. For example,

printf("%s\n", fround(x, buf, 3, 8, 3));

would produce, when given successive x values of 12.345, .6666, and 4853.4836, the output

12.3

.667

4850.

There are some reliability limitations to the printf family of functions. Since numeric output will occupy as many characters as are needed, it is dangerous to use sprintf to write into strings which are not large enough for the largest possible output, nor should we use printf to write into files whose contents are strictly formatted in size. The only way to be certain of the size of numeric output is to bounds-check each number before printing or to allow the absolute maximum output size for each number.

Oftentimes it is more convenient to use a more specialized, but more reliable, function for converting numbers into strings. The itoa function below is one example. This is much less powerful than the printf functions, but it is suitable for converting integers into small strings. It assumes that the stated string size is inviolate, so a string of 9's is the best response to an out-of-bounds argument, with a Boolean return of NO if the full result would not fit. Here is the itoa function:

itoa:

/* itoa - convert integer n to string (ndigits max) */

#include "local.h"

bool itoa(n, str, ndigits)

int n;

char str[];

int ndigits;

{

int sign;

short i;

bool success;

unsigned un = n;    /* need room for most-negative n */

success = YES;

if (ndigits < 1 | | n < 0 && ndigits < 2)

return (NO);         /* can't even get started */

else if (n == 0)

{

strcpy(str, "0");    /* dispose of a special case */

return (YES);

}

else if (n < 0)

{

sign = -1;

un = -n;

--ndigits;           /* sign consumes one print digit */

}

else

sign = 1;

for (i = 0; i < ndigits && un != 0; ++i)

{

str[i] = `0' + un % 10;

un /= 10;

}

if (un != 0)/            /* still more digits to print, and no room */

{

for (i = 0; i < ndigits; ++i)

str[i] = `9';

success = NO;

}

if (sign < 0)

str[i++] = '-';

str[i] = `\0';

reverse(str);

return (success);

}

5.2 Standard Files

Before the main function is invoked, three standard files are opened, known as the standard input, the standard output, and the standard error files. The standard input is used by getchar and scanf; the standard output is used by printf and putchar. All the information needed to keep track of each file is kept in a struct declared in <stdio.h> with the defined-type FILE [5-2]. Also in <stdio.h> are definitions of three symbols -- stdin, stdout, and stderr -- each of which is defined to be the address of a FILE structure. Thus, stdin has the type FILE *, or "pointer to FILE," as do stdout and stderr.

Most hosted environments for C provide a method for redirecting the standard input and the standard output on the command line:

pgm <filel >file2

will associate stdin with file1, and stdout with file2. This redirection is accomplished by the command language interpreter (e.g., "shell" or COMMAND.COM) on UNIX systems, MS-DOS (since Version 2.0), and many other systems. Other environments provide special startup code in the program itself. In either event, the redirection is accomplished before the main function is called.

Files that are accessed through the FILE mechanism provided by <stdio.h> are known as stream files, or (less officially) as buffered files - "buffered," because most implementations provide a char array (whose size is named BUFSIZ in <stdio.h>) to provide in-memory temporary storage of characters being read or written.

The main advantage of buffering is execution speed. On many systems, input and output are vastly more efficient if done in large chunks; disk transfers in particular are often more efficient in blocks of 256, 512, or 1024 characters. Taking putchar as an example, its usual implementation involves storing the output character into the buffer, updating a pointer to the next buffer location, and decrementing a count of the space left in the buffer. When the buffer is full, a physical I/O transfer then takes place to write out all the characters in the buffer. This scheme is known as "fully buffered" I/O.

Each of the default-file I/O functions has an equivalent version that accepts a FILE * argument:

getchar()         is equivalent to   getc(stdin)

putchar()         is equivalent to   putc(stdout)

scanf(fmt, ...)   is equivalent to   fscanf(stdin, fmt, ...)

printf(fmt, ...)  is equivalent to   fprintf(stdout, fmt, ...)

Until we discuss how to open files by name, the most useful of these functions is fprintf for the printing of error messages onto stderr:

if (x < x_low_lim)

fprintf(stderr, "x=%.6e is below x_low_lim=%.6e\n", x, x_low_lim);

It is often advantageous not to buffer the standard error file, since the last error message printed by a program in deep trouble might get lost in the buffer if the program's demise is untidy. Many environments provide an unbuffered stderr by default, but the safe course is to use the setbuf function:

setbuf(stderr, NULL);

will, if called before the first I/O operations upon stderr, put stderr into the "unbuffered" mode. Some environments will also put stdin and/or stdout into unbuffered mode if they are associated with an interactive terminal.

One final advantage of the "buffered" scheme of stream input is that "pushing-back" a character is easily provided for. If c contains a character read from stdin, calling ungetc(c, stdin) will cause the next input operation to read that character again. This is often useful in scanning an input file in which some punctuation character marks the end of each word or item. The punctuator can be "pushed back" onto the stream for another part of the program to handle.

5.3 Reading Lines of Input

For reading lines of input, two functions are provided in the standard library: gets and fgets. The gets function has the simpler calling sequence:

gets(buf)

reads one line from stdin, stores it in buf (with a nul-terminator but without a newline), and returns NULL at end-of-file. Since it provides no means of specifying the size of the receiving string, it can seldom be used in reliable programs. Furthermore, it gives no convenient way to tell whether a newline was present in the input. The fgets function is more reliable, but oftentimes awkward to use. Calling

fgets(buf, n, stdin)

will store at most n bytes into buf, reading from the file specified by the FILE * third argument (stdin, in this example). Of these bytes, the last byte stored is always a nul-terminator. If a newline is encountered in the input, it will precede the nul-terminator. Like gets, fgets returns at end-of-file.

In many applications, the newline in the string is a nuisance, and the more useful returned value would be the length of the stored string. I have many times found more use for a slightly different function, named getsnn ("get string with no newline"). This function deletes the newline, and returns the length of the stored string, or EOF at end-of-file. If the length is less than its maximum (i.e., the specified size minus one), a newline was present in the input; if the length reaches the maximum, no newline was present. The function looks like this:

getsnn;

/* getsnn - get one line, omitting the newline (if any)

* Returns the number of characters stored,

* which equals size-1 only if no newline was found to that point.

* Returns EOF at end of file.

*/

#include "local.h"

int getsnn(s, size)

char s[];

int size;

{

int i;

metachar c;

for (i = 0; i < size-1 && (c = getchar()) != EOF && c != '\n'; ++i)

s[i] = c;

s[i] = '\0';

if (c == EOF)

return (EOF);

return (i);

}

Question [5-3] Modify getsnn into fgetsnn, a function with identical specification except for a third (FILE *) argument:

int fgetsnn(s, size, fp)

char s[];

int size;

FILE *fp;

As an example of the use of getsnn, consider the function getreply, designed for the common task of printing a prompting message and receiving a reply. If the user types more characters than are wanted, we are not interested in them. (In the next chapter we will see more sophisticated means of receiving replies in which the user sees immediately that no more characters are allowed on input. The getreply function will adequately handle the simple cases such as getting a 'y' or 'n' reply.) The function prints the prompt, gets a reply using getsnn, consumes the rest of the line if getsnn did not consume the newline, and returns the length of the reply string, or EOF if end-of-file.

getreply:

/* getreply - print a prompt and get one-line reply

* Returns length of reply string, or EOF if end-of-file

* Consumes an entire input line, even if over-long.

*/

#include "local.h"

int getreply(prompt, reply, size)

char prompt[];

char reply[];

int size;

{

metachar last_char;      /* the last char read */

int get_ret;             /* getsnn return: length of reply or EOF */

printf("%s", prompt);

get_ret = getsnn(reply, size);

if (get_ret == EOF)

return (EOF);

else if (get_ret == size-1) /* no newline was read */

while ((last_char = getchar()) != EOF && last_char != '\n')

;

if (last_char == EOF)

return (EOF);

return (get_ret);

}

5.4 Formatted Input

Topics assumed from first book: Basic scanf formats [3.12].

The combinations of basic conversion characters for scanf can be illustrated by the following table:

data size

short  int  long      conversion

%hd  %d    %ld      decimal integer

%ho  %o    %lo      octal integer

%hx  %x    %lx      hexadecimal integer

%hu  %u    %lu      unsigned integer

float      double

%e or %f   %le or %lf  floating-point number

A "one-word" string (delimited by whitespace) can be read with the %s format:

char next_word[LARGE_ENOUGH];

if (scanf("%s", next_word) != 1)

/* must be EOF if scanf failed */

The value of LARGE_ENOUGH must be adequate for the largest possible input word.

For all the input formats discussed so far, scanf will skip over any input whitespace (including newline!) to find the next input item. If a width specification precedes the conversion character, leading whitespace is still skipped, and then only the specified number of input characters are considered in the conversion. Thus,

char next_char[2];

scanf("%1s", next_char)

will read into next_char the next non-whitespace input character, followed by a nul-terminator. If the percent-sign is followed by an asterisk (the "assignment-suppression flag"), the corresponding input item will be matched but not assigned. This means that

scanf("%*1s%1s", next_char)

will read into next_char the second non-whitespace character from the input.

The %c input format behaves somewhat differently: it produces the next input character, whitespace or not. For example, an input file consisting of 80-character "card-images" could be read by

scanf("%80c", card_image)

Note that no nul-terminator will be placed after the characters.

For discussion of recent additions to scanf, as well as a comparison of different versions, see the appendix [5-4].

Now we will consider some of the reliability problems of the scanf functions. Numeric input is not checked for overflow. This means that numerical input using scanf can be trusted only if the inputs are somehow known to be in-bounds (perhaps previously produced by a reliable program). As alluded to earlier, strings read by the %s format have no guaranteed upper bound on their length. And character input, such as %80c, works reliably only when the input file is known to have the proper number of characters available. A format like this would be a disaster if applied to input lines of varying length; the newlines would just be swallowed up into the 80-character inputs.

Another problem with scanf is that its returned value is a very crude indicator of the success of the scan. The format argument can specify literal text characters that are supposed to be matched in the input, but scanf tells only how many arguments were successfully assigned. In particular, if a format string ended with literal text instead of a format specifier ("percent-sign something"), there would be no way to tell whether the text was successfully matched or not. For example, if a format string ends with a whitespace character, this requests scanf to skip over any trailing whitespace after the last input item -- but there is no way to tell if there was any such trailing whitespace after scanf returns.

Finally, there is scanf's "push-back" behavior: unless the final format stopped because of a specified width bound, there is one input character which scanf pushed back onto the input stream, using ungetc, or its equivalent. This makes for problems when scanf input conversion is combined with other input functions. For example, unless the format ends with whitespace, scanf will not consume the newline at the end of a line that it has read, so calling a line-oriented function must be done with full awareness that the input is still on the same line that scanf partially consumed.

Somewhat finer control is available by reading an entire input line with a line-oriented function such as getsnn, and then splitting it up with sscanf. This works for reasonably error-free inputs where "diagnose-and-terminate" (or "diagnose and re-prompt") is adequate for errors.

The scanf functions provide no control over the size of strings read via %s formats. When the input can be expected not to exceed some generous maximum line length, an intermediate buffer can solve the problem. For example, we can make a getpstr function which looks for a specified "prompt" sequence in the input and passes back the whitespace-delimited "word" which follows:

getpstr:

/* getpstr - get a string from input

* string must be preceded by specified "prompt" text

* return YES if text matched, NO otherwise

*/

#include "local.h"

#define FMTSIZE 84  /* arbitrary limit on size of "prompt" */

bool getpstr(p, s, n)

char p[];                /* : !NULL */

char s[];                /* : string */

size_t n;                /* : {1:sizeof(p[*])} */

{

char buf[BUFSIZ];        /* : string */

char fmt[FMTSlZE];       /* : string */

metachar c;

if (!strfit(fmt, p, FMTSIZE - 4))    /* copy "prompt" into fmt */

return (NO);         /* prompt text is too long */

strcat(fmt, "%s");       /* make fmt into a scanf format */

while (isspace(c = getchar()))

;                    /* skip leading whitespace */

ungetc(c, stdin);        /* put the non-whitespace back */

if (scanf(fmt, buf) != 1)

return (NO);         /* scanf match failed */

if (!strfit(s, buf, n))

fprintf(stderr, "<%s> chopped to <%s>\n", buf, s);

return (YES);

}

Here is a typical usage: the input is expected to contain the characters "Name: " (preceded and followed by any amount of whitespace) and then one "word" (whitespace-delimited string) which is to be read into a string (name_str, say). The corresponding call to getpstr would look like this:

if (!getpstr("Name:", name_str, sizeof(name_str)))

error("Expected   Name: ...", "");

The getpstr function consumes only one word after the expected "prompt" prefix. If we want a similar function which will consume the entire remainder of the line, it is simpler to read the rest of the line with a line-oriented function such as getsnn. Here is the function getplin ("get prompted line") which does so:

getplin:

/* getplin - get a line from input

* line must start with specified "prompt" text

* return YES if text matched, NO otherwise

*/

#include "local.h"

#define FMTSIZE 84

bool getplin(p, s, n)

char p[];

char s[];

size_t n;

{

char buf[BUFSIZ];

char fmt[FMTSIZE];

metachar c;

if (!strfit(fmt, p, FMTSIZE - 4))

return (NO);    /* prompt text is too long */

strcat(fmt, "%c");

while (isspace(c = getchar()))

;

ungetc(c, stdin);

if (scanf(fmt, buf) != 1)

return (NO);

if (getsnn(buf, BUFSIZ) == EOF)

return (NO);

if (!strfit(s, buf, n))

fprintf(stderr, "<%s> chopped to \n<%s>\n", buf, s);

return (YES);

}

5.5 Direct Terminal I/O

Most of the reliability problems discussed above are eliminated by using direct terminal I/O. In this mode, the program reads the user's inputs character-by-character, accepting only good data as it is typed. If the user types too many characters, or the wrong kind of characters (e.g., non-numeric in a numeric field), the program can "beep" and refuse to echo the character. This mode also has the advantage of controlling the physical appearance of the screen directly.

To accomplish this, we need control over the cursor position and control over the echoing of characters. There is, unfortunately, no portable means to enter this mode in standard C; the best that we can do is to define a minimal set of functions and write an implementation for them in each different environment. (The libraries known as curses, termcap, and terminfo in the UNIX world provide all of this and more, but these libraries are not available on most non-UNIX C implementations.)

Our functions will address the screen by line and column numbers. Line zero, column zero is the "home" position, in the upper left corner. Global variables scr_lins and scr_cols give the number of lines and columns on the terminal:

These are the functions that we will need:

scr_open() will do whatever is necessary in order that each key be received by the program as it is typed.

scr_close() will restore the ordinary terminal state.

scr_clear() erases the screen and moves the cursor to the home position.

scr_cel() clears to the end of the current line.

scr_curs(line, col) causes the cursor to move to the specified location.

scr_putc(c) prints the character c at the current screen position and moves the cursor accordingly.

scr_print(fmt, ...) performs a printf at the current screen position.

scr_refresh() forces any buffered screen changes to be written to the physical screen.

To achieve the needed control over the terminal screen, some environments have to turn off the usual mapping of newline ('\n') into carriage-return+linefeed. (The newline character when unmapped produces a linefeed only, dropping down one line without return to the margin.) If we need to accomplish a carriage-return+newline, we will have to transmit "\r\n" explicitly. If newline mapping is still turned on, nothing has been lost in doing so.

The scr_refresh macro forces the flushing of the standard-output buffer by calling the library function fflush.

Regarding the input, there is a function scr_getkey which performs some useful mappings. Many terminals now have cursor-control keys (typically left-arrow, right-arrow, up-arrow, down-arrow, and home) but the actual code sequences which they generate are not standard. The scr_getkey function interprets these special sequences and returns a single scalar value, which is either an ordinary character or a special code for each of the mapped keys.

The screen functions work together to ensure that the terminal screen has an invariant property that we might call the screen-sync property: The program knows what the screen looks like. There are no characters on the screen that were not known to the program.

To be very strict about the screen-sync property, we should take steps to ensure that no other programs or other users can write to the screen during execution of the program. The details will vary, of course, from system to system.

The code examples below are simple implementations of the above operations. One implementation (screenU.c) works on a UNIX system with an ANSI terminal (DEC VT-100, Zenith Z-19 and Z-29, etc.), the other one works on an IBM PC-compatible system with the "ANSI.SYS" driver.

screen.h:

/* screen.h - header for terminal package

* Assumes ANSI terminal

*/

# ifndef SCREEN_H

#define SCREEN_H

typedef short SCR_CMDCHAR;

#define SCR_RETURN '\r'

#define SCR_EXIT   0x101

#define SCR_UP     0x102

#define SCR_DOWN   0x103

#define SCR_LEFT   0x104

#define SCR_RIGHT  0x105

#define SCR_HOME   0x106

#define SCR_CLEAR  "\33[2J"    /* clear entire screen, go HOME */

#define SCR_CEL    "\33[K"     /*clear to end of line */

#define SCR_TTY 1  /* screen is in ordinary (non-direct) mode */

#define SCR_RAW 2  /* screen is in "total-control" mode */

#define SCR_CMD(c) \

(c == SCR_RETURN || c >= 0x101 && c <= 0x106)

extern short scr_mode;     /* {SCR_TTY, SCR_RAW} */

extern short scr_lins;     /* number of lines on screen */

extern short scr_cols;     /* number of columns on screen */

void scr_close();          /* PARMS(void) */

SCR_CMDCHAR scr_getkey();  /* PARMS(void) */

void scr_open();           /* PARMS(void) */

#define scr_beep()     putchar('\7')

#define scr_clear()    printf(SCR_CLEAR)

#define scr_cel()      printf(SCR_CEL)

#define scr_curs(r, c) printf("\33[%d;%dH", (r)+1, (c)+1)

#define scr_print      printf

#define scr_putc(c)    putchar(c)

#define scr_refresh()  fflush(stdout)

#endif

screenU.c:

/* screen - environment-specific terminal functions

* UNIX version for ANSI terminal

*/

#include "local.h"

#include "screen.h"

#define get_7bit_char() (getchar() & 0177)  /* "raw" getchar never EOF */

short scr_mode = SCR_TTY;   /* screen mode - TTY or RAW */

short scr_lins = 24;    /* screen lines (default) */

short scr_cols = 80;    /* screen columns (default) */

/* scr_getkey - get a (coded) keyboard char */

SCR_CMDCHAR scr_getkey()

{

char c1, c2;

scr_refresh();

if ((c1 = get_7bit_char()) != '\33')

return (c1);

else if ((c2 = get_7bit_char()) == 'O')

{

if (get_7bit_char() =='S')      /* F1 function key */

return ( SCR_EXIT);

scr_beep();

return (scr_getkey());

}

else if (c2 == '[')

{

switch (get_7bit_char())

{

case 'A':   return (SCR_UP);    /* no "break" needed - all returns */

case 'B':   return (SCR_DOWN);

case 'C':   return ( SCR_RIGHT ) ;

case 'D':   return (SCR_LEFT);

case 'H':   return (SCR_HOME);

default:    scr_beep();

return (scr_getkey());

}

}

else

{

scr_beep();

return (scr_getkey());

}

}

/* remark - print error message, appropriate for scr_mode */

void remark(sl, s2)

char s1[], s2[];    /* strings to be printed */

{

if (scr_mode == SCR_TTY)

fprintf(stderr, "%s %s\n", s1, s2);

else

{

scr_curs(scr_lins-1, 0);

scr_print("%s %s; hit any key to continue", s1, s2);

scr_getkey();

scr_curs(scr_lins-1, 0);

scr_cel();

}

}

/* scr_open - initialize the terminal */

void scr_open()

{

system("stty raw -echo");   /* slow but universal */

printf("\33[>6h");          /* keypad-shifted; not universal ANSI */

scr_mode = SCR_RAW;

}

/* scr_close - re-establish normal terminal environment */

void scr_close()

{

printf("\33[>6l");          /* exit keypad-shifted mode */

system("stty - raw echo");  /* slow but universal */

scr_mode = SCR_TTY;

}

screen86.c:

/* screen - environment-specific terminal functions

* PC Version - uses bdos function

*/

#inclue "local.h"

#include "screen.h"

short scr_mote = SCR_TTY;   /* screen mode - TTY or RAW */

short scr_lins = 24;    /* screen lines (default) */

short scr_cols = 80;    /* screen columns (default) */

/* scr_getkey - get a (coded) keyboard char */

SCR_CMDCHAR scr_getkey()

{

char c1;

scr_refresh();

if ((c1 = bdos(8)) != '\0')

return (c1 & 0x7F);

switch (c1 = bdos(8))

{

/* no "break" needed - all returns */

case 'H':   return (SCR_UP);

case 'P':   return (SCR_DOWN);

case 'M':   return (SCR_RIGHT);

case 'K':   return (SCR_LEFT);

case 'G':   return (SCR_HOME);

case ';':   return (SCR_EXIT);  /* F1 function key */

default:         scr_beep();

return (scr_getkey());

}

}

/* remark - print error message, appropriate for scr_mode */

void remark(s1, s2)

char s1[], s2[];    /* strings to be printed */

{

if (scr_mode == SCR_TTY)

fprintf(stderr, "%s %s\n", s1, s2);

else

{

scr_curs(scr_lins-1, 0);

scr_print("%s %s; hit any key to continue", s1, s2);

scr_getkey();

scr_curs(scr_l ins-1, 0);

scr_cel();

}

}

/* scr_open - enter "raw" screen mode */

void scr_open()

{

scr_mode = SCR_RAW; /* otherwise no work; bdos(8) is unbuffered */

}

/* scr_close - restore normal tty state */

void scr_close()

{

scr_mode = SCR_TTY;

}

This simple implementation of scr_putc outputs each character with putchar, which may be totally unbuffered in some environments. A significantly more efficient implementation would store up characters internally until the "screen-refresh" function (scr_refresh) was called. Even more sophisticated is the scheme of the curses library. Output to the screen is saved in a screen-image buffer until a refresh function is called. Then, the desired new screen image is compared with the existing image, and only those screen positions which have changed are output, with appropriate cursor motions, character-inserts, character-deletes, etc. Both approaches to screen refreshing are easy to add onto the basic scheme, if you want to.

As a simple demonstration of cursor addressing, the function plot_trk (listed below) simulates a pseudo-oval track around the screen. (There are 100 points on the track, chosen so that the physical distance between points is roughly equal.)

plot_trk:

/* plot_trk - plot position around a pseudo-oval track */

#include "local.h"

#include "screen.h"

#define LIN_ORIGIN 11

#define COL_ORIGIN 40

#define NPOINTS 26

#define IMAX (NPOINTS - 1)

#define POS(n, signlin, signcol) \

scr_curs(LIN_ORIGIN + (signlin) * coords[n].lin, \

COL_ORIGIN + (signcol) * coords[n].col)

static struct coords

{

short lin, col;

} coords[INPOINTS] =

{       /* points for one quadrant (quarter-screen) */

{11,  0},  {11,  2},  {11,  4},  {11,  6},  {11,  8},

{11, 10},  {11, 12},  {11, 14},  {11, 16},  {11, 18},

{11, 20},  {11, 22},  {11, 24},  {11, 26},  {11, 28},

{10, 29},  { 9, 30},  { 8, 31},  { 7, 32},  { 6, 33},

{ 5, 34},  { 4, 35},  { 3, 36},  { 2, 36},  { 1, 36},

{ 0, 36},

};

/* plot_trk - plot a point */

void plot_trk(n, c)

int n;

char c;

{

asserts(n >= 0,"plot_trk: n is non-negative");

n %= 4 * IMAX;

if (n < IMAX)

POS(n, 1, 1);               /* 1st quadrant - lower right */

else if (n < 2 * IMAX)

POS(2 * IMAX - n, -1, 1);   /* 2nd quadrant - upper right */

else if (n < 3 * IMAX)

POS(n - 2 * IMAX, -1, -1);  /* 3rd quadrant - upper left */

else

POS(4 * IMAX - n, 1, -1);   /* 4th quadrant - lower left */

scr_putc(c);

}

A simple "test-drive" function runs four times around the track, then stops.

plot_m.c:

/* plot_m - demonstrate plot_trk function */

#include "local.h"

#include "screen.h"

main()

{

short i;

scr_open();

scr_clear();

for (i = 0; i < 400; ++i)  /* four times around the track */

plot_trk(i, 'X');

scr_curs(scr_lins-1, 0);

scr_close();

}

Those of you programming race-track simulations will immediately see the applicability. In the next chapter, we will have more substantive uses for all these new functions.

Go to Chapter 6 Return to Table of Contents