User:Jimregan/C Primer chapter 4

From Wikibooks, open books for an open world
Jump to navigation Jump to search

C Library Functions & Other Comments

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

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.

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

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.

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 out:

  Original strings:
  string 1
  string 2
  New strings:
  string 1
  string 1

Please be aware of two features of this program:

   * This program assumes that "s1" has enough space to store the final string. The "strcat()" function won't bother to check, and will give you erroneous results if that is not the case.
   *A string constant can be used as the source string instead of a string variable. Using one for the destination, of course, makes no sense. 

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()":

   * A "strncmp()" function which, as you might guess, compares "n" characters in the source string with the destination string: "strncmp( s1, s2, 6 )".
   * A "stricmp()" function that ignores case in comparisons.
   * Finally, a case-insensitive version of "strncmp" called "strnicmp". 
  • 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.

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/alnum.
  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

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 );
  }

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.

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 point 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> );

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; ...
  1. Confusing precedence of operations in expressions. When in doubt, use parentheses to enforce precedence.
  1. 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
  1. Using incorrect formatting codes for "printf()" and "scanf()". Using a "%f" to print an "int", for example, can lead to bizarre outputs.
  1. Remember that the actual base index of an array is 0, and the final index is 1 less than the declared size:
  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.

  1. 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.

  1. 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.

  1. 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 manipulating strings on a character-by-character basis and forget to tack the null character onto the end of it.
  1. Failing to allocate enough memory for a string -- or, if you declare pointers, to allocate any memory for it at all.
  1. Declaring a string with a fixed size and then assigning it to a string literal:
  char a[256] = "This doesn't work!";
  1. 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.

  1. Having duplicate library-function names. The compiler will not always catch such bugs.
  1. Forgetting to specify header files for library functions.
  1. 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.

  1. 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.
  1. Forgetting semicolons, though the compiler usually catches that, 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.

  1. 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.
  1. 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.

  1. Confusion of variable names. It is recommended that such identifiers be unique in the first 6 characters to ensure portability of code.
  1. 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.

v2.0.7 / 4 of 7 / 01 feb 02 / greg goebel / public domain