Code Capsules

The Standard C Library, Part 1


Although it may not seem like it, C is a very small language. In fact, it was first implemented on a very small platform by today's standards. The first C compiler I owned ran on a Commodore 64! C's simplicity and compactness make it ideal for systems programming and for developing programs that run in embedded systems, such as in automobiles or cameras.

A key difference between C in such freestanding environments and C in a hosted environment, such as a desktop or mid-range computer, is the presence of the Standard C library. In a freestanding environment a conforming compiler only needs to provide the types and macros specified in <float.h>, <limits.h>, <stdarg.h>, and <stddef.h>. By contrast, in hosted environments, programmers who work on typical data processing projects take the library for granted — in fact, they think of it as part of the language. A large portion of everyday C code consists of library calls. Even I/O facilities like printf and scanf are part of the library, not the language.

The Standard C library consists of functions, type definitions, and macros declared in fifteen header files. Each header more or less represents a domain of programming functionality, such as I/O or string processing operations. Some macros and type definitions, such as NULL and size_t, appear in more than one header file for convenience.

In this article I divide the Standard library into three groups (see Table 1 through Table 3) . Group I represents library components that you should understand thoroughly if you want to consider yourself a C programmer. Too often I have seen programs that "reinvent" basic library facilities such as memcpy or strchr. Such programs should not be executed, but sometimes I think their programmers should be (or at least fired). To receive your paycheck in good conscience, though, you should really master Group II as well. And although you may need the functions in Group III only once in a blue moon, you should be familiar enough with them that you know how to use them when that need arises.

I certainly don't plan to review the entire library in this article. If you want a comprehensive reference, there is nothing better than Plauger's book, The Standard C Library [1]. For tips, tricks, and tutorials, you can read most of the back issues of this column (which started in October, 1992). What I will do here is bring to light some of the library functions you may have overlooked, and some behavior you may not be aware of.

Group I — For the "Adequate" C Programmer

<ctype. h>

The functions in <ctype.h> support typical operations for handling single characters (see Table 4) . For example, to determine if a character C is upper case, use the expression isupper(c). Many old-time C programs are peppered with expressions such as

   ('A' <= c && c <= 'Z')
instead, which makes poor reading. Putting such an expression in a macro helps, as in

   #define ISUPPER(c) ('A' <= c & c <= 'Z')
But this makes expressions with side effects (such as ISUPPER(c++)) unreliable. And of course this test for uppercase membership works only with a character set that encodes the alphabet contiguously, such as ASCII. By contrast, the character classification functions in <ctype.h> are safe and portable across all platforms.

