Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

An Aid to Documenting C


AUG88: AN AID TO DOCUMENTING C

AN AID TO DOCUMENTING C

The only thing more painful than debugging your program is documenting it. This utility takes some of the sting out of documenting C projects.

Stewart Nutter

Stewart Nutter is a senior programmer for York International Inc. He may be reached at 111 W Gay St., Red Lion, PA 17356.


Did you ever get caught between a code deadline and the need for revised documentation? All too often, large programming projects reach completion with a large discrepancy between the design documentation and the final actual program. With C's ability to divide a big task into small tasks or modules, keeping the documentation up to date can be a headache. In addition, with the creation of each new module, the effort to revise the documentation can triple. In the crunch to get the program done on time, the master design documentation never seems to get updated.

Like many DDJ readers, I've faced this documentation problem myself. I recently wrote a large program in C that eventually had more than 750,000 bytes of source code. Not only were there more than 220 different source-code modules, but there were lots of assembly code modules and screen files as well. As you might guess, many of the modules weren't specified in the original design. This became a major problem because I wrote the program for another company that planned to maintain it from my documentation. The way the various modules fit together was important, and the best way I could show the relationships was by drawing a tree structure. The problem I faced was that detailing over 220 modules is very time-consuming and, given the project deadline, I probably wouldn't finish.

Here is my solution: a utility to document the source-code modules. Like many programs, this one was based on a philosophy of laziness. My thinking has always been that to do something once is fine, but if the task is boring or repetitious, then it's time for a computer. It was obvious from the circumstances that I needed a program that would do the tedious tree charting for the project.

A quick look through magazines for exactly what I needed did not produce results. But after examining an ad for a program that showed the control flow of a single source-code module, I quickly realized that I could use the same concept for every module and function of a C project. After thinking about it, I came up with an outline of my utility program. The following list are features that I wanted in my charting program:

  • It should take as input a list of filenames.
  • It should output a list of all functions, in the order called, starting with main.
  • The program should count the number of times each function is invoked.
  • The program should maintain the module's filename that contains the function's source code.
  • It should count the number of lines and characters in each module.
  • It should identity the functions not used in the project.
  • It should create a function index.
I eventually wound up writing a program to satisfy this list. The rest of this article details the operation of my program, which I call cp (for C printer).

Figure 1 on opposite page, shows a greatly trimmed printout from the program. In this case, I used the utility to document the various functions and modules in the source code for the cp program itself. Figure 1 gives an example of the first and largest part of any report: the module relationships. The generated report also contains statistics for the sizes of the modules, with function indexes including a list of functions not used.

Figure 1: Sample output from the C printer program. Following this function map come reports on the module sizes and function lists indexed by both module and page number.

The cp program itself is broken into three files: cp.c (the main module), cpfuncts.c (containing the support functions), and cpheader.h (the header module). Listing One, Listing Two, and Listing Three begin with op.c, chheader.h, an cpfuncts.c, respectively.

How the Program Works

The cp program starts by scanning the module files contained in the source-list file for every function either called or defined. (The source-list file is simply an ASCII text file that contains the names of the modules used in the project, one name per line.) After the scan, the program stores all the gathered information in an array of module structures. Each entry in the module structure contains the following information:

  • A pointer to the name of the module.
  • A pointer to the name of the defined function.
  • A pointer to a sequential list of different function structures that are called.
  • The number of different functions that are called by the defined function.
  • A pointer to a linked list of page references.
  • The number of times the defined function is used in the program.
As the program reads each source file, the program keeps a structured list of module names, recording the number of lines and characters, for statistical purposes.

After finding a called function, the program adds the function's name to the sequential list of functions. The list is maintained in a first-come-first served order. Prior to placing a function in the list, the program checks for duplicate entries. If the function is already in the list, then the used count is incremented for be duplicate entry.

The program continues until it has read all the source files. It then sorts the list of defined functions by function name. The printout is started by searching the sorted list for the function main. If the function main was found, the program prints each function in the called function list. If a called function name is found in the defined module list, then the usage count is checked. All defined functions that are used only once are then recursively passed to the print function. When the program completely prints main, the print routine prints the defined functions that are used more than once. While each function is printed, a linked list of page references is updated.

As you can see in the sample printout, the program prints each function inside a text box. Printed in the lower-left corner of the box is a word or description about the function. If the function is a root function or a defining function, then the program prints the name of the module where the function is defined. If the function is defined somewhere in the list of code modules and is currently a called function, then the word defined is printed. The program prints the word library if the function is not defined in any of the source modules. The word recursive is printed if the function is calling either itself or one of its parent functions. The program checks for a recursion depth of 50. If the function being printed is a root function, then the program prints the number of called functions. Otherwise, the number of times each called function is used is printed.

Just the Stats, Madam

