< Prev | Next > | Index | HOME

[4.0] C Library Functions & Other Comments

v3.0.1 / chapter 4 of 5 / 01 aug 05 / greg goebel / public domain

* This chapter discusses some useful standard C libraries:

   Math library
   Standard utility library
   The "sprintf()" function
   String function library
   Character class test library
-- and the following minor topics:
   Command-line arguments
   Dynamic memory allocation
   Pointers to functions
   PC memory model and other declarations
   Troubleshooting hints

[4.1] C MATH LIBRARY
[4.2] C STANDARD UTILITY LIBRARY & TIME LIBRARY
[4.3] THE C "SPRINTF()" FUNCTION
[4.4] C STRING FUNCTION LIBRARY
[4.5] C CHARACTER CLASS TEST LIBRARY
[4.6] C COMMAND LINE ARGUMENTS
[4.7] POINTERS TO C FUNCTIONS
[4.8] C DYNAMIC MEMORY ALLOCATION & DEALLOCATION
[4.9] COMMON PROGRAMMING PROBLEMS IN C

[4.1] C MATH LIBRARY

* To use the math library, you need the declaration:

   #include <math.h>
The math functions consist of:
   sin( x ):      Sine of x.
   cos( x ):      Cosine of x.
   tan( x ):      Tangent of x.
   asin( x ):     Inverse sine of x.
   acos( x ):     Inverse cosine of x.
   atan( x ):     Inverse tangent of x.
   sinh( x ):     Hyperbolic sine of x.
   cosh( x ):     Hyperbolic cosine of x.
   tanh( x ):     Hyperbolic tangent of x.
   exp( x ):      Exponential function -- e^x.
   log( x ):      Natural log of x.
   log10( x ):    Base 10 log of x.
   pow( x, y ):   Power function -- x^y.
   sqrt( x ):     Square root of x.
   ceil( x ):     Smallest integer not less than x, returned as double.
   floor( x ):    Greatest integer not greater than x, returned as double.
   fabs( x ):     Absolute value of x.
All values are "doubles", and trig values are expressed in radians.

BACK_TO_TOP

[4.2] C STANDARD UTILITY LIBRARY & TIME LIBRARY

* The utility functions library features a grab-bag of functions. It requires the declaration:

   #include <stdlib.h>
Useful functions include:
   atof( <string> ):    Convert numeric string to double value.
   atoi( <string> ):    Convert numeric string to int value.
   atol( <string> ):    Convert numeric string to long value.
   rand():              Generates pseudorandom integer.
   srand( <seed> ):     Seed random-number generator -- "seed" is an "int".
   exit( <status> ):    Exits program -- "status" is an "int".
   system( <string> ):  Tells system to execute program given by the seed.
   abs( n ):            Absolute value of "int" argument.
   labs( n ):           Absolute value of long-int argument.
The functions "atof()", "atoi()", and "atol()" will return 0 if they can't convert the string given them into a value.

The time and date library includes a wide variety of functions, some of them obscure and nonstandard. To use this library you need the declaration:

   #include <time.h>
The most essential function is "time()", which returns the number of seconds since some long-ago date. It returns a value as "time_t" (a "long") as defined in the header file.

The following function uses "time()" to implement a program delay with resolution in seconds:

   /* delay.c */

   #include <stdio.h>
   #include <time.h>

   void sleep( time_t delay );

   void main()
   {
     puts( "Delaying for 3 seconds." );
     sleep( 3 );
     puts( "Done!" );
   }

   void sleep( time_t delay )
   {
     time_t t0, t1;
     time( &t0 );
     do
     {
       time( &t1 );
     }
     while (( t1 - t0 ) < delay );
   }
The "ctime()" function converts the time value returned by "time()" into a time-and-date string. The following little program prints the current time and date:
   /* time.c */

   #include <stdio.h>
   #include <time.h>

   void main()
   {
     time_t *t;
     time( t );
     puts( ctime( t ));
   }
This program prints a string of the form:
   Tue Dec 27 15:18:16 1994
BACK_TO_TOP

[4.3] THE C "SPRINTF()" FUNCTION

* The "sprintf" function allows you to create strings with formatted data. Technically speaking, this is part of the standard-I/O library, and requires the declaration:

   #include <stdio.h>
However, it is really a string function and needs to be discussed along with the other string functions. The syntax of "sprintf()" is exactly the same as it is for "printf()", with the notable exception that the first parameter is a pointer to a string. For example:
   /* csprntf.c */

   #include <stdio.h>

   void main()
   {
      char b[100];
      int i = 42;
      float f = 1.1234f;
      sprintf( b, "Formatted data:  %d / %f", i, f );
      puts( b );
   }