It is important not to assume that ASCII is always the execution character set. For example, ASCII control characters comprise the code 127 and those less than 32, but only seven control characters behave uniformly across all environments: alert ( '\a'), backspace ( '\b'), carriage return ( '\r'), form feed ( '\f''), horizontal tab ( '\t'), newline ( '\n'), and vertical tab ( '\v'). The only character-handling functions that do not change behavior when you change locale are isdigit and isxdigit.(See Group III next month for more on locales).

Although you can assume that the digits '0' through '9' have contiguous codes in all C execution character sets, the hexadecimal digits, being alphabetic characters, do not. The function atox in Listing 1 shows how to convert a hexadecimal string to an integer value. Unfortunately, it only works for ASCII-like character sets. The offending line is:

   digit= toupper(*s) - 'A' + 10;
There is no guarantee that the expression

   toupper(*s) - 'A'
will give the correct result. The version in Listing 2 works on any platform because it stores all hexadecimal digits contiguously in its own array. It searches the array with strchr and then uses pointer arithmetic to compute the value of the digit.

<stdio.h>

The author of Listing 1 and Listing 2 could have avoided a lot of trouble if he had only understood scanf formatting a little better. As Listing 3 illustrates, the "%x" edit descriptor does all the work of reading hexadecimal numbers for you. Unlike the previous two versions, it even handles a leading plus or minus sign. Both scanf and printf are laden with features that so many programmers overlook. For more detail on these two functions, see the Code Capsules in the October 1992 and November 1992 issues of CUJ.

The printf/scanf families of functions shown in Table 5 perform formatted I/0. Furthermore, they provide these facilities for three types of streams: standard streams, file streams, and string (i.e., in-core) streams. Formatting operates identically on the different types of streams, but of necessity the function names and calling sequences are somewhat different.

The <stdio.h> component of the Standard C library provides two other classes of input/output facilities: character I/0 and block I/0 (see Table 6 and Table 7) . The functions in Listing 4 and Listing 5 copy one file to another using character I/0 and block I/0 functions respectively. Note that since fread does not return an error code, I must make an explicit call to ferror to detect a read error.

As Table 7 illustrates, <stdio.h> provides functions for file positioning. The time-worn functions fseek and ftell work reliably only on files opened in binary mode, and are limited to file positions that can be represented by a long integer. To overcome these limitations, the ANSI committee invented fgetpos and fsetpos, which use the abstract type fpos_t (Table 8) as a file position indicator.

The program in Listing 6 puts fgetpos and fsetpos to good use in a simple four-way scrolling browser for large files. The browser keeps only one screen's worth of text in memory. If you want to scroll up or down through the file, it reads (or re-reads) the adjacent text and displays it. When scrolling down (i.e., forward) through the file, the program pushes the file position corresponding to the old screen data on a stack, and reads the next screenful from the current file position. To scroll up, the program retrieves the file position of the previous screen from the stack. For the complete program, and for more detailed information about file I/0, see the Code Capsule in the May 1993 edition of CUJ.

<stdlib.h>

The header <stdlib.h> is a bit of a catch-all. It defines types, macros, and functions for memory management, sorting and searching, integer arithmetic, string-to-number conversions, sequences of pseudorandom numbers, interfacing with the environment, and converting multi-byte strings and characters to and from wide character representations (see Table 9) . The program in Listing 7 uses all four memory management functions to sort a text file. Whenever its array of pointers to char fills up, it expands it with realloc, which preserves the original contents. See "Code Capsules: Dynamic Memory Management, Part 1," CUJ, October 1994, for an in-depth treatment of memory management in C. For more information on the sort function qsort, see the Code Capsule "Sorting with qsort," CUJ, April 1993. The search function, bsearch, searches a sorted list for a given key. Like qsort, you supply bsearch with a compare function and it returns a pointer to the array element containing the key (see Listing 8) .

The program in Listing 9 illustrates some of <stdlib.h>'s seldom-used functions. It shuffles a deck of 52 cards by creating a randomized sequence of the numbers 0 through 51. The srand function seeds the pseudo-random generator by encoding the current time and date. To derive the suit and denomination from a number, I divide the number by 13, the number of cards in each suit. The remainder of this division is the denomination (0 through 12 corresponding to ace through king), and the quotient represents the suit as follows:

0 == clubs

1 == diamonds

2 == hearts

3 == spades

The div function computes the quotient and remainder all at once and stores the result in a structure of type div_t..

The functions in the scanf family call strtol to convert character strings to integers. Using strtol directly, however, you can read numbers in any base from 2 to 35, as the program in Listing 10 illustrates. strtol updates nextp through its second argument so you can progress through the string, converting one number after another. The functions strtoul and strtod behave similarly for unsigned longs and doubles respectively. With strtol, I can write a superior version of the atox conversion function, as shown in Listing 11.

Everyone reading this column is probably familiar with the functions exit and abort. You may not know, however, that you can "register" functions to be called automatically at program exit. These functions are usually called "exit handlers" and you register them with the atexit function, like this:

   void my_handler();
   atexit(my_handler);
An exit handler must take no arguments and must return void. Upon normal exit (i.e., return from main or a call to exit), C calls all of your handlers in the reverse of the order that they were registered. You can register up to 32 exit handlers.

The getenv function allows you to query strings in your host environment. For example, to find the current setting of the PATH variable, which is common to many environments, you can do the following:

   char *path = getenv("PATH");
The pointer refers to memory outside of your program, so if you want to keep the value, you'll have to copy it to a program variable before the next call to getenv.

<string. h>

The functions defined in <string.h> are shown in Table 10. All the functions with the str prefix work on null-terminated strings, and the mem-functions process raw memory. You've already seen strchr in Listing 2, and its companion memchr in Listing 9. To transfer raw bytes from one location to another, use memcpy, or memmove, if the source and destination buffers overlap. (Judging by the number of times I've seen memcpy reinvented in others' code, I believe that it is the most overlooked function in the standard library).

The string search functions also go too often unused. The program in Listing 12 uses strstr to extract all lines from a text file that contain a given string. Due to their cryptic names, the following three <string.h> functions are probably the least used:

1) size_t strspn(char *s1, char *s 2); "Spans" the characters from s2 occurring in s1. In plain English, strspn returns the index of the first character in s1 which is not in s2.