After printing all the functions, the program prints statistical data collected during the scan of the modules. The first list contains the name each module with its number of lines and bytes. At the end of the list is the total byte count and the total number of lines. The second list contains the names of all functions that were defined in the list of code modules. The functions are printed in ascending order, with the name of the module file where the function is defined and the number of times the function was used in the program. The third list is an index or a cross reference to the pages where each module is used or defined in the printout. The last list contains the names of all the functions that are not used or called in the program, along with the module where the unused function is defined.

The header file cpheader.h ( Listing Two) contains the definitions of the structures and the global variables. Each source file that includes the header must define the constant MAINMODULE. If MAINMODULE equals zero, then the global variables are stated as external; if MAINMODULE does not equal zero, then the variables are declared. In the file that contains main the constant MAINMODULE is defined as 1 so that the global variables will be initially defined. The other files should define MAINMODULE as 0 so the variables will be externals. The constant MAXFNCTS, in the header file, is the total number of different called functions used in each function. The constant MAXMODULES is the total number of different files that make up the program.

The main file cp.c contains the main function, which checks for command-line inputs, opens the sourcelist file, displays error messages, and prints the data to the destination device. Each file is opened in main and the filename is passed to the function xref.

The function xref looks through the module file looking for functions. Functions to xref are text words followed by an open parenthesis. Defined functions occur when the open brace count is 0. When the open brace count is greater than 1, the function is a called function. Defined functions are differentiated from function declarations because a semicolon follows a function declaration.

Supporting Characters

The module file cpfuncts.c (Listing Three) contains functions called by main and xref. The function getnext processes each character from the source-module file, and rejects any character between quotation marks, apostrophes, or comments.

The function getchars either reads a character from the file or from a last-in-first-out buffer. The buffer contains characters that were read once and need to be reread. If there are no more characters to read, the program returns an end-of-file character.

If a character that was read from the source file needs to be reread, the function pushc will place the character into a last-in-first-out buffer. The function checks for room in the buffer prior to putting the character into the buffer.

The function addlist adds the name of the called function to the defined function's list. The function checks to see if the called function is one of C's reserved keywords or if the called function is already in the list before adding it. If the called function already exists in the list, then addlist increments the usage count.

The function find_mod searches the sorted list of defined functions for the supplied function name. The function find_mod returns the index number of the defined function if it is in the list; otherwise, find_mode returns -1 to indicate that it was not found.

The function doprint starts the printout with a defined function's index. This function is the "meat and potatoes" of the printer program: do print prints all the called functions of the supplied function index. If a called function is defined only once in the source files, then doprint calls itself with the new defined function index. It also knows whether the function is defined, a library function, or a recursive call. Each time that doprint is called, it checks a recursion list. If the new function is not in the list, it is then added to the end of the list. When do print exits, it removes the last entered item from the recursion list.

The function getstats checks for the use of each defined function in the source files. The function getstats scans the list of used functions collected by the function xref when each used function is found in the defined function list, (find_mod) the used count for the defined function is incremented.

The function pagebreak checks the count of printed lines to see if enough lines have been printed to print both a formfeed and a new page header. The function forces the printing of a new page by setting the line value to 99, which is greater than the number of lines allowed per page.

The function recur_chk checks through a list of parent functions for the current function name. If the function is in the list, then the current function is a recursive function. The recursion list is a first-in-last-out list of function names.

The function setpage adds the page number to the linked list of structures containing page data for the supplied function name. The last entry in the list contains a NULL pointer to the next entry.

The function strcheck checks a character set for valid characters in a function name. If the character is a valid function-name character, strcheck returns a value of 1; if not, strcheck returns a value of 0.

Construction Details

I used Microsoft's C compiler version 4.0 and its make utility program. I then compiled the program using the large memory model. As far as I can tell, there aren't any dangerous dependencies on Microsoft C in the code, so translating it to another version of C should be straightforward.

I controlled the project with Microsoft's make utility; the make file is shown in Example 1, on page 48. (If you don't have make then you'll have to enter the appropriate command lines yourself.) The make utility compares the dates of the independent files, which are to the right of the colon, to the dependent file, which is left of the colon. If any of the independent files are newer than the dependent file--implying that you've edited one of them--then the program executes the list of commands that follows. To link the resulting object files, type on the command

     line      link cp + cpfuncts;

Example 1: A sample make file for cp, specifying large model and maximum optimization.

cp.obj : cp.c cpheader.h
     cl -Ox -AL cp.c -c

cpfuncts.obj : cpfuncts.c cpheader.h
     cl -AL -Ox cpfuncts.c -c

cp.ex. : cp.obj cpfuncts.obj
     link cp+cpfuncts;

To use the program, you must create a file that contains a list of all your C source filenames. You do not need to sort the file: cp sorts the file itself so it can tell how far along it is during the processing. For example, the program cp.exe is created from the two source files cp.c and cpfuncts.c. Example 2, on page 48, shows the contents of the list file. The command line in this example should have the following information: cp mylist outfile. mylist is the file that contains the names of the sourcecode files. outfile is the name of the output file or device. If the cp program is called without any arguments on the command line then it will display the proper usage for the program. The program defaults to an output file name of prn if no file name is given on the command line. To invoke cp for the example program, type

     cp cplist cplist.prn <Enter>