-- prints the string:
   Formatted data:  42 / 1.1234
There is also an "sscanf()" function that similarly mirrors "scanf()" functionality.

BACK_TO_TOP

[4.4] C STRING FUNCTION LIBRARY

* The string-function library requires the declaration:

   #include <string.h>
The most important string functions are as follows:
   strlen():   Get length of a string.
   strcpy():   Copy one string to another.
   strcat():   Link together (concatenate) two strings.
   strcmp():   Compare two strings.
   strchr():   Find character in string.
   strstr():   Find string in string.
   strlwr():   Convert string to lowercase.
   strupr():   Convert string to uppercase.
* The "strlen()" function gives the length of a string, not including the NULL character at the end:
   /* strlen.c */

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

   void main()
   {
     char *t = "XXX";
     printf( "Length of <%s> is %d.\n", t, strlen( t ));
   }
This prints:
   Length of <XXX> is 3.
* The "strcpy" function copies one string from another. For example:
   /* strcpy.c */

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

   void main()
   {
     char s1[100],
          s2[100];
     strcpy( s1, "string 2" );
     strcpy( s2, "string 1" );

     puts( "Original strings: " );
     puts( "" );
     puts( s1 );
     puts( s2 );
     puts( "" );

     strcpy( s2, s1 );

     puts( "New strings: " );
     puts( "" );
     puts( s1 );
     puts( s2 );
   }
This will print:
   Original strings:

   string 1
   string 2

   New strings:

   string 1
   string 1
Please be aware of two features of this program:

These comments are applicable to most of the other string functions.

There is a variant form of "strcpy" named "strncpy" that will copy "n" characters of the source string to the destination string, presuming there are that many characters available in the source string. For example, if you make the following change in the example program:

   strncpy( s2, s1, 5 );
-- then the results change to:
   New strings:

   string 1
   string
Notice that the parameter "n" is declared "size_t", which is defined in "string.h".

* The "strcat()" function joins two strings:

   /* strcat.c */

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

   void main()
   {
     char s1[50],
          s2[50];
     strcpy( s1, "Tweedledee " );
     strcpy( s2, "Tweedledum" );
     strcat( s1, s2 );
     puts( s1 );
   }
This prints:
   Tweedledee Tweedledum
There is a variant version of "strcat()" named "strncat()" that will append "n" characters of the source string to the destination string. If you used "strncat()" in the example above with a length of 7:
   strncat( s1, s2, 7 );
-- the result would be:
   Tweedledee Tweedle
Again, the length parameter is of type "size_t".

* The "strcmp()" function compares two strings:

   /* strcmp.c */

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

   #define ANSWER "blue"

   void main()
   {
     char t[100];
     puts( "What is the secret color?" );
     gets( t );
     while ( strcmp( t, ANSWER ) != 0 )
     {
       puts( "Wrong, try again." );
       gets( t );
     }
     puts( "Right!" );
   }
The "strcmp()" function returns a 0 for a successful comparison, and nonzero otherwise. The comparison is case-sensitive, so answering "BLUE" or "Blue" won't work.

There are three alternate forms for "strcmp()":

* The "strchr" function finds the first occurrence of a character in a string. It returns a pointer to the character if it finds it, and null if not. For example:

   /* strchr.c */

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

   void main()
   {
     char *t = "MEAS:VOLT:DC?";
     char *p;
     p = t;
     puts( p );
     while(( p = strchr( p, ':' )) != NULL )
     {
       puts( ++p );
     }
   }
This prints:
   MEAS:VOLT:DC?
   VOLT:DC?
   DC?
The character is defined as a character constant, which C regards as an "int". Notice how the example program increments the pointer before using it ("++p") so that it doesn't point to the ":" but to the character following it.

The "strrchr()" function is almost the same as "strchr()", except that it searches for the last occurrence of the character in the string.

* The "strstr()" function is similar to "strchr()" except that it searches for a string, rather than a character. It also returns a pointer:

  char *s = "Black White Brown Blue Green";
  ...
  puts( strstr( s, "Blue" ) );
* The "strlwr()" and "strupr()" functions simply perform lowercase or uppercase conversion on the source string. For example:
   /* casecvt.c */

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

   void main()
   {
     char *t = "Die Barney die!";
     puts( strlwr( t ) );
     puts( strupr( t ) );
   }
-- prints:
   die barney die!
   DIE BARNEY DIE!
These two functions only implemented in some compilers and are not part of ANSI C.

BACK_TO_TOP

[4.5] C CHARACTER CLASS TEST LIBRARY