2) size_t strcspn(char *s1, char *s2); "Spans" the characters not in s2 occurring in s1. In other words, strcspn returns the index of the first character in s1 that is also in s2.

3) char *strpbrk(char *s1, char *s2); Returns a pointer to the first character from s2 that occurs in s1. It's kind of like a cross between strchr and strcspn.

The program in Listing 13 illustrates these functions. For more on string handling, see the "Code Capsule" in the December 1992 issue (vol. 10, no. 12).

Summary

Although you may not agree totally with my categorization of Standard C library components, I hope I have caused you to think about your own practices and level of expertise. Whatever our opinions, one bit of advice is indisputably in order: know and use the Standard library! Next month I'll cover Group II.

Further Reading

[1] Plauger, P. J. The Standard C Library. Prentice-Hall, 1992. ISBN 0-13-131509-9.

Table 1 Standard C Headers: Group I (required knowledge for every C programmer)

<ctype.h>    Character Handling

<stdio.h>    Input/Output

<stdlib.h>   Miscellaneous Utilities

<string.h>   Text Processing

Table 2 Standard C Headers: Group II (tools for the professional)

<assert.h>   Assertion Support for Defensive Programming

<limits.h>   System Parameters for Integer Arithmetic

<stddef.h>   Universal Types & Constants

<time.h>     Time Processing

Table 3 Standard C Headers: Group III (power at your fingertips when you need it)

<errno.h>    Error Detection

<float.h>    System Parameters for Real Arithmetic

<locale.h>   Cultural Adaptation

<math.h>     Mathematical Functions

<setjmp.h>   Non-local Branching

<signal.h>   Interrupt Handling (sort of)

<stdarg.h>   Variable-length Argument Lists

Table 4 <ctype.h> functions

         Character Testing Functions
-----------------------------------------------------
isalnum      alphanumeric ( isalpha || isdigit)

isalpha      alphabetic

iscntrl      control (beware!)

isdigit      '0' through '9'

isgraph      visible when printed

islower      lower case alphabetic

isprint      isgraph || ' '

ispunct      isgraph && !isalnum

isspace      whitespace

isupper      upper case alphabetic

isxdigit     isdigit || 'a' thru 'f' || 'A' thru 'F'

        Character Mapping Functions
-----------------------------------------------------
tolower      convert to lower case (if applicable)

toupper      convert to upper case (if applicable)

Table 5 Functions for Formatted I/0 defined in <stdio.h>

    Fixed-length argument lists
--------------------------------
scanf        (standard input)

fscanf       (file input)

sscanf       (in-core input)

printf       (standard output)

fprintf      (file output)

sprintf      (in-core output)

    Variable-length argument lists
----------------------------------
vprintf      (standard output)
vfprintf     (file output)
vsprintf     (in-core output)

Table 6 Character I/O Functions in <stdio.h>

Single-character Processing Functions
-------------------------------------
getchar      (standard input)

getc         (file input)

ungetc       (affects file input)

fgetc        (file input)

putchar      (standard output)

putc         (file output)

fputc        (file output)

    String Processing Functions
-------------------------------------
gets         (standard input)

fgets        (file input)

puts         (standard output)

fputs        (file output)

A name="002A_001F">Table 7 Other <stdio.h> Functions

       Block I/0
-----------------------
        fread
        fwrite

Operations via Filename
-----------------------
        remove
        rename

    Temporary Files
-----------------------
        tmpfile
        tmpnam

 File Access Functions
-----------------------
         fopen
        freopen
        fclose
        fflush
        setbuf
        setvbuf

   File Positioning
-----------------------
        fgetpos
        fsetpos
         fseek
         ftell
        rewind

    Error Handling
-----------------------
        clearerr
          feof
         ferror

Table 8 Types and macros defined in <stdio.h>

                          Types
------------------------------------------------------------
FILE          Encapsulates file access info
fpos_t        File Position (returned by fgetpos)

                          Macros