The program cp will read the file cplist for the list of filenames and will output the printout to the file cplist.prn.

Example 2: Sample file list created for cp.c.

     cp.c
     cpfuncts.c


Revisions and Improvements

It's difficult to resist the urge to improve a program while you're actually programming, and it's even harder once you've started testing the results. I found this program to be no different. Since I finished the first version of cp, I've added a number of improvements, including these:

    1. Support for Microsoft Windows C programs.

    2. The ability to enter a starting function name, instead of just main from the command line. For example, in a windows program, main would be named WinMain.

    3. Command-line arguments for page width and length.

    4. The saving of the first comment of each module to be printed as a short module description list.

    5. A command-line switch to print the statistical information only.

Feel free to use the program at home or in your business, but please don't distribute it for profit. Programs like this are tools to increase your productivity and should be freely shared. Please direct any questions, comments, improvements, or bugs to the address at the beginning of the article. If you need a reply, include a self-addressed stamped envelope.

_AUTOMATIC MODULE CONTROL IN C_ by Stewart Nutter

[LISTING ONE]

<a name="0179_000c">


/* print a visual tree representation of a 'C' program */
/* cp.c */

/***********************************************************

  cprinter - print a visual tree representation
             of a 'C' program

  copyright 1987, Stewart A. Nutter

  Please do not distribute this for profit. For
  individual use only.

  written by: Stewart A. Nutter

 **********************************************************/

#define MAINMODULE 1
#include "cpheader.h"

main( argc, argv )

char **argv;
int argc;