* These functions perform various tests on characters. They require the declaration:

   #include <ctype.h>
The character is represented as an "int" and the functions return an "int". They return 0 if the test is false and non-0 if the test is true:
   isalnum( c ):    Character is alpha or digit.
   isalpha( c ):    Character is alpha.
   iscntrl( c ):    Character is control character.
   isdigit( c ):    Character is decimal digit.
   isgraph( c ):    Character is printing character (except space).
   islower( c ):    Character is lower-case.
   isprint( c ):    Character is printing character (including space).
   ispunct( c ):    Character is printing character but not space/alpha-digit.
   isspace( c ):    Character is space, FF, LF, CR, HT, VT.
   isupper( c ):    Character is upper-case.
   isxdigit( c ):   Character is hex digit.
The library also contains two conversion functions that also accept and return "int":
   tolower( c ):   Convert to lower case.
   toupper( c ):   Convert to upper case.
BACK_TO_TOP

[4.6] C COMMAND LINE ARGUMENTS

* C allows you to obtain the command line arguments provided when your executable is called, using two optional parameters of "main()" named "argc (argument count)" and "argv (argument vector)".

The "argc" variable gives the count of the number of command-line parameters provided to the program. This count includes the name of the program itself, so it will always have a value of at least one. The "argv" variable is a pointer to an array of strings, with each element containing one of the command-line arguments.

The following example program demonstrates:

   /* cmdline.c */

   #include <stdio.h>

   void main( int argc, char *argv[] )
   {
     int ctr;
     for( ctr=0; ctr < argc; ctr++ )
     {
       puts( argv[ctr] );
     }
   }
If you run this program from the command line as follows:
   stooges moe larry curley 
-- you'll get the output:
   stooges
   moe
   larry
   curley
In practice, the command line will probably take a number of arguments, some of which will indicate options or switches, designated by a leading "-" or "/". Some of the switches may be specified separately or together, and some may accept an associated parameter. Other arguments will be text strings, giving numbers, file names, or other data.

The following example program demonstrates parsing the command-line arguments for an arbitrary program. It assumes that the legal option characters are "A", "B", "C", and "S", in either upper- or lower-case. The "S" option must be followed by some string representing a parameter.

   /* cparse.c */
   
   #include <stdio.h>
   #include <stdlib.h>
   #include <string.h>
   
   main( int argc, char *argv[] )
   {
     int m, n,                              /* Loop counters. */
         l,                                 /* String length. */
         x,                                 /* Exit code. */
         ch;                                /* Character buffer. */
     char s[256];                           /* String buffer. */
   
     for( n = 1; n < argc; n++ )            /* Scan through args. */
     {
       switch( (int)argv[n][0] )            /* Check for option character. */
       {
       case '-':
       case '/': x = 0;                   /* Bail out if 1. */
                 l = strlen( argv[n] );
                 for( m = 1; m < l; ++m ) /* Scan through options. */
                 {
                   ch = (int)argv[n][m];
                   switch( ch )
                   {
                   case 'a':              /* Legal options. */
                   case 'A':
                   case 'b':
                   case 'B':
                   case 'C':
                   case 'd':
                   case 'D': printf( "Option code = %c\n", ch );
                             break;
                   case 's':              /* String parameter. */
                   case 'S': if( m + 1 >= l )
                             {
                               puts( "Illegal syntax -- no string!" );
                               exit( 1 );
                             }
                             else
                             {
                               strcpy( s, &argv[n][m+1] );
                               printf( "String = %s\n", s );
                             }
                             x = 1;
                             break;
                   default:  printf( "Illegal option code = %c\n", ch );
                             x = 1;      /* Not legal option. */
                             exit( 1 );
                             break;
                   }
                   if( x == 1 )
                   {
                     break;
                   }
                 }
                 break;
       default:  printf( "Text = %s\n", argv[n] ); /* Not option -- text. */
                 break;
       }
     }
     puts( "DONE!" );
   }