------------------------------------------------------------
NULL          Zero pointer
EOF           Special value representing end-of-file
BUFSIZ        Preferred stream buffer size
FOPEN_MAX     Max # of files open simultaneously
FILENAME_MAX  Max # of characters in a file name minus 1
L_tmpnam      Max # of characters in a tempfile name minus 1
TMP_MAX       Max # of distinct filenames returned from
              tmpnam
SEEK_CUR      Signals fseek to seek relative to current position
SEEK_END      Signals fseek to seek from end-of-file
SEEK_SET      Signals fseek to seek from start-of-file

Table 9 <stdlib.h> Declarations

                      Types
--------------------------------------------------------
div_t         (structure returned by div)
ldiv_t        (structure returned by ldiv)

                    Constants
--------------------------------------------------------
NULL
EXIT_FAILURE  (Portable error code for exit)
EXIT_SUCCESS  (Portable success code for exit)
RAND_MAX      (Max value returned by rand)
MB_CUR_MAX    (Max # of bytes in a multi-byte character)

         String Conversion Functions
--------------------------------------------------------
atof          strtod
atoi          strtol
atol          strtoul

           Random number Functions
--------------------------------------------------------
rand          (Returns the next pseudo-random number)
srand         ("Seeds" the sequence of pseudo-random
              numbers)

             Memory Management
--------------------------------------------------------
calloc        realloc
malloc        free

         Interface to the Environment
--------------------------------------------------------
abort         getenv
atexit        system
exit

             Searching and Sorting
--------------------------------------------------------
bsearch
qsort

              Integer Arithmetic
--------------------------------------------------------
abs           labs
div           ldiv

         Multibyte Character Functions
--------------------------------------------------------
mblen         mbctowcs
mbtowc        wcstombs
wctomb

Table 10 Functions defined in <string.h>

           Copying
------------------------------------
memcpy             strcpy
memmove            strncpy

         Concatenation
------------------------------------
strcat
strncat

          Comparison
------------------------------------
memcmp             strncmp
strcmp             strxfrm
strcoll

        Search Functions
------------------------------------
memchr             strrchr
strchr             strspn
strcspn            strstr
strpbrk            strtok

          Miscellaneous
------------------------------------
memset
strerror
strlen

Listing 1 Converts a hex-string to a number in ASCII environments

#include <ctype.h>
#include <assert.h>

long atox(char *s)
{
    long sum;

    assert(s);

    /* Skip whitespace */
    while (isspace(*s))
        ++s;

    /* Do the conversion */

    for (sum = 0L; isxdigit(*s); ++s)
    {
        int digit;

        if (isdigit(*s))
            digit = *s - '0';
        else
            digit = toupper(*s) - 'A' + 10;
        sum = sum*16L + digit;
    }

    return sum;
}
/* End of File */

Listing 2 A portable version of Listing 1

#include <ctype.h>
#include <assert.h>
#include <string.h>

long atox(char *s)
{
    char xdigs[] = "012345679ABCDEF":
    long sum;

    assert(s);

    /* Skip whitespace */
    while (isspace(*s))
        ++s;

    /* Do the conversion */
    for (sum = 0L: isxdigit(*s); ++s)
    {
        int digit = strchr(xdigs,toupper(*s)) - xdigs;
        sum = sum*16L + digit;
    }

    return sum;
}
/* End of File */

Listing 3 Converts a hex-string to a number via sscanf

#include <stdio.h>

long atox(char *s)
{
    long n = 0L;
    sscanf( s, "%x", &n );
    return n;
}

/* End of File */

Listing 4 A Function that copies a file via character I/0

/* copy1.c */
#include <stdio.h>

int copy(FILE *dest, FILE *source)
{
   int c;

   while ((c = getc(source)) != EOF)
      if (putc(c,dest) == EOF)
         return EOF;
   return 0;
}

/* End of File */

Listing 5 A Function that copies a file via block I/0

/* copy2.c */
#include <stdio.h>

int copy(FILE *dest, FILE *source)
{
   size_t count;
   static char buf[BUFSIZ];

   while (!feof(source))
   {
      count = fread(buf,l,BUFSIZ,source);
      if (ferror(source))
         return EOF;
      If (fwrite(buf,l,count,dest) != count)
         return EOF;

   }
   return 0;
}

/* End of File */

Listing 6 Outline of a file viewing program that illustrates file positioning

/* view.c: A simple 4-way-scrolling file browser */

/* cls(), display(), read_a_screen() omitted...*/

main(int argc, char *argv[])
{
   fpos_t top_pos, stk_[MAXSTACK];
   /* Details omitted...*/

top:
   /* Display initial screen */
   rewind(f);
   fgetpos(f,&top_pos);
   /* Details omitted...*/

   for (;;)
   {
       switch(c = toupper(getchar()))
       {
          case 'D': /* Display the next screen */
             if (!feof(f))
             {
                 PUSH(top_pos);
                 fgetpos(f,&top_pos);
                 read_a_screeen(f);
                 display(file);
             }
             break;

          case 'U': /* Display the previous screen */
             if (stkptr_> 0)
             {
                 top_pos = POP();
                 fsetpos(f,&top_pos);
                 read_a_screen(f);
                 display(file);
             }
             break;
          case 'T': /* Display last screen */
             stkptr_ = 0;
             goto top;

          case 'B': /* Display last screen */
             while (!feof(f)
             {
                 PUSH(top_pos);
                 fgetpos(f,&top_pos);
                 read_a_screen(f)
             }
             display(file);
             break;

          case 'Q': /* Quit */
             cls();
             return EXIT_SUCCESS;
       }
      /* Details omitted...*/
   }
}
/* End of file */

Listing 7 Sorts files as large as available memory

/* sort.c */
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define MAXLINES 512

int comp(const void *, const void *);

main()
{
   int i;
   size_t nlines, maxlines = MAXLINES;
   static char s[BUFSIZ];
   char **lines = calloc(sizeof(char *), maxlines);

   /* Read file */
   for (nlines = 0; fgets(s,BUFSIZ,stdin); ++nlines)
   {
      if (nlines == maxlines)
      {
         /* Expand array of strings */
         maxlines += MAXLINES;
         lines = realloc(lines,maxlines*sizeof(char *));
         assert(lines);
      }

      /* Store this line */
      lines[nlines] = malloc(strlen(s)+1);
      assert(lines[nlines]);
      strcpy(lines[nlines],s);
   }

   /* Sort */
   qsort(lines,nlines,sizeof lines[0],comp);

   /* Print / free memory */
   for (i = 0; i < nlines; ++i)
   {
      fputs(lines[i],stdout);
      fflush(stdout);
      assert(!ferror(stdout));
      free(lines[i]);
   }
   free(lines);
   return 0;
}

/* Compare function for qsort(): */
int comp(const void *pl, const void *p2)
{
   return strcmp(* (char **) pl, * (char **) p2);
}

/* End of File */

Listing 8 Searches a sorted array of records with the bsearch function

/* search.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct person
{
   char last[16];
   char first[11];
   char phone[13];
   int age;
};

static int comp(const void *, const void *);

main()
{
   int i;
   struct person *p;
   static struct person key = {"","","555-1965",0};
   static struct person people[] =
     {{"Ford","Henry","555-1903",98},
      {"Lincoln","Abraham","555-1865",161},
      {"Ford","Edsel","555-1965",53},
      {"Trump","Donald","555-1988",49}};

   /* Sort */
   qsort(people, 4, sizeof people[0], comp);

   /* Search */
    p = bsearch(&key, people, 4, sizeof people[0], comp);
    if (p != NULL)
    {
       printf(
             "%s, %s, %s, %d\n",
             p->last,
             p->first,
             p->phone,
             p->age
             );
    }
    else
       puts("Not found");
    return 0;
}

/* Compare function: */
static int comp(const void *x, const void *y)
{
   struct person *pl = (struct person *) x;
   struct person *p2 = (struct person *) y;

   return strcmp(p1->phone,p2->phone);
}

/* Output: */
Ford, Edsel, 555-1965, 53

/* End of File */

Listing 9 A card shuffling program that illustrates the random number and integer division functions in <stdlib.h>

/* deal.c: Deal a hand from a shuffled deck of cards */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define DECKSIZE 52
#define SUITSIZE 13

main(int argc, char *argv[])
{
   int ncards = DECKSIZE;   /* Deal full deck by default */
   char deck[DECKSIZE];     /* An array of small integers */
   size_t deckp;
   unsigned int seed;

   /* Get optional hand size */
   if (argc > 1)
      if ((ncards = abs(atoi(argv[1])) % DECKSIZE) == 0)
         ncards = DECKSIZE;

   /* Seed the random number generator */
   seed = (unsigned int) time(NULL);
   srand(seed);

   /* Shuffle */
   deckp = 0;
   while (deckp < ncards)
   {
      int num = rand() % DECKSIZE;
      if (memchr(deck, num, deckp) == NULL)
         deck[deckp++] = (char) num;
   }

   /* Deal */
   for (deckp = 0; deckp < ncards; ++deckp)
   {
       divt_card = div(deck[deckp], SUITSIZE);
       printf(
             "%c(%c)%c",
             "A23456789TJQK"[card.rem],
             "CDHS" [card.quot],
             (deckp+1) % SUITSIZE ? ' ' : '\n'
            );
   }

   return 0;
}

/* Output: */
A(C) 6(S) 7(C) 9(C) 3(H) 6(C) 8(D) 3(C) 6(D) 5(D) 2(H) A(S) 4(H)
8(C) 8(H) 6(H) J(S) 7(S) Q(C) 2(C) Q(H) K(H) 4(C) 5(S) T(H) Q(S)
9(H) T(D) T(S) 9(D) K(C) 3(S) J(C) 5(C) T(C) K(S) 7(D) 2(D) 4(S)
8(S) 5(H) A(D) 7(H) 3(D) Q(D) A(H) 2(S) J(D) 9(S) K(D) J(H) 4(D)

/* End of File */

Listing 10 Uses strtol() to read numbers in different bases

#include <stdio.h>
#include <stdlib.h>

main()
{
    char *input = "101 123 45678 90abc g";
    char *nextp = input;
    long bin, oct, dec, hex, beyond:

    bin = strtol(nextp,&nextp,2);
    oct = strtol(nextp,&nextp,8);
    dec = strtol(nextp,&nextp,10);
    hex = strtol(nextp,&nextp,16);
    beyond = strtol(nextp,&nextp,17);

    printf("bin = %ld\n",bin);
    printf("oct =
                    %lo\n",oct);
    printf("dec = %ld\n",dec);
    printf("hex = %lx\n",hex);
    printf("beyond = %ld\n",beyond);
    return 0;
}

/* Output: */
bin = 5
oct = 123
dec = 45678
hex = 90abc
beyond = 16

/* End of File */

Listing 11 An even better version of atox() using strtol()

#include <stdlib.h>

long atox(char *s)
{
return strtol(s, NULL, 16);
}

/* End of File */

Listing 12 Uses strstr to find substrings

/* find.c:  Extract lines from a file */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

main(int argc, char *argv[])
{
    char line[BUFSIZ];
    char *search_str;
    int lineno = 0;

    if (argc == 1)
       return EXIT_FAILURE;
       /* Search string required */
    else
       search_str = argv[l];

    while (gets(line))
    {
       ++lineno;
       if (strstr(line,search_str))
          printf("%d: %s\n",lineno,line);
    }

    return EXIT_SUCCESS;
}

/*Results from the command
  "find str <find.c": */
5: #include <string.h>
12:      char *search_str;
16:        return EXIT_FAILURE;
18:        search_str = argv[l];
23:        if (strstr(line,search_str))

/* End of File */

Listing 13 Illustrates selected string search functions

#include <stdio.h>
#include <string.h>

void display_span(char *, int);
main()
{
    char *s = "Eeek! A mouse device!;
    char *vowels = "AEIOUaeiou";
    char *punct =
       "'~!@#$%^&*()-_=+\\|[{]};:'\",<.>/?";
    char *ptr;

    display_span(s,strspn(s,vowels));
    display_span(s,strspn(s,punct));
    display_span(s,strcspn(s,vowels));
    display_span(s,strcspn(s,punct));

    ptr = strpbrk(s,vowels);
    puts(ptr);
    ptr = strpbrk(s,punct);
    puts(ptr);

    return 0;
}

void display_span(char *s, size_t index)
{
   printf("%d characters spanned: %.*s\n",
          index,index,s);
}

/* Output: */
3 characters spanned: Eee
0 characters spanned:
0 characters spanned:
4 characters spanned: Eeek
Eeek! A mouse device!
! A mouse device!

/* End of File */