{
char szName[20];             /* input file name */
int iRet;
int l, i, j, k;              /* index variables */
int sflag;
int pcnt;
int tmp;
int pmax;                    /* max number of xref columns */
int index;
FILE *stream;
struct Pages *p;
long int total;              /* total number of bytes */
long int ltotal;             /* total number of lines */

pFlist = Flist;
pMlist = Mlist;
pMnames = Mnames;

for ( i=0; i<50; i++ )     /* clear out the recursion list */
   rlist[i] = NULL;

printf( "\ncp - ver. 1.3, (C) 1987, 1988  Stewart A. Nutter" );
printf( "\n     written by Stewart A. Nutter\n" );

/* no arguments - print instructions */

if ( argc < 2 )
   {
   printf( "\ncp listfile [ outfile ] [ /l:xx /w:yy /t:main /s:z ]\n" );
   printf( "     outfile            = \"prn\" \n" );
   printf( "     l: page length     = 66       [0, 50-255]\n" );
   printf( "     w: page width      = 80       [80-255]\n" );
   printf( "     m: left margin     = 8        [0-30]\n" );
   printf( "     r: right margin    = 8        [0-30]\n" );
   printf( "     t: target function = \"main\"\n" );
   printf( "     s: statistics only = 0        [0=all, 1=stats only]\n" );
   printf( "\n" );
   printf( "Notes: 1. Maximum recursive function displacement of 50.\n" );
   printf( "       2. Maximum number of functions calls is %d.\n", MAXFNCTS );
   printf( "       3. Maximum number of modules is %d.\n", MAXMODULES );
   exit( 0 );
   }


if ( ( stream = fopen( argv[1], "r" ) ) == NULL )
   {
   fprintf( stderr, "\n%s", strerror( errno ) );
   exit( 1 );
   }

/* an output file name was given? */

index = 2;

if ( argc > 2 && argv[index][0] != '/' )
   {
   output = fopen( argv[2], "w+" );
   index++;
   }
else
   output = fopen( "prn","w+" );  /* prn device by default */

for ( i=index; i<argc; i++ )
   {
   if ( argv[i][0] == '/' && strlen( argv[i] ) > 3 && argv[i][2] == ':' )
      {
      switch ( argv[i][1] )
         {
         case 'l' :              /* change the page length */
            tmp = atoi( &argv[i][3] );
            if ( ( tmp > 50 && tmp < 256 ) || tmp == 0 )
              pl = tmp;
            break;

         case 'm' :              /* change the left margin */
            tmp = atoi( &argv[i][3] );
            if ( tmp >= 0 && tmp <= 30 )
              lm = tmp;
            break;

         case 'r' :              /* change the rignt margin */
            tmp = atoi( &argv[i][3] );
            if ( tmp >= 0 && tmp <= 30 )
              rm = tmp;
            break;

         case 's' :              /* set the stats only? */
            stats = atoi( &argv[i][3] );
            break;

         case 't' :              /* change the target function */
            strcpy( target, &argv[i][3] );
            break;

         case 'w' :              /* change the width */
            tmp = atoi( &argv[i][3] );
            if ( tmp > 79 && tmp < 256 )
              pw = tmp;
            break;

         }
      }
   else
      {
      printf( "\nUnknown argument: %s", argv[i] );
      exit( 1 );
      }
   }

if ( output == NULL )
   {
   fprintf( stderr, "\n%s", strerror( errno ) );
   exit( 1 );
   }

width = pw - lm - rm;
if ( width < 40 )
   {
   fprintf( stderr, "\nThe page width is too narrow." );
   exit( 1 );
   }

printf( "\n" );


/* read the input file for file names */

while ( !feof( stream ) )
   {
   szName[0] = '\0';
   fgets( szName, 19, stream );
   if ( ( l = strlen( szName ) ) > 1 )
      {
      if ( szName[l - 1] == '\n' )
         szName[l - 1] = '\0';    /* remove newline char */
      xref( szName );
      }
   }

/* pointer list for sort */

for ( i=0, pMlist=Mlist; i<Mqty; i++ )
   {
   pm[i] = pMlist++;
   }

printf( "\n\nSorting the function list...\n" );

sflag = 1;
while ( sflag )                 /* sort the function names */
   {
   sflag = 0;
   for ( i=0; i<Mqty-1; i++ )
      {
      if ( strcmp( pm[i]->function, pm[i+1]->function )>0 )
         {
         sflag = 1;
         pMlist = pm[i];
         pm[i] = pm[i+1];
         pm[i+1] = pMlist;
         }
      }
   }
i = find_mod( target );  /* must start with the target function */

if ( i >= 0 )                   /* 'main' must exist */
   {
   depth = 0;

   printf( "Checking for usage...\n" );

/* check how many times each function is used */

   getstats( );
   depth = 0;
   bfr[0] = 0;

   printf( "Starting the printout...\n" );

   line = 0;
   if ( stats == 0 )
      {
      pm[i]->used = 1;   /* set so main shows up in the list */
      doprint( i );  /* print the non-library functions */

      for ( i=0; i<Mqty; i++ )  /* print defined functions now */
         {
         fprintf( output, "\n" );
         line++;
         if ( pm[i]->used > 1 ) /* must be used more than once */
            {
            doprint( i );          /* print the tree structure */
            }
         }
      }

/* print statistics on the modules */

   line = 9999;                 /* force a new page */
   pMnames = Mnames;
   pagebreak( );
   leftmargin( output );
   fprintf( output, "Module statistics :\n" );
   line++;
   total = 0L;
   ltotal = 0L;
   for ( i=0; i<Mcnt; i++ )  /* print module names & sizes */
      {
      pagebreak( );
      leftmargin( output );
      fprintf( output,
              "%-12s - %5u lines, %6ld bytes\n",
              pMnames->name, pMnames->length,
              pMnames->size );
      total += pMnames->size;
      ltotal += pMnames->length;
      line++;
      pMnames++;
      }
   fputc( '\n', output );
   leftmargin( output );
   fprintf( output,
           "Total source size = %ld bytes in %ld lines for %d modules\n",
           total, ltotal, Mcnt );

/* print the used function page index */

   line = 9999;                 /* force a new page */
   pagebreak( );
   leftmargin( output );
   fprintf( output, "Function index :\n" );
   line++;
   for ( i=0; i<Mqty; i++ )    /* print used function names */
      {
      pMlist = pm[i];
      if ( pMlist->used > 0 )
         {
         pagebreak( );
         leftmargin( output );
         fprintf( output,
                 "%-25s - %-12s - used =%d \n",
                 pMlist->function, ( pMlist->name )->name,
                 pMlist->used );
         line++;
         }
      }

/* print the function page cross reference */

   if ( stats == 0 && pl > 0 )     /* print everything */
      {
      pmax = ( int )( width - 27 )/5;
      line = 9999;                 /* force a new page */
      pagebreak( );
      leftmargin( output );
      fprintf( output, "Function cross reference :\n" );
      line++;
      for ( i=0; i<Mqty; i++ )  /* print used function names */
         {
         pMlist = pm[i];
         if ( pMlist->used > 0 )
            {
            pagebreak( );
            leftmargin( output );
            fprintf( output, "%-25s- ", pMlist->function );
            p = pMlist->pg;
            if ( p != NULL )
               {
               pcnt = 0;
               while ( p->next != NULL )
                  {
                  fprintf( output, "%4d,", p->pg );
                  p = p->next;
                  pcnt++;
                  if ( pcnt >= pmax )
                     {
                     fputc( '\n', output );
                     leftmargin( output );
                     fprintf( output, "%27s", " " );
                     line++;
                     pcnt = 0;
                     }
                  }
               fprintf( output, "%4d\n", p->pg );
               line++;
               }
            else
               fprintf( output, "\n" );
            }
         }
      }

/* print statistics on all unused modules */

   line = 9999;                 /* force a new page */
   pagebreak( );
   leftmargin( output );
   fprintf( output, "Un-used function list :\n" );
   line++;
   pcnt = 0;
   for ( i=0; i<Mqty; i++ )  /* print unused function names */
      {
      pMlist = pm[i];
      if ( pMlist->used == 0 )
         {
         pagebreak( );
         pcnt++;
         leftmargin( output );
         fprintf( output,
                 "%-25s - %-12s \n",
                 pMlist->function, ( pMlist->name )->name );
         line++;
         }
      }
   if ( pcnt == 0 )
      {
      leftmargin( output );
      fprintf( output,
              "No un-used functions in the list.\n" );
      }

/* print module comments */

   line = 9999;                 /* force a new page */
   pMnames = Mnames;
   pagebreak( );
   leftmargin( output );
   fprintf( output, "Module comments :\n" );
   line++;
   for ( i=0; i<Mcnt; i++ )  /* print module names & comments */
      {
      pagebreak( );
      leftmargin( output );
      fprintf( output,
              "%12s -%s\n",
              pMnames->name, pMnames->cmt );
      line++;
      pMnames++;
      }
   fprintf( output, "%c", 0x0c );         /* ending formfeed */
   }
}



/* process the file for function names */

xref( fname )

char *fname;

{
int done;               /* loop termination flag */
int brace_cnt;          /* count of the open braces */
int open_paren;         /* open paranthisis count */
int ret;                /* return value */
int indx;               /* for/next index */
int dflg;               /* function definition flag */
static int wflg = 0;
char c;                 /* character read from disk file */
char buffer[50];        /* temporary buffer */
char bufr[256];         /* temporary buffer */
register char *p;       /* fast character pointer */
FILE *stream;           /* module file pointer */
struct Mod_list *cptr;  /* pointer to the module list structure */
static char back[] =
   {8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
    8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8};

printf( "%cProcessing file: %-12s   ", 0x0d, fname );

if ( ( stream = fopen( fname, "r" ) ) == NULL )
   return( -1 );

pp = pc;
if ( ( pMnames->name = strdup( fname ) ) == NULL )
   {
   fprintf( stderr, "Ran out of memory.\n" );
   exit( 1 );
   }
pMnames->length = 0;
pMnames->size = 0L;

buffer[0] = 0;
p = buffer;

open_paren = 0;
brace_cnt = 0;
firstcmt = 0;
filecmt[0] = 0;
done = 0;
ret = 0;
while ( !ret )
   {
   c = getnext( stream );
   switch ( c )
      {
      case '{'  :
         brace_cnt++;   /* increment the open brace count */
         break;
      case '}'  :
         brace_cnt--;   /* decrement the open brace count */
         break;
      case '('  :
         if ( open_paren == 0 ) /* first open paren only */
            {
            open_paren = 1;
            }
         wflg = 1;
         break;
      case ' ' :              /* skip tabs and spaces */
      case '\t' :
         do
            {
            c = getnext( stream );
            }
         while ( c == '\t' || c == ' ' );
         if ( c != '(' )
            wflg = 1;
         pushc( c );
         break;
      case 0x1a :            /* end of the file indicator */
         ret = 1;
         wflg = 1;
      default :

/* the character must be a variable character */

         if ( strcheck( c ) )
            {
            *p++ = c;
            *p = 0;
            }
         else
            wflg = 1;
         break;
      }
   if ( wflg )
      {
      if ( buffer[0] && ( buffer[0] < '0' || buffer[0] > '9' ) )
         {
         done = 1;
         }
      else
         {
         p = buffer;
         buffer[0] = 0;
         open_paren = 0;
         }
      wflg = 0;
      }

/* if done != 0 there is a token */

   if ( done )
      {
      done = 0;
      *p = 0;

      if ( open_paren )               /* functions start with an open paren */
         {
         open_paren = 0;
         if ( brace_cnt == 0 )        /* and no braces */
            {
            dflg = 0;
            for ( indx=0; indx<256 && dflg==0; indx++ )
               {
               c = getnext( stream );
               if ( c == ';' )
                  dflg = 1;
               else if ( c == '\n' )
                  dflg = 2;
               bufr[indx] = c;
               }
            if ( dflg == 0 )
               {
               fprintf( stderr, "\nSyntax error: " );
               fprintf( stderr, "Module description.\n" );
               bufr[indx] = 0;
               fprintf( stderr, "\n%s\n", bufr );
               exit( 1 );
               }

/* put the characters back to be read */

            while ( indx )
               {
               pushc( bufr[indx-1] );
               indx--;
               }

/* this is a function definition */

            if ( dflg == 2 )
               {
               printf( "%-40s%s", buffer, back );
               pMlist->name = pMnames;
               pMlist->qty = 0;
               pMlist->ptr = pFlist;

               /* allocate memory for name */

               if ( ( pMlist->function = strdup( buffer ) )
                    == NULL )
                  {
                  fprintf( stderr, "\nRan out of memory." );
                  exit( 1 );
                  }
               pMlist->used = 0;
               pMlist->pg = NULL;
               cptr = pMlist;
               pMlist++;
               Mqty++;
               if ( Mqty > MAXMODULES )
                  {
                  fprintf( stderr,
                          "Too many new functions\n" );
                  exit( 1 );
                  }
               }
            }
         else
            {
            cptr->qty += addlist( cptr->ptr,
                                 buffer, cptr->qty );

            }
         }
      p = buffer;
      *p = 0;
      }
   }
fclose( stream );
pMnames->cmt = strdup( filecmt );
pMnames++;   /* point to the next function data structure */
Mcnt++;               /* count of the different functions */
return( ret );
}