For a more practical example, here is a simple program, based on an example from the previous chapter, that attempts to read the names of an input and output file from the command line. If no files are present, it uses standard input and standard output instead. If one file is present, it is assumed to be the input file and opens up standard output. This is a useful template for simple file-processing programs.
   /* cpfile.c */

   #include <stdio.h>
   #include <stdlib.h>
   #define MAX 256

   void main( unsigned int argc, unsigned char *argv[] )
   {
   
     FILE *src, *dst;
     char b[MAX];
   
     /* Try to open source and destination files. */
   
     switch (argc)
     {
     case 1:          /* No parameters, use stdin-stdout. */
       src = stdin;
       dst = stdout;
       break;

     case 2:          /* One parameter -- use input file & stdout. */
       if ( ( src = fopen( argv[1], "r" )) == NULL )
       {
          puts( "Can't open input file.\n" );
          exit( 0 );
       }
       dst = stdout;
       break;

     case 3:         /* Two parameters -- use input and output files. */
       if ( ( src = fopen( argv[1], "r" )) == NULL )
       {
          puts( "Can't open input file.\n" );
          exit( 0 );
       }
       if ( ( dst = fopen( argv[2], "w" )) == NULL )
       {
          puts( "Can't open output file.\n" );
          exit( 0 );
       }
       break;

     default:        /* Too many parameters. */
       puts( "Wrong parameters.\n" );
       exit( 0 );

     }
   
     /* Copy one file to the next. */
   
     while( ( fgets( b, MAX, src ) ) != NULL )
     {
        fputs( b, dst );
     }
   
     /* All done, close up shop. */
   
     fclose( src );
     fclose( dst );
   }
BACK_TO_TOP

[4.7] POINTERS TO C FUNCTIONS

* You know by now that you can declare pointers to variables, arrays, and structures in C. You can also define pointers to functions. This feature allows you to pass functions as arguments to other functions. This is useful if you want to, say, build a function that determines solutions to a range of math functions.

The syntax for declaring pointers to functions is obscure, and so let's start with an idiot example: declaring a pointer to the standard library function "printf()":

   /* ptrprt.c */

   #include <stdio.h>

   void main()
   {
     int (*func_ptr) ();                  /* Declare the pointer. */
     func_ptr = printf;                   /* Assign it a function. */
     (*func_ptr) ( "Printf is here!\n" ); /* Execute the function. */
   }
The function pointer has to be declared as the same type ("int" in this case) as the function it represents.

Next, let's pass function pointers to another function. This function will assume the functions passed to it are math functions that accept double and return double values:

   /* ptrroot.c */

   #include <stdio.h>
   #include <math.h>
   
   void testfunc ( char *name, double (*func_ptr) () );
   
   void main()
   {
     testfunc( "square root", sqrt );
   }
   
   void testfunc ( char *name, double (*func_ptr) () )
   {
     double x, xinc;
     int c;
   
     printf( "Testing function %s:\n\n", name );
     for( c=0; c < 20; ++c )
     {
       printf( "%d: %f\n", c,(*func_ptr)( (double)c ));
     }
   }
You obviously cannot pass any arbitrary function to "testfunc()", since it must agree with the expected number and type of parameters, as well as with the value returned.

BACK_TO_TOP

[4.8] C DYNAMIC MEMORY ALLOCATION & DEALLOCATION

* For simple programs, it is OK to just declare an array of a given size:

  char buffer[1024]
In more sophisticated programs, this leads to trouble. You may not know how big an array needs to be for the specific task the program is performing, and so allocating an array in a fixed size will either result in wasted memory or in not having enough to do the job.

The answer to this problem is to have the program allocate the memory at runtime, and that's what the "malloc()" library function does. For example, let's use "malloc()" to allocate an array of "char":

   /* malloc.c */

   #include <malloc.h>
   #include <stdio.h>
   #include <stdlib.h>                    /* For "exit" function. */

   void main()
   {
     char *p;                             /* Pointer to array. */
     unsigned count;                      /* Size of array. */
   
     puts( "Size of array?" );
     scanf( "%d", count );                /* Get size in bytes. */
     p = (char *)malloc( (size_t)count ); /* Allocate array. */
     if( p == NULL )                      /* Check for failure. */
     {
       puts( "Can't allocate memory!" );
       exit( 0 );
     }
     puts( "Allocated array!" );
     free( p );                           /* Release memory. */
   }
The header file "malloc.h" must be included, and a pointer to the memory block to be allocated must be declared. The "malloc()" function sets the pointer to the allocated memory block with:
   p = (char *)malloc( (size_t)count );
The count is in bytes and it is "cast" to the type of "size_t", which is defined in "malloc.h". The pointer returned by "malloc()" is "cast" to type "char *", that is, a pointer to type "char". By default, in ANSI C, "malloc()" returns a pointer of type "void", which allows the pointer to be cast to any other type.

If the "malloc()" fails because it can't allocate the memory, it returns the value null (as defined in "stdio.h").

It is simple to allocate other data types by changing the "cast" operations:

   int *buf;
   ...
   buf = (int *)malloc( (size_t)sizeof( int ) * count );
The "sizeof()" function is used to determine the number of bytes in the "int" data type.