<a name="0179_000e"><B>[LISTING TWO]</B><pre>

cp.obj : cp.c cpheader.h
   cl -Ox -AL cp.c -c

cpfuncts.obj : cpfuncts.c cpheader.h
   cl -Ox -AL cpfuncts.c -c

cp.exe : cpfuncts.obj cp.obj
   link cp+cpfuncts/st:4096;


<a name="0179_000f"><a name="0179_000f">
<a name="0179_0010">
[LISTING THREE]
<a name="0179_0010">


/* function module for the program 'cp'
   the cp program must be compiled with the large model */

/***********************************************************

 cpfuncts.c - function module for the program 'cp'

 copyright 1987, Stewart A. Nutter

 written by: Stewart A. Nutter

 ***********************************************************/


#define MAINMODULE 0
#include "cpheader.h"

/* getnext - get the next character from the stream */

getnext( stream )

FILE *stream;

{
register char c;
static int qflag=0, cflag=0, eflag=0;
static int dflag=0, aflag=0, ncnt=0;
static int fp;
int b, done;

done = 1;
do
   {
   if ( ( qflag | cflag | eflag | dflag | aflag ) == 0 )
      done = 1;
   c = getchars( stream );

/* process escape sequence characters */

   if ( eflag && c != 0x1a )
      {
      if ( c >= '0' && c <= '7' && ncnt < 3 )
         ncnt++;
      else
         {

/* had less than the 3 octal digits */

         if ( ncnt < 3 && ncnt > 0 )
            pushc( c );
         ncnt = 0;
         eflag = 0;
         }
      }
   else if ( cflag && c != 0x1a )  /* skipping a comment */
      {
      if ( firstcmt == 1 )
         {
         if ( c != '\n' && strlen( filecmt ) < ( width - 14 ) )
            {
            filecmt[fp] = c;
            fp++;
            filecmt[fp] = 0;
            }
         else
            {
            do       /* remove extraneous spaces and tabs */
               {
               b = getchars( stream );
               }
            while ( b == ' ' || b == '\t' );
            pushc( b );
            filecmt[ fp++ ] = ' ';
            filecmt[ fp ] = '\0';
            }
         }
      if ( c == '*' )
         {
         b = getchars( stream );
         if ( b == '/' )
            {
            firstcmt = 2;     /* done with the comment */
            if ( fp > 0 )
               filecmt[fp - 1] = 0; /* terminate the line */
            cflag = 0;
            }
         else
            pushc( b );
         }
      }
   else if ( qflag && c != 0x1a )  /* skipping a string */
      {
      if ( c == 0x27 )
         eflag = 1;
      else if ( c == '\"' )
         {
         pushc( 0x1b );
         qflag = 0;
         }
      }
   else if ( dflag && c != 0x1a )  /* defines/includes etc. */
      {
      if ( c == '\n' )
         dflag = 0;
      }
   else if ( aflag && c != 0x1a )  /* skip a character */
      {
      if ( c == 0x27 )
         {
         aflag = 0;
         pushc( 0x1b );
         }
      else if ( c == '\\' )
         eflag = 1;
      }
   else
      {
      switch ( c )
         {
         case '\"' :
            qflag = 1;
            break;
         case 0x27 :
            aflag = 1;
            break;
         case '#' :
            dflag = 1;
            break;
         case '/' :
            b = getchars( stream );
            if ( b == '*' )
               {

               /* this is the first comment of the file */

               if ( firstcmt == 0 )
                  {
                  firstcmt = 1;
                  filecmt[0] = 0;
                  fp = 0;
                  }
               cflag = 1;
               }
            else
               {
               pushc( b );
               }
            break;
         }
      }
   if ( aflag || dflag || qflag || eflag || cflag )
      done=0;
   }
while ( !done && c != 0x1a );
if ( c == 0x1a )
   {
   ncnt = 0;
   aflag = 0;
   dflag = 0;
   qflag = 0;
   eflag = 0;
   cflag = 0;
   }
return( c );
}


/* getchars - read inputs from the file */

getchars( stream )

FILE *stream;

{
register char c;

if ( pp != pc )
   c = *--pp;
else
   {
   c = fgetc( stream );
   if ( c == EOF )
      c = 0x1a;
   if ( c == 0x0a )
      {
      pMnames->length++;
      pMnames->size++;     /* count the unseen <cr> */
      }
   pMnames->size++;
   }
return( c );
}

/* pushc - save the char. in a last in first out stack */

pushc( c )

char c;

{
if ( ( pp - pc ) < 1000 )
   *pp++ = c;
else
   {

   fprintf( stderr, "\nProgram syntax error:" );
   fprintf( stderr, " Too many pushed characters.\n" );
   exit( 1 );
   }
}


/* addlist - add the name to the list if different */
/*           and if not one of the 'c' key words */

#define KEYS 5

addlist( p, buf, cnt )

struct Func_list *p;
char *buf;
int cnt;

{
int i, ret;
static char *keywords[KEYS] =
   {
   "while",
   "if",
   "for",
   "switch",
   "return",
   };

for ( i=0; i<KEYS && strcmp( buf, keywords[i] )!=0; i++ )
   ;

if ( i < KEYS )
   return( 0 );

for ( i=0; i<cnt && strcmp( buf, p->name ); i++ )
   p++;

if ( i == cnt )
   {
   ret = 1;
   if ( ( pFlist->name = strdup( buf ) ) == NULL )
      {
      fprintf( stderr, "Ran out of memory.\n" );
      exit( 1 );
      }
   pFlist->used = 1;
   pFlist++;            /* point to the next empty cell */
   Fqty++;
   if ( Fqty > MAXFNCTS )
      {
      fprintf( stderr, "Too many functions.\n" );
      exit( 1 );
      }
   }
else
   {
   ret = 0;
   p->used++;
   }
return( ret );
}


/* find_mod - return the index of the linked list for
              the indicated function. A -1 means that
              the function name was not found in the list */

find_mod( buf )

char *buf;

{
int lo, hi, mid;
int d;

lo = 0;
hi = Mqty - 1;
mid = ( hi + lo )/2;

while ( 1 )
   {
   d = strcmp( buf, pm[mid]->function );
   if ( d == 0 )
      break;

   if ( lo >= hi )
      {
      mid = -1;
      break;
      }

   if ( d < 0 )
      {
      hi = mid - 1;
      }
   else
      {
      lo = mid + 1;
      }
   mid = ( hi + lo )/2;
   }

return( mid );
}


/* doprint - print the function name and sub - functions */

static char lib[] = {"(library)"};
static char use[] = {"Used="};
static char fct[] = {"Functs="};

doprint( n )

int n;
{
int i, j, k, l, ret;
struct Mod_list *p;
struct Func_list *q;

l = n;
p = pm[l];

/* add function to list for recursion check */

rlist[depth] = p->function;

pagebreak( );

setpage( pm[l] );
ret = page - 1;
pblock( bfr, p->function, ( p->name )->name, fct, p->qty );

depth++;
strcat( bfr, "   |" );

k = p->qty;
for ( j=0, q = p->ptr; j<k; j++, q++ )
   {
   pagebreak( );
   i = find_mod( q->name );

   if ( recur_chk( q->name ) )
      {
      leftmargin( output );
      fprintf( output, "%s\n", bfr );
      if ( i >= 0 )
         setpage( pm[i] );
      pblock( bfr, q->name, "(recursive)", "", 0 );
      line++;
      }
   else
      {
      if ( i >= 0 )
         {
         if ( pm[i]->used == 1 )
            {

/* got a new function */

            leftmargin( output );
            fprintf( output, "%s\n", bfr );
            line++;
            doprint( i );          /* used only once */
            }
         else
            {

/* a previously defined function */

            leftmargin( output );
            fprintf( output, "%s\n", bfr );
            setpage( pm[i] );
            pblock( bfr, q->name, "(defined)",
                    use, q->used );
            line++;
            }
         }
      else
         {

/* a library function */

         leftmargin( output );
         fprintf( output, "%s\n", bfr );
         pblock( bfr, q->name, lib, use, q->used );
         line++;
         }
      }
   }

/* remove the function from the recursion list */

rlist[depth] = NULL;
bfr[strlen( bfr )-4] = 0;
depth--;
return( ret );
}


/* getstats - get the number of times each
              function is used */

getstats( )

{
register int i;
int j;
register struct Func_list *p;

p = Flist;

for ( i=0; i<Fqty; i++ )
   {
   j = find_mod( p->name );  /* see if the name exists */
   if ( j >= 0 )
      pm[j]->used += p->used;
   p++;
   }
}


/* pblock - print a function block */

pblock( pre, fptr, mptr, sptr, cnt )

char *pre, *fptr, *sptr, *mptr;
int cnt;

{
leftmargin( output );
fprintf( output, "%s %s\n", pre, tline );
leftmargin( output );
fprintf( output, "%s-+%-25s|\n", pre, fptr );
leftmargin( output );
fprintf( output, "%s |%-12s %8s%3d |\n",
        pre, mptr, sptr, cnt );
leftmargin( output );
fprintf( output, "%s %s\n", pre, tline );
line += 4;

}


/* pagebreak - check for a page break and if so
               then print the page header */

pagebreak( )

{
   int i;
   static char title[] = { "C PRINTER  - (c) 1987, 1988  rev. 1.2" };

if ( pl == 0 && line == 9999 )
   {
   fprintf( output, "\n\n\n\n" );
   line = 0;
   }
else if ( pl != 0 )
   {
   if ( line > ( pl - 11 ) )
      {
      fprintf( output, "%c", 0x0c );
      line = 0;
      }
   if ( line == 0 )
      {
      leftmargin( output );

      fprintf( output, "%s", title );
      for ( i=strlen( title ); i<width-10; i++ )
         fputc( ' ', output );
      fprintf( output, "Page:%4d\n", page );

      leftmargin( output );
      for ( i=0; i<width; i++ )
         fputc( '-', output );
      fprintf( output, "\n\n" );
      line = 3;
      page++;
      }
   }
}


/* recur_chk - check if the function just called
               is one being processed */

recur_chk( buf )

char *buf;

{
register char **p;
int ret;

p = rlist;

while ( *p != NULL && strcmp( *p, buf ) )
   {
   p++;
   }

if ( *p == NULL )
   ret = 0;   /* the function was not in the list */
else
   ret = 1;   /* found it */

return ret;
}


/* setpage - put the current page number
             into the linked page list */

setpage( ptr )

struct Mod_list *ptr;

{
struct Pages *p;

p = ptr->pg;
if ( p == NULL )
   {
   p = ( struct Pages * )malloc( sizeof( struct Pages ) );
   if ( p == NULL )
      {
      fprintf( stderr,
              "Ran out of memory for page # list.\n" );
      exit( 1 );
      }
   ptr->pg = p;
   p->next = NULL;
   p->pg = page - 1;
   }
else
   {
   while ( p->next != NULL )
      p=p->next;
   p->next = ( struct Pages * )malloc( sizeof( struct Pages ) );
   if ( p->next == NULL )
      {
      fprintf( stderr,
              "Ran out of memory for page # list.\n" );
      exit( 1 );
      }
   p = p->next;
   p->next = NULL;
   p->pg = page - 1;
   }
}


/* strcheck - check if the character is
              in the variable name set */

strcheck( c )

char c;

{
if ( ( c >= 'A' && c <= 'Z' ) || ( c >= 'a' && c <= 'z' ) ||
     c == '_' || ( c >= '0' && c <= '9' ) )
   return( 1 );
else
   return( 0 );
}


stop( )
{
printf( "hello" );
}


/* print the left margin for the printout */

leftmargin( output )

   FILE *output;                    /* the output device pointer */

{
   register int i;

for ( i=0; i<lm; i++ )
   fputc( ' ', output );
}


<a name="0179_0011"><a name="0179_0011">
<a name="0179_0012">
[LISTING FOUR]
<a name="0179_0012">

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

#define MAXFNCTS   4000    /* maximum number of functions */
#define MAXMODULES 500     /* number of different files */

struct Func_list           /* function statistics */
   {
   char *name;             /* function name */
   int used;               /* times used in function */
   };

struct Pages               /* linked list of pages */
   {
   int pg;                 /* page number */
   struct Pages *next;     /* pointer to next page */
   };

struct Module              /* module statistics */
   {
   char *name;             /* pointer to the module name */
   char *cmt;              /* module comment/description */
   unsigned int length;    /* lines in the module */
   long size;              /* bytes in the module */
   };

struct Mod_list            /* module control stucture */
   {
   struct Module *name;    /* pointer to module file name */
   char *function;         /* name of function in file */
   struct Func_list *ptr;  /* pointer to the first function */
   int qty;                /* different functions called */
   struct Pages *pg;       /* point to the page list */
   int used;               /* times the function is used */
   };

#if MAINMODULE != 0

struct Module Mnames[MAXMODULES], *pMnames;
struct Mod_list Mlist[MAXMODULES], *pMlist, *pm[MAXMODULES];
struct Func_list Flist[MAXFNCTS], *pFlist;
char *rlist[50];           /* recursion function list */
int Mqty = 0, Fqty = 0, Mcnt = 0;
int page=1, line=0, depth=0;
int fline;
int firstcmt;
char bfr[300];
char tline[] = {"+-------------------------+"};
char dbl[] = {"  "};
char vbar[] = {"| "};
char hbar[] = {"+-"};
FILE *output;
char pc[1000] = {0,0,0,0,0,0,0,0,0,0};
char *pp;
char filecmt[300];
int pw = 80;               /* page width */
int pl = 66;               /* page length */
int lm = 8;                /* left margin */
int rm = 8;                /* right margin */
int width;                 /* the printable page width */
char target[40] = "main";  /* target function */
int stats = 0;

#else

extern struct Module Mnames[MAXMODULES], *pMnames;
extern struct Mod_list Mlist[MAXMODULES], *pMlist;
extern struct Mod_list *pm[MAXMODULES];
extern struct Func_list Flist[MAXFNCTS], *pFlist;
extern char *rlist[];       /* recursion function list */
extern int Mqty, Fqty, Mcnt;
extern int page, line, depth;
extern int fline;
extern int firstcmt;
extern char bfr[];
extern char tline[];
extern char dbl[];
extern char vbar[];
extern char hbar[];
extern FILE *output;
extern char pc[];
extern char *pp;
extern char filecmt[];
extern int pw;                    /* page width */
extern int pl;                    /* page length */
extern int lm;                    /* left margin */
extern int rm;                    /* right margin */
extern int width;                 /* the printable page width */
extern char target[];             /* target function */
extern int stats;

#endif









Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.