When you have finished using the memory block, you get rid of it using the "free" function:

   free( p );
C also contains two other memory-allocation functions closely related to "malloc()": the "calloc()" function, which performs the same function as "malloc()" but allows you to specify the block allocated in terms of number of elements:
   void *calloc( size_t <number_elements>, size_t <sizeof_element_type> );
-- and the "realloc()" function, which allows you to reallocate the size of an array that's already been allocated:
   void *realloc( void *<block_pointer>, size_t <size_in_bytes> );
BACK_TO_TOP

[4.9] COMMON PROGRAMMING PROBLEMS IN C

* There are a number of common programming pitfalls in C that even trap experienced programmers:

1: Confusing "=" (assignment operator) with "==" (equality operator). For example:

   if ( x = 1 )
   {
   }
-- is bogus, and so is:
   for ( x == 1; ...
2: Confusing precedence of operations in expressions. When in doubt, use parentheses to enforce precedence.

3: Confusing structure-member operators. If "struct_val" is a structure and "struct_ptr" is a pointer to a structure, then:

   struct_val->myname
-- is wrong and so is:
   struct_ptr.myname
4: Using incorrect formatting codes for "printf()" and "scanf()". Using a "%f" to print an "int", for example, can lead to bizarre output.

5: Remember that the actual base index of an array is 0, and the final index is 1 less than the declared size. For example:

   int data[20];
   ...
   for ( x = 1; x <= 20; ++x )
   {
     printf( "%d\n", data[x] );
   }
-- will give you invalid results when "x" is 20. Since C does not do bounds checking, this one might be hard to catch.

6: Muddling syntax for multidimensional arrays. If:

   data[10][10]
-- is a two-dimensional array, then:
   data[2][7]
-- will select an element in that array. However:
   data[ 2, 7 ]
-- will give invalid results but not be flagged as an error by C.

7: Confusing strings and character constants. The following is a string:

   "Y"
-- as opposed to the character constant:
   'Y'
This can cause troubles in comparisons of strings against character constants.

8: Forgetting that strings end in a null character ('\0'). This means that a string will always be one character bigger than the text it stores. It can also cause you trouble if you are creating a string on a character-by-character basis and forget to tack the null character onto the end of it.

9: Failing to allocate enough memory for a string -- or, if you declare pointers, to allocate any memory for it at all.

10: Declaring a string with a fixed size and then assigning it to a string literal:

   char a[256] = "This doesn't work!";
11: Failing to check return values from library functions. Most library functions return an error code; while it may not be desireable to check every invocation of "printf()", you should take care not to ignore error codes in critical operations.

Of course, forgetting to store the value returned by a function when that's the only way to get the value out of it is a bonehead move, but people do things like that every now and then.

12: Having duplicate library-function names. The compiler will not always catch such bugs.

13: Forgetting to specify header files for library functions.

14: Specifying variables as parameters to functions when you need to specify pointers, and the reverse. If the function returns a value through a parameter, that means it must be specified as a pointer:

   myfunc( &myvar );
The following will not do the job:
   myfunc( myvar );
Remember that a function may require a pointer as a parameter even if it doesn't return a value, though as a rule this is not a good programming practice.

15: Getting mixed up when using nested "if" and "else" statements. The best way to avoid problems with this is to always use brackets. Avoiding complicated "if" constructs is also a good idea; use "switch" if you have any choice in the matter. Using "switch" is also useful even if you have simple "if" statements, since it makes it easier to expand the construct if that is necessary.

16: Forgetting semicolons -- though the compiler usually catches this -- or adding one where it isn't supposed to be -- which it usually doesn't. For example:

   for( x = 1; x < 10; ++x );
   {
      printf( "%d\n", x )
   }
-- never prints anything.

17: Forgetting "break" statements in "switch" constructs. As commented earlier, doing so will simply cause execution to flow from one clause of the "switch" to the next.

18: Careless mixing and misuse of signed and unsigned values, or of different data types. This can lead to some insanely subtle bugs. One particular problem to watch out for is declaring single character variables as "unsigned char". Many I/O functions will expect values of "unsigned int" and fail to properly flag EOF. You should usually cast function arguments to the proper type even if it appears that type conversion will take care of it on its own.

19: Confusion of variable names. It is recommended that such identifiers be unique in the first 6 characters to ensure portability of code.

20: In general, excessively tricky and clever code. Programs are nasty beasts and even if you get one to work, remember that you will have to modify it and even port it to different languages. Maintain a clean structure and do the simple straightforward thing, unless it imposes an unacceptable penalty.

BACK_TO_TOP


< Prev | Next > | Index | HOME