home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-02-07 | 3.3 MB | 101,098 lines |
Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Designing an Extensible API in C
-
-
- Charles Mirho
-
-
- Charles Mirho is a consultant in New Jersey, specializing in Multimedia. He
- holds a Masters Degree in Computer Engineering from Rutgers University. He can
- be reached on CompuServe at: 70563,2671.
-
-
-
-
- Definition of an Extensible API
-
-
- An application program interface is the set of function calls that a software
- library exports to applications. These functions, along with their parameters,
- are usually prototyped in a header, or include file. In addition to
- prototypes, the header file also contains definitions for structures used by
- the functions, and #defines for flags and return values. All these components
- of the header file make up the complete definition of the API.
- An extensible API can accomodate growth in the library without requiring
- changes to existing applications (beyond a possible recompile). Listing 1
- contains a simple, extensible API.
- The function in Listing 1 is useful in GUI APIs. It defines a region of the
- display where a mouse click can be trapped. A typical call to the function
- might look something like
- #define ID_HELP_BUTTON 10
- int vertices[] = {10,20, 50,20, 50,40, 10,40};
- REGION HelpButton = {sizeof(vertices)/sizeof(int)/2,
- &vertices[0]};
- .
- .
- .
- DefineHotSpot (ID_HELP_BUTTON, &HelpButton);
- The value of ID_HELP_BUTTON varies depending on the application.
- ID_HELP_BUTTON also provides a unique ID for the region. The second parameter,
- &HelpButton, defines the boundary of the region as a set of vertices. Notice
- that the REGION structure is defined in API.H, the header file for the API.
- REGION contains a field for the number of vertices in the region, and a
- pointer to a list of vertices (coordinate pairs). Early versions of the
- library might only support rectangular hot spots (four vertices), but the API
- is extensible because more complex shapes can be used in the future without
- altering the prototype for DefineHotSpot. Compare Listing 1 to a
- non-extensible version of the same API in Listing 2.
- Since the region is always rectangular, only two vertices are required,
- specifying the upper-left and lower-right corners of the rectangle. While this
- function may seem cleaner and more intuitive at first glance, it is extremely
- confining. If future versions of the library must support regions with more
- than four vertices, then you must choose one of two undesirable alternatives:
- You can create an extensible version of DefineHotSpot. Now developers must
- learn two functions, since the old version of DefineHotSpot must be retained
- for compatibility. The result is a cluttered API.
- You must modify existing applications that use DefineHotSpot to support the
- new function API.
-
-
- Structured Parameters
-
-
- The extensible version of DefineHotSpot (Listing 1) uses a structured
- parameter, while the non-extensible version (Listing 2) passes all the
- parameters through the function prototype. Using structured parameters is one
- of the best ways to design an API that is more extensible. As the capabilities
- of the library expand, fields can be added to the structures without changing
- the function prototype. A good application of this technique is in functions
- that return information, such as
- int GetSystemInfo (SYSTEM_INFO
- * sinfo);
- This function is used to get information about devices in the computer system.
- The SYSTEM_INFO structure:
- typedef struct tagSYSTEM_INFO
- {
- int num_displays; /* number of attached displays */
- int num_printers; /* number of attached printers */
- int num_drives; /* number of attached disk drives */
- } SYSTEM_INFO;
- has fields to hold the important properties of the system. (I kept it short
- for clarity.)
- Later versions of the API can expand the structure to accomodate new additions
- to the system, such as tape drives, as in:
- typedef struct tagSYSTEM_INFO
- {
- int_num displays; /* number of attached displays */
- int_num_printers; /* number of attached printers */
- int_num_drives; /* number of attached disk drives */
- int_num_tapes; /* number of attached tape drives */
- } SYSTEM_INFO;
- Because the features of the system are passed through the API in the form of a
- structure, rather than as separate parameters, it is easy to add a field for
- tape drives.
-
-
-
- The Size Field
-
-
- You can add even more flexibility to structured parameters with the size
- field. The size field holds the size, in bytes, of the structure containing
- it. When using a size field, you must make it the first field in the
- structure, as in
- typedef struct tagSYSTEM_INFO
- {
- int size; /* size of this structure */
- int num_displays; /* number of attached displays */
- int num_printers; /* number of attached printers */
- int num_drives; /* number of attached disk drives */
- int num_tapes; /* number of attached tape drives */
- } SYSTEM_INFO;
- The size field makes it possible for existing applications to use newer
- versions of the library without performing a recompile. This is especially
- useful on platforms that use dynamic linking, because dynamic link libraries
- are often packaged separately and sold directly to customers. Application
- developers often have no control over which version of the library customers
- are using.
- To see how the size field can save a recompile, look at the SYSTEM_INFO
- structure again. When the num_tapes field is added, the size of the structure
- changes. It would normally be necessary to recompile applications that use the
- structure so that static and dynamic allocations reserve the correct amount of
- storage. Otherwise, the newer library would write too much data into the
- structure parameter, corrupting memory. However, if the first field of the
- structure contains the structure's size, and you are careful to add fields
- only to the end of the structure, then the structure can be extended without
- the need to recompile existing applications. The library simply examines the
- size field to determine which version of the structure the application is
- passing. If the application is passing the older structure, the size will be
- smaller, and the library knows not to fill the extended fields. Listing 3
- contains an example.
- In Listing 3, the library keeps the declaration of the old SYSTEM_INFO
- structure as oSYSTEM_INFO. The oSYSTEM_INFO structure does not appear in the
- header file that applications use.
-
-
- Interpretation Flag
-
-
- Suppose the GetSystemInfo function is extended in the future to report details
- about particular devices in the system. You can use the same function to get
- the number of displays in the system, and details about the displays the
- system is using, as in:
- typedef struct tagDISPLAY_INFO
- {
- int size;
- /* size of this structure */
- int displayno;
- /* display to get info on */
- int xpixels;
- /* display width in pixels */
- int ypixels;
- /* display height in pixels */
- int bits_per_pixel;
- /* bits per pixel */
- int planes;
- /* video planes */
- } DISPLAY_INFO;
- You can insure that the GetDisplayInfo function will support this and any
- other device-specific structures that come along by changing the original
- prototype to
- int GetSystemInfo
- (int flag, unsigned char *info);
- GetDisplayInfo now accepts a byte-aligned pointer instead of a pointer to a
- specific structure. The function interpretes the pointer differently,
- depending of the value of the flag parameter. You call the function for
- general system information with
- /* API.H */
- #define GET_SYSTEM_INFO 1
- #define GET_DISPLAY_INFO 2
- .
- .
- /* application */
- SYSTEM_INFO sinfo = { sizeof(SYSTEM_INFO), 0, 0, 0 };
- .
- .
- GetSystemInfo (GET_SYSTEM_INFO, (unsigned char *)
- sinfo);
- For details on display devices, you call the function with
- /* application */
- DISPLAY_INFO dinfo = { sizeof (DISPLAY_INFO), 1, 0,
- 0, 0, 0};
- .
- .
- GetSystemInfo (GET_DISPLAY_INFO, (unsigned char *)
- dinfo);
-
- Inside, the GetSystemInfo function would look something like Listing 4.
- Different structures describing entirely different things evolve differently,
- and so it is entirely possible for them to be the same size by coincidence.
- When different structures (as opposed to different versions of the same
- structure) are passed through the API as in Listing 4, the size field alone is
- not sufficient. The interpretation flag resolves any ambiguity.
-
-
- Variable-Sized Structures
-
-
- Variable-sized structures typically have a fixed-sized header portion and a
- variable-sized data portion. The header usually defines or limits the data in
- some way. The header and data are stored contiguously in memory, so that the
- data can be referenced as elements of the structure. This often makes the C
- code that manipulates the data easier to write and read. A variable-sized
- structure is also extensible because the data portion can be any size.
- The REGION structure in Listing 1 can be made variable-length by changing its
- definition to
- /* api.h */
- typedef struct tagREGION {
- int vertex_count;
- int vertices[1];
-
- } REGION;
- int DefineHotSpot (int id,
- REGION *pRegion);
- Suppose you want the user to decide the shape of the region in which to trap
- mouse events. The number and values of the vertices in the region are not
- known at compile time. You first prompt the user for the number of vertices,
- then allocate a region of the proper size, as in
- /* application */
- REGION *pRegion;
-
- printf ("Enter the number of
- vertices:\n");
- scanf {"%d", &cnt);
- pRegion = (REGION *) malloc
- (sizeof (REGION) +
- (2*cnt-1)*sizeof(int));
- The pointer pRegion points to a region large enough to hold cnt vertices. In
- the malloc statement, sizeof(REGION) allocates enough space for the header
- (the field vertex_count) and one vertex, since the typedef for REGION contains
- one vertex. Since each vertex is a pair of integers, cnt vertices requires
- 2*cnt integers. You thus allocate space for 2*cnt-1 integers in addition to
- the space already allocated for the base structure. You then cast the return
- value of malloc to a pointer to a REGION structure. From then on, you can
- refer to the vertices as members of the structure. A loop is used to read the
- vertex pairs, as in
- pRegion->vertex_count = cnt;
- for (i=0;i<cnt;i++)
- {
- fflush (stdin);
- printf ("Enter X,Y of vertex %d\n",
- i+1);
- scanf ("%d , %d",
- &pRegion->vertices [2*i],
- &pRegion->vertices [2*i+1]);
- }
- The region with all its vertices is now passed cleanly to the DefineHotSpot
- function
- DefineHotSpot (ID_HELP_BUTTON,
- pRegion);
-
-
- Templates
-
-
- Sometimes the arguments to a function are so unpredictable that even
- structured parameters are limiting. The classic example of this is the
- Standard C library function printf. The prototype for printf declares a single
- parameter, a string that acts as a template for optional arguments. The printf
- function scans the template for clues to the number and size of the optional
- arguments. This is an extremly powerful technique, since it allows the
- function to accept any number of arguments of any size, in any order.
- Consider, for example, a function that draws an arbitrary set of line
- segments. Each segment has its own attribute for width and color. Segments may
- or may not be connected. We create a simple language to tell the function how
- to draw one or more segments. The language describes the motion and attributes
- of an imaginary pen which moves across the drawing surface. Table 1 describes
- the Simple Drawing Language. Figure 1 shows sample output from the Simple
- Drawing Language. Figure 2 shows pen styles. Listing 5 contains an example of
- the Draw function using the Simple Drawing Language.
- Extending the API is as simple as extending the drawing language. For example,
- to support different line styles, the language is extended to include an s
- (for style) followed by a number 1-5.
-
-
- Callback Functions
-
-
- Callback functions provide a useful way for developers to enter the API. They
- provide developers with a means of extending the API without altering it.
- Suppose, for example, that an API included a function for copying one file to
- another, with optional compression, such as
- int zcopy (char *szSourceFile, char *szDestFile,
- int (*fnCompress)());
- This function takes two files as arguments. The first file is the source to
- copy from and the second is the destination to copy to. The third argument
- specifies an optional callback function to perform compression, so that the
- destination file takes up less space than the source. The callback function,
- if used, is provided by the developer who uses the library. The zcopy function
- calls the callback function repeatedly during the file copy. Developers are
- free to use any compression algorithm they desire. This makes the API
- extensible, since better compression algorithms can be developed and inserted
- without altering the API. Not only that, developers who use the library have a
- means of differentiating their products by offering better compression. The
- callback function resembles
- int fnCompress (unsigned char *pData, int *iSize)
- {
-
- //code to perform compression here
- }
- The zcopy function passes to the function a buffer, pData, which contains the
- raw data from the source file and the size of the buffer. Function fnCompress
- is expected to compress the data in pData (possibly using intermediate
- buffers) and return the buffer and new size to the zcopy function.
- This example is slightly oversimplified. A commercial version of zcopy would
- require additional (possibly structured) parameters to specify things like the
- compression block size. This example is meant only to illustrate the utility
- of callback functions in extending the API.
-
-
- Conclusion
-
-
- Following simple guidelines when designing an API can save headaches down the
- road as the API expands to accomodate new features. Structured parameters,
- variable-sized structures, size fields, interpretation flags, templates, and
- callback functions are some of the ways to prepare an API for future growth.
- Figure 1 Sample output from the Simple Drawing Language
- Figure 2 Pen Styles
- Table 1 Simple drawing language
- p (lower p) - pick up the pen; subsequent moves do not mark the
- drawing surface
- P (upper p) - put down the pen; subsequent moves mark the drawing
- surface
- c set pen color to the value which follows: R=red, G=green,
- B=blue, b=black
- Example: cR - set pen to color red
- w set pen width to the value which follows: T=thin, M=medium, F=fat
- Example: wF - set pen width to fat
- %x move to X coordinate specified by the next parameter
- %y move to Y coordinate specified by the next parameter
-
- Listing 1 An extensible API
- /* API.H */
- typedef struct tagREGION
- {
- int vertex_count;
- int *vertices;
- } REGION;
- int DefineHotSpot (int id, REGION *pRegion);
-
- /* End of File */
-
-
- Listing 2 A non-extensible version of the header file API.H in Listing 1
- /* API.H */
- int DefineHotSpot (int id, int x1, int y1, int x2, int y2);
- .
- .
- .
- /* Application */
- #define ID_HELP_BUTTON 10
-
- DefineHotSpot (ID_HELP_BUTTON, 10,20,50,40);
- /* End of File */
-
-
- Listing 3 An example of how the size field can save a recompile
- int GetSystemInfo (SYSTEM_INFO *sinfo)
- {
- sinfo->num_displays = _getNumDisplays ();
- sinfo->num_printers = _getNumPrinters();
- sinfo->num_drives = _getNumDrives();
- if (sinfo->size == sizeof (oSYSTEM_INFO))
- {
- /* don't touch extended fields */
-
- }
- if (sinfo->size == sizeof (SYSTEM_INFO))
- {
- /* fill extended fields */
- sinfo->num_tapes = _getNumTapes();
- }
- return 0;
- }
-
- /* End of File */
-
-
- Listing 4 The function GetSystemInfo
- int GetSystemInfo (int flag, unsigned char *ptr)
-
- int GetSystemInfo (int flag, unsigned char *ptr)
- {
- if (flag == GET_SYSTEM_INFO)
- {
- if ( (int)*ptr == sizeof (oSYSTEM_INFO))
- {
- oSYSTEM_INFO *sinfo= (oSYSTEM_INFO *)ptr;
- /* don't touch extended fields */
- sinfo->num_displays = _getNumDisplays();
- sinfo->num_printers = _getNumPrinters();
- sinfo->num_drives = _getNumDrives();
- }
- if ( (int)*ptr == sizeof (SYSTEM_INFO))
- {
- SYSTEM_INFO *sinfo = (SYSTEM_INFO *)ptr;
- /* fill extended fields */
- sinfo->num_displays = _getNumDisplays();
- sinfo->num_printers = _getNumPrinters();
- sinfo->num_drives = _getNumDrives();
- sinfo->num_tapes = _getNumTapes();
- }
- }
- if (flag == GET_DISPLAY_INFO)
- {
- DISPLAY_INFO *dptr = (DISPLAY_INFO *)ptr;
- dptr->xpixels = _getDisplayWidth(dptr->displayno);
- dptr->ypixels = _getDisplayHeight(dptr->displayno);
- dptr->bits_per_pixel = _getDisplayBPPix(dptr->displayno);
- dptr->planes = _getDisplayPlanes(dptr->displayno);
- }
- return 0;
- }
-
- /* End of File */
-
-
- Listing 5 The Draw function using the Simple Drawing Language in Table 1
- #include <stdio.h>
-
- #define PENUP() pen_up=1
- #define PENDOWN() pen_up=0
- #define SETCOLOR(x) color=x
- #define SETWIDTH(x) width=x
- #define BLACK 0
-
- #define RED 1
- #define GREEN 2
- #define BLUE 3
- #define FAT 0
- #define MEDIUM 1
- #define THIN 2
-
- #define TRUE 1
- #define FALSE 0
-
- int pen_up;
- int color;
- int width;
- int x1, x2, y1, y2;
- int gotx1, gotx2, goty1, goty2;
-
- /* Function Draw - demonstrates use of the Simple Drawing Language
- Does not actually draw lines, simple parses the first parameter and
- prints commands on screen */
-
- Draw (char *str, int val)
- {
- int *pval=&val; //points to optional parameters
- //restore defaults
- gotx1=goty1=gotx2=goty2=FALSE;
- x1=x2=y1=y2=0;
- pen_up=1;
- color = BLACK;
- width=MEDIUM;
- //loop to parse the command string
- while (TRUE)
- {
- switch (*str++)
- {
- case 'p': PENUP(); break; //pick pen up
- case 'P': PENDOWN(); break; //put pen down
- case 'c': //set color
- switch (*str++)
- {
- case 'R': SETCOLOR(RED); printf ("Color set to red\n");break;
- case 'G': SETCOLOR(GREEN); printf ("Color set to green\n");break;
- case 'B': SETCOLOR(BLUE); printf ("Color set to blue\n");break;
- case 'b': SETCOLOR(BLACK); printf ("Color set to black\n");break;
- default: return -1;
- } //End switch (on character)
- break;
- case 'w': //set width
- switch (*str++)
- {
- case 'T': SETWIDTH(THIN); printf ("Width set to thin\n");break;
- case 'M': SETWIDTH(MEDIUM); printf ("Width set to medium\n");break;
- case 'F': SETWIDTH(FAT); printf ("Width set to fat\n");break;
- default: return -1;
- } //End switch (on character)
- break;
- case '%': //get next optional parameter
- switch (*str++)
- {
- case 'x': //set x coordinate
-
- if (gotx2) {x1=x2; x2=*pval++; break;}
- if (!gotx1) {x1=*pval++; gotx1=TRUE; break;}
- if (!gotx2) {x2=*pval++; gotx2=TRUE; break;}
- break;
- case 'y': //set y coordinate
- if (goty2) {y1=y2; y2=*pval++; break;}
- if (!goty1) {y1=*pval++; goty1=TRUE; break;}
- if (!goty2) {y2=*pval++; goty2=TRUE; break;}
- break;
- default: return -1;
- } //End switch (token)
- //do we have enough info to draw the line?
- if (gotx2 && goty2 && !pen_up)
- {
- printf ("Drawing line <%d,%d>-<%d,%d>\n", x1,y1,x2,y2);
- x1 = x2;
- y1 = y2;
- goty2 = FALSE;
- gotx2 = FALSE;
- } //end if (got both coordinates - draw line)
- break;
- case '\0': //end of command string
- return 0;
- default: return -1;
- } //End switch (on character)
- } //End while (TRUE)
- return 0;
- } //End function (Draw)
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Using Wrappers to Improve Portability of Commercial Libraries
-
-
- Kenneth E. Van Camp
-
-
- Kenneth Van Camp is a Senior Programmer/Analyst for IMI Systems. He has been
- developing professional software applications for over ten years, and holds a
- B.S. in Mechanical Engineering from the State University of New York at Stony
- Brook. He can be reached at: R.D. #1 Box 1255, East Stroudsburg, PA 18301.
-
-
- You've seen the ads for them: the Ultimate Portability Libraries. One supports
- Windows, PenPoint, and Presentation Manager. Another supports UNIX, MS-DOS,
- and VMS. Each one is the "only" tool you'll ever need. Promise.
- Well, maybe not. But you can improve the portability of software that uses
- third-party libraries by isolating all library-specific code and writing
- wrappers. Most programmers are familiar with wrappers as applied to functions,
- but wrappers can also be written for all data structures, constants, and
- global variables. In C++, this concept is known as encapsulation, but it works
- equally well in C.
-
-
- Why Use Wrappers
-
-
- There are several compelling reasons to use wrappers:
- Future portability
- New version protection
- Better parameter and return value checking
- Programming for the lowest common denominator
- Simplification
- Enforced corporate standardization
- Uniformity of naming standards
- See the sidebar "Why Use Wrappers" for more information.
-
-
- Wrapper Functions
-
-
- In their simplest form, wrapper functions are a one-to-one match of library
- functions. Wrappers simplify the interface to a library by combining multiple
- function calls or eliminating unused parameters. If you later determine you
- need a parameter you thought was unnecessary, it is a simple matter to write a
- second wrapper with a more complete parameter list. The simpler wrapper can
- then call the more complex one.
- In C++, unused parameters can be given default values so a future need can be
- easily accommodated. Alternatively, over-loading the wrapper can provide a
- simple mechanism for dealing with multiple complexity levels.
- One of the most important rules of a wrapper function is that none of its
- parameters can be of a type declared by a third-party library. This means that
- all data structures must be wrapped, too. C++ handles this by encapsulation.
- Simply write a class that includes the library data structure as a private
- member, and write access functions for any structure members that are required
- in application code. While you are at it, consider including related wrapper
- functions as members of the class, so they can pass the original data
- structure to library functions without having to be a friend to the class.
- Listing 1 shows an example of a C++ class that encapsulates the Windows 3.1
- API WNDCLASS structure and a related API function.
- In C, this behavior can be easily mimicked. For example, Listing 2 shows an
- equivalent set of wrappers in C for the WNDCLASS structure. typedefs rename
- the Windows structures, and several access functions (all beginning with the
- prefix WCL_) allow the application programmer to define or retrieve values of
- the structure members.
- Notice the initialization function (WCL_init) used in place of a C++
- constructor to initialize values to reasonable defaults. In this case, I
- included several parameters to set structure members that are usually
- initialized in any application. Additional access functions allow me to set or
- retrieve the values of any other members. These can be added as the need
- arises.
- Once you have become used to C++ constructors, you quickly become dependent on
- the idea of an initialization function for your data structures. The only
- problem with using one in C is that the programmer must remember to call it
- before using the data structure. The best approach to this is to write an
- initialization function for every structure type, even if you don't need it,
- and require that an initializer be called before using any data structure.
- That way it is less likely to be forgotten.
- A case can be made for an exit function for every data structure, analogous to
- the C++ destructor. In practice, I have found this is rarely necessary and
- merely places an undue burden on the C programmer.
-
-
- Callback Functions
-
-
- Sometimes, you must write a function with an interface determined by the
- library in use. For instance in Windows, callback functions are commonly
- written to handle messages for dialog boxes, for enumerations, and timer
- processing. In each case, your application's function must process parameters
- that are beyond its control.
- It is possible to write a macro to wrap callback functions and give them an
- interface of your choosing. Personally, I do not use this approach because I
- find this type of macro leads to code that is confusing to read and debug.
- Instead, I place all callback functions in a module of their own. The only
- thing these functions do is load the parameter values into a data structure
- and pass the structure to the real callback function, which is kept in the
- normal application code.
- The problem with this approach is that each callback function has a separate
- wrapper, so each one must be rewritten if the library's callback interface
- changes. For instance, one of the differences between the WIN16 and WIN32 APIs
- is that some data was moved from the lParam to the wParam for several
- messages. A translation function could handle this if you properly
- encapsulated the parameters in a data structure, but every callback wrapper
- must be modified to call the translation function.
- In fact, it is not necessary to have a separate callback function for every
- Windows dialog box. Since a window handle is provided to the callback
- function, a single callback function can handle all dialog messages and
- dispatch them to the appropriate function in your application code for the
- window in use. The situation is the same for enumerations and timer
- procedures.
-
-
- Conclusions
-
-
- Wrappers and encapsulating classes are a simple way to increase your
- software's longevity. If you have experience working with several different
- libraries, you may be able to put an even higher-level interface on your
- wrappers -- one which removes the application programmer even farther from the
- specifics of one library. This will give your application even greater
- longevity as you declare your independence of any one library.
- There is an initial investment in all of these techniques, but this investment
- usually pays for itself long before the initial project ends. In later
- projects, the payback will be nearly instantaneous. In addition, increased
- reliability and standardization provide benefits that are difficult to
- quantify but obvious to any experienced developer.
- Why Use Wrappers
-
-
- Future Portability
-
-
-
- A simple but solid goal when developing software should be to remain as
- independent as possible from third-party software, so that your project does
- not depend on any one vendor or environment. However, you should not reinvent
- the wheel and write your own libraries. Instead you should isolate all of your
- library-specific code in a few modules, including all references to a
- library's functions, data structures, classes, header files, constants, and
- global variables.
- When you isolate all system-dependent code in a few modules, porting to new
- environments in the future might be as easy as rewriting the wrapper modules
- to support a new library. There is no guarantee that your new library can be
- mapped onto the function calls of the old library, but chances are most of it
- will.
- Don't be tempted to ignore this argument, even if you can write compatible
- wrappers on future systems to make them emulate your current environment. This
- can be a trap. No matter how much time you allocate for a future port, you
- will never implement all of the options of your current library. By taking the
- time to isolate your system-dependent code now, parameter checking can be
- tightened in the future to prevent use of features that are not implemented in
- all of your environments.
- While you are wrapping third-party library functions, you will probably also
- want to wrap the file- and memory-access functions that you use. Even though
- the ANSI file-access functions may be all you need right now, there is a good
- chance that a future environment (a network, Windows, OS/2, etc.) will require
- you to use some special access functions. For memory access, you may want to
- use a handle for major data structures to allow future porting to
- virtual-memory environments. A simple, easy-to-maintain handle table allows
- for future migration.
-
-
- New-Version Protection
-
-
- No one can predict how a future release of a commercial library will behave.
- There will always be bug fixes and enhancements that change the behavior of
- the library, for better or for worse. If your application depends on the
- behavior of the old version, you may be able to correct for the changes in the
- wrappers. For example, Microsoft changed the return value of the fputs
- function in Microsoft C between versions 4.0 and 5.0. Although the new return
- value is now an ANSI-standard function, no one expected fputs to change. An
- application developed with a wrapper for fputs could probably have been
- revised to accommodate this change with minimal effort.
-
-
- Better Parameter and Return Value Checking
-
-
- In a perfect world, programmers check the values of all parameters before
- passing them to functions. They also check all function return values to catch
- any errors. In the real world, many junior-level programmers don't know all of
- the conditions they should check for. Even senior-level programmers may
- overlook unlikely error conditions, especially under the stress of a deadline.
- Wrapper functions can check for any unlikely conditions and call a global
- error-reporting function. The application can determine where such error
- messages should be directed, depending upon the current state.
- For instance, when retrieving a database record you normally write application
- code that handles only two conditions. If the call fails, it was most likely
- caused by a missing record. In the wrapper, you can take care of checking for
- unusual conditions like a corrupted database and branch to an appropriate
- routine to shut down and perform an integrity check on the database.
- This approach allows you to write your code checking software in increments.
- As you gain experience with a library and discover new conditions that merit
- checking, you can add these checks without modifying all of your application
- code.
-
-
- Programming for the Lowest Common Denominator
-
-
- While checking parameters, you can also check for options that are not
- available on all the platforms your software supports. If a future port
- determines that an option should not be used, the wrapper can provide a
- warning for programmers in the old environment as well as the new one. This
- will stop future programmers from using options that can't be supported on all
- systems.
-
-
- Simplification
-
-
- If you have six programmers on a team developing a Windows application, should
- you train all six in Windows development? Not necessarily. By assigning two or
- three members to write the wrappers and document the available options, you
- can decrease the learning curve for the remaining team. The remaining team can
- then focus on problems they are better qualified to solve. The team's database
- experts, for instance, can concentrate on database issues.
- Documentation for the wrappers can be as simple as, "This duplicates the
- functionality of the xyz function in library ABC, with the following
- changes..." However, the documentation for library ABC will change from one
- release to another, but your wrapper's interface should not. If time permits,
- you should write complete documentation for the wrapper. Otherwise, the
- library's release number should be noted and the old manuals retained for
- reference.
- If you have the time to properly document your work, you might as well try to
- simplify the application developer's job. If you find that calls to a library
- function are always preceded by one or more "setup" functions, it may make
- sense to combine the functions into a single wrapper call. Also, if you find
- yourself always initializing data structure members to a certain value before
- calling a function, this can easily be placed in the wrapper.
-
-
- Enforced Corporate Standardization
-
-
- By limiting parameter choices and function calls in the wrappers, you can make
- a more consistent application. The look and feel of the application should not
- be a decision left to junior-level programmers, but rather should be a matter
- of corporate policy. Wrappers are the perfect place to enforce this policy.
-
-
- Uniformity of Naming Standards
-
-
- Instead of using whatever naming standards are used in a third-party library,
- the wrappers can be written to conform to your company's naming standards.
- This shortens the learning curve for new programmers, and makes code easier to
- read. For instance, if your company does not normally use Hungarian notation,
- calls to Windows API functions probably look out of place.
-
- Listing 1 A C++ class that encapsulates the Windows 3.1 API WNDCLASS structure
- and a related API function
- #include "windows.h"
-
- typedef int BOOL;
- typedef WNDPROC MYWNDPROC;
- typedef HINSTANCE MYHINSTANCE;
- typedef HCURSOR MYHCURSOR;
- typedef HBRUSH MYHBRUSH;
-
- class MyWndClass
- {
-
- public:
- MyWndClass (char *, MYWNDPROC, MYHINSTANCE, char *);
- void SetCursor (MYHCURSOR hCursor)
- { wndclass.hCursor = hCursor; }
- void SetBackground (MYHBRUSH hbrBackground)
- { wndclass.hbrBackground = hbrBackground; }
- void SetMenu (char *szMenuName)
- { wndclass.lpszMenuName = szMenuName; }
- BOOL RegisterClass (void)
- { return (::RegisterClass (&wndclass)); }
-
- private:
- // Default constructor is inaccessible.
- MyWndClass (void);
- WNDCLASS wndclass;
- };
-
- MyWndClass::MyWndClass (char *szAppName, MYWNDPROC
- WndProc, MYHINSTANCE hInstance,
- char *szMenuName)
- {
- wndclass.style = CS_HREDRAW CS_VREDRAW;
- wndclass.lpfnWndProc = WndProc;
- wndclass.cbClsExtra = 0;
- wndclass.cbWndExtra = 0;
- wndclass.hInstance = hInstance;
- wndclass.hIcon = LoadIcon (NULL,
- IDI_APPLICATION);
- wndclass.hCursor = LoadCursor (NULL,
- IDC_ARROW);
- wndclass.hbrBackground =
- GetStockObject (WHITE_BRUSH);
- wndclass.lpszMenuName = szMenuName;
- wndclass.lpszClassName = szAppName;
- }
-
- /* End of File */
-
-
- Listing 2 The equivalent of Listing 1 using wrapper functions in C
- #include "windows.h"
-
- typedef int BOOL;
- typedef WNDCLASS MYWCLASS;
- typedef WNDPROC MYWNDPROC;
- typedef HINSTANCE MYHINSTANCE;
- typedef HCURSOR MYHCURSOR;
- typedef HBRUSH MYHBRUSH;
-
- void WCL_Init (MYWCLASS *wndclass, char *szAppName,
- MYWNDPROC WndProc, MYHINSTANCE hInstance,
- char *szMenuName)
- {
- wndclass->style = CS_HREDRAW CS_VREDRAW;
- wndclass->lpfnWndProc = WndProc;
- wndclass->cbClsExtra = 0;
- wndclass->cbWndExtra = 0;
- wndclass->hInstance = hInstance;
- wndclass->hIcon = LoadIcon (NULL,
-
- IDI_APPLICATION);
- wndclass->hCursor = LoadCursor (NULL,
- IDC_ARROW);
- wndclass->hbrBackground =
- GetStockObject (WHITE_BRUSH);
- wndclass->lpszMenuName = szMenuName;
- wndclass->lpszClassName = szAppName;
- }
-
- void WCL_SetCursor (MYWCLASS *wndclass,
- MYHCURSOR hCursor)
- {
- wndclass->hCursor = hCursor;
- }
-
- void WCL_SetBackground (MYWCLASS *wndclass,
- MYHBRUSH hbrBackground)
- {
- wndclass->hbrBackground = hbrBackground;
- }
-
- void WCL_SetMenu (MYWCLASS *wndclass,
- char *szMenuName)
- {
- wndclass->lpszMenuName = szMenuName;
- }
-
- BOOL WCL_RegisterClass (MYWCLASS *wndclass)
- {
- return (RegisterClass (wndclass));
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Template Classes for the iostreams Library
-
-
- Randal Kamradt
-
-
- Randy has been programming since 1984, and is working for Bordart Automation
- where he develops CD-ROM based applications for libraries. His special
- interests include object-oriented programming/design and C/C++ programming in
- general.
-
-
- One of the nicer additions that C++ made to C is the iostreams facility. Even
- if you only use C++ as "a better C," iostream provides an elegant method of
- I/O and allows easy extensions for user-defined types. This ease of extension
- only works one way, though, as creating new stream types that work with
- existing code can be fairly complicated, and requires a complete understanding
- of the streams hierarchy.
- When I first got my hands on a C++ compiler I wanted to create a streams class
- that would work with a compact disk. My compact disk driver provided basic
- read routines, and I already had a class that I used as an interface to the
- driver. To provide seamless integration with existing code I needed to create
- new classes that fit in with the existing iostreams hierarchy. (See the
- sidebar "iostreams Hierarchy" for a discussion of this class and virtual
- inheritance.)
- The first step was to copy the definitions of the ifstream, fstreambase, and
- filebuf classes and rename them to cdstream, cdstreambase, and cdbuf classes.
- (I was not concerned with output classes since I was working with CDs.) Then I
- changed the open parameters and constructor parameters to suit the class I
- used to interface with the CD driver. Within the cdbuf class I replaced the
- file-handle integer with a pointer to the CD interface class, and added
- appropriate new and delete statements in the open and close routines of cdbuf.
- Finally, I replaced the calls to the global open, close, read, and seek with
- ones that used the CD class pointer. After doing all that and then fixing the
- few bugs I let creep in, I was convinced that there had to be a better way.
- A few months after creating these classes I was faced with doing it again. By
- this time we had a new version of Borland C++ with templates. After thinking
- about it for a while, I decided that templates could be the better way I
- wanted. By creating generic versions of the classes I had copied before, I
- could create an endless number of different stream types for anything that
- resembled a stream. In this article, I will present these template classes,
- and a serial port stream class as an example. These classes were compiled and
- tested with Borland C++ v3.1. The portability of the templates depends on
- consistency in the iostreams facility across compilers.
-
-
- Template Classes
-
-
- To avoide duplicating classes unnecessarily, I created templates for the
- classes. (See the sidebar "Templates" for more information.) The classes I
- made templates of included filebuf, fstreambase, fstream, ifstream, and
- ofstream, I call the template classes tbuf<T>, tstreambase<T>, tstream<T>,
- itstream<T>, and otstream<T>. The <T> suffix indicates a templeate class. The
- replaceable type is a basic class that has open, close, read, write, and seek
- member functions. (I will call this class T when referrring to this
- replaceable type.) When creating a class for an entity that does not have one
- or more of these functions, you can either create a dummy function that does
- nothing, or one that calls an error routine. See Listing 1 for all template
- class definitions.
- The first template class, tbuf<T> is the most complex. It contains one object
- of the variable type, and controls the operation of that object. tbuf<T>'s
- open, close, and seek member functions directly call T's open, close, and seek
- member functions. The overflow and underflow member functions call read and
- write, along with setting the various buffer pointers. It also has the ability
- to attach an already open T to itself. tbuf<T> is derived from streambuf,
- which provides it's interface. Some of the member functions of tbuf<T>
- override the virtual functions of streambuf. Since ios contains a pointer to
- streambuf, ios has access to these virtual functions, and is able to read and
- write tbuf<T>.
- The second template class, tstreambase<T> contains a tbuf<T>. It is derived
- (virtually) from ios. In its constructor tstreambase<T> initializes ios with a
- pointer to its tbuf<T> object. It can also open its tbuf<T> object if called
- with the necessary parameters. Otherwise, it has an open, close, and attach
- call that map directly to the tbuf<T> open, close, and attach member function.
- The last set of template classes are tstream<T>, itstream<T>, and otstream<T>.
- These are multiply derived from istream/ostream and tstreambase<T>. They are
- shell classes that simply combine the capabilities of the two inherited
- classes. The only thing necessary in the definition is the duplication of the
- constructors, and an open and rdbuf member function, that calls the
- tstreambase open and rdbuf member functions. The open function is redefined to
- give default mode values to itstream<T> and otstream<T>. The rdbuf function is
- redefined to avoid ambiguities with ios which contains its own rdbuf function.
- Note that when creating a deep hierarchy, constructors need to be defined for
- all classes, even if they don't change from the base class's constructor.
- Duplicating numerous constructors, or constructors with long parameter lists,
- can be a nuisance. There are four constructors for itstream<T>/otstream<T>
- that are duplicates of the tstreambase<T> constructors:
- tstream()
- tstream(const char *name, int mode, int prot)
- tstream(T &f)
- tstream(T &f, char *buffer, int length)
- The default constructor, tstream() initializes the buffer using default
- parameters for buffer size. The stream is considered closed. tstream(const
- char *name, int mode, int prot) initializes the buffer, and opens the named
- stream. tstream(T &f) initializes the buffer with the T parameter. tstream(T
- &f, char *buffer, int length) initializes the buffer with T, and sets the
- buffer to the char * with the length specified. These four constructors are
- duplicated in the three classes itstream<T>, otstream<T>, and tstream<T>. In
- all of these cases, the parameters are passed on directly to tstreambase<T>'s
- constructors.
- The constructors for tstreambase<T> call the constructors either for the
- default tbuf<T> constructor, or in the case of constructors tstream(T &f) and
- tstream(T &f, char *buffer, int length) it passes the parameters to tbuf<T>'s
- constructors. It then calls the ios init function to set ios pointer to its T
- data member. For constructor tstream (const char *name, int mode, int prot),
- it calls T's open function.
- The tbuf<T> class has three constructors:
- tbuf()
- tbuf(T &f)
- tbuf(T &f, char *buffer, int length)
- tbuf(), the default constructor, builds a buffer of default size. The T stream
- is considered closed. tbuf(T &f) builds a default buffer, but attaches T as
- its stream. The T stream is considered open for read or write. tbuf(T &f, char
- *buffer, int length) builds a buffer using the buffer and length parameters,
- and attaches T as its stream.
-
-
- SerialStream and ComBuffer classes
-
-
- As an example of how to use these templates, I decided to use a serial port
- stream. In MS-DOS you can access the serial port as a stream, but it is not
- interrupt driven, so it is nearly useless without special drivers. The serial
- port stream is divided into two classes, SerialStream, and ComBuffer. The
- ComBuffer provides an interrupt-driven circular buffer for input, and passes
- output directly to the port. The Serial-Stream class uses the ComBuffer class
- to do its I/O, and has the correct interface to plug into the template. I
- split the classes in two in case there was more then one stream per port.
- However, ComBuffer needed to be mapped directly to a port.
- The only instance of ComBuffer is a static global (see Listing 2 and Listing
- 3) CommPorts which is an array of two, one for each port. Since it is a static
- global, it is initialized before main. It uses the static data member initPort
- to ensure the correct port number is initialized. In the constructor and
- destructor for ComBuffer I included a print statement to visually assert the
- order of the construction. ComBuffer is not a safe or complete class that
- could be used anywhere in a program, so I made the definition private to the
- module. I could alternatively have made the definition private to the
- SerialStream class definition.
- The SerialStream class uses ComBuffer to communicate with the physical ports,
- via a pointer. When a SerialStream is opened, the name parameter is decoded to
- give the port number. The port number is used as an index into the CommPorts
- array, and the pointer to that index is saved. During reading and writing the
- request is passed on to ComBuffer via that pointer. Only one character is read
- or written at a time. This inefficiency does not concern me, since the
- iostream should be unbuffered, and should only request one character at a time
- anyway.
- Any class that is to be used in the streams templates must meet certain
- requirements. First it needs a default constructor. This constructor should
- leave the stream in a closed state. It needs a open function that takes a
- const char *, and two ints for parameters. This is perhaps the most extreme
- restriction, as not all streams will be able to map these parameters. For
- streams that take fewer parameters, as my SerialStream class does, it is easy
- enough to ignore the rest. For a class that needs more information to get
- started, this can be a problem. One alternative could be to use the name
- parameter to pass in additional information:
- x.open("COM1, INT=7, SPEED=2400",ios::in);
- Although this appears sloppy, and requires character translation in the open
- function, it is not without precedent. Another alternative is to access the T
- class directly:
- x.open("COM1",ios::in);
- x.rdbuf()->fd()->setspeed(1200);
- x.rdbuf()->fd()->setint(7);
- The stream class must also define a close function that takes no parameters. A
- read and write function that takes a char * and an int, as well as a seek
- function that takes a long and a ios::seek_dir type must be present. Finally,
- a const void * conversion operator needs to return the open/close state, as
- the this pointer or a null pointer. The open, close, read, write, and seek
- function can all be dummied up if not needed. If seek is dummied, you need to
- make sure the stream is unbuffered.
- I mentioned previously that the portability of the templates depends on how
- similarly different libraries implement the internals of the iostreams
- classes. This code was made with the Borland C++ v3.1 libraries in mind, and
- might need to be changed for other implementations. In the header files,
- Borland mentions minor differences with the AT&T library, so I assume that the
- templates will work as well under AT&T and any other vendor that follows them.
- If the internals of iostreams are not under discussion in the ANSI X6J16
- commitee, then perhaps vendors should include a set of templates similar to
- these in the C++ library to allow different streams types to be portable from
- one implementation to another.
- iostreams Hierarchy
- To provide seamless integration with existing code you need to create new
- classes that fit in with the existing iostreams hierarchy. To illustrate some
- of the relationships, Figure 1 contains a diagram of the ifstream path of the
- iostreams class hierarchy.
- At the bottom of the hierarchy is the ifstream, ofstream, and fstream classes.
- You would normally use these classes when dealing with files as streams. These
- classes provide no new functions or data members, and each is completely
- dependent on its base classes for its abilities. They are derived multiply
- from fstreambase and one of the istream, ostream, or iostream classes.
- The istream class gives ifstream all of its formatted input functionality. The
- >> operator is defined for all basic types, and get, getline, and read are
- also defined for reading. In addition, the control functions gcount, ignore,
- peek, putback, seekg, and tellg are defined. You should use the istream class
- whenever you create input functions for your user-defined types. The ostream
- class is similar but for output streams. The ostream class defines the
- functions flush, put, seekp, tellp, and write along with the < < operator. The
- iostream class is a "shell" class that simply inherits istream and ostream.
- All of these classes are virtually derived from the ios class. I will explain
- more on this later.
- fstreambase gives fstream the ability to open and close a file. fstreambase
- defines the functions attach, close, open, rdbuf, and setbuf. It also contains
- a single data member, a filebuf type. This filebuf type gives fstream the
- ability to read and write, though not directly. The read/write capability is
- given to istream/ostream through the virtual base class ios.
- Both fstreambase and istream/ostream are derived from ios. Since it is a
- virtual derivation, both of the classes share the same copy of ios's data
- members. Among these members are a pointer to a streambuf type. fstreambase
- gives ios a pointer to its filebuf object during initialization. During use,
- istream/ostream uses this pointer to access the filebuf for reading and
- writing. The ios class also has various format and state flags and numerous
- functions for setting and reading these flags.
- The filebuf class is the workhorse of the hierarchy. It is the one that
- actually calls for reads and writes from the file. It also handles opening,
- closing, seeking, and buffering. When an fstream is opened, the fstream-base
- open function calls filebuf's open which calls the global open. The filebuf
- class is derived from streambuf.
-
- The streambuf class is an abstract type that allows ios to communicate with
- different types of buffer classes. Since ios contains a pointer to a streambuf
- type it can communicate with filebuf through virtual functions. The virtual
- functions overflow, underflow, seekoff, setbuf, and sync provide ios and
- istream/ostream the ability to read, write, and seek.
- Templates and Virtual Functions
- Template classes allow a generic class to be defined. Individual objects can
- be declared from it by filling in type information in the individual
- declarations. The classic template example is the array-class. Once the array
- class template is defined somewhere, various types of arrays can be declared:
- // an array of ints:
- Array<int> ArrayOfInts;
- // an array of doubles:
- Array<double> ArrayOfDoubles;
- The compiler will generate the code needed for access and initialization of
- each of the above arrays. If the compiler/linker can optimize, it will
- automatically remove similar code from different modules. Be aware that each
- of these Array declarations will produce its own code. This may be more code
- then you might want. There are ways around this, but that is the general
- limitation of templates.
- Virtual functions can be seen as alternatives to templates. While templates
- provide generic source code, virtual functions provide generic object code.
- Where templates are generally faster, virtual functions generally produce less
- code. This trade off is not always true, and there are other disadvantages on
- both sides. I have found however that virtual functions and templates often
- complement one another, and that very good solutions can be found in the
- mixture of the two. In the above example one might define:
- // an array of anything derived from
- // Numerics:
- Array<Numerics *> ArrayOfNumbers;
- Then if Numerics is used as a base class, anything derived from Numerics can
- be used in this array, and Numerics provides the least-common-denominator for
- operations available for ArrayOfNumbers.
- Figure 1 The ifstream path of the iostreams class hierarchy
-
- Listing 1 tstream.h -- template class definitions
- #if !defined TSTREAM_H
- #define TSTREAM_H
-
- #include <iostream.h> // for base class definitions
-
- // class tbuf<>. Derived publicly from streambuf to
- // allow class ios which contains pointer to streambuf
- // access to virtual functions. tbuf<> implements
- // basic buffering, reading, writing and seeking on
- // a stream. It also provides open, close, attach,
- // and utility functions.
- template <class T>
- class tbuf : public streambuf {
- public:
- // openProtection provides a default parameter to the
- // open functions to specify what protection a file
- // will be created with. You can ignore this if it is
- // not necessary
- static const int openProtect;
- // tbufSize specifies the default buffer size. It is
- // set to 516 bytes.
- static const int tbufSize;
- // Default contructor. Make a buffer without a
- // stream attached. mode has a dual meaning, if it
- // is zero it means that any operation is allowable,
- // and the stream should not be deleted when closing.
- tbuf()
- : stream(0), mode(0), opened(0)
- {
- makbuf();
- }
- // create buffer and attach to t. t is assumed to be
- // already opened in read/write mode. t will not be
- // deleted or closed when closing this buffer
- tbuf(T &t)
- : stream(&t), mode(0), opened(1)
- {
- makbuf();
- }
- // create buffer from parameters, and attach to t.
- tbuf(T &t, char* b, int l)
- : stream(&t), mode(0), opened(1)
- {
-
- setbuf(b, l);
- }
- // destroy buffer. If mode is not zero, t will be
- // closed and deleted. Otherwise just flush the
- // output buffer.
- ~tbuf()
- {
- if(mode)
- close();
- else
- overflow(EOF);
- }
- // return open status
- int is_open() const
- {
- return opened;
- }
- // return reference to stream
- T &fd() const
- {
- return *stream;
- }
- // open stream. mode must not be zero. stream will
- // be closed and deleted when closing buffer.
- tbuf *open(const char *name, int mode,
- int prot = tbuf::openProtect);
- // close buffer and optionally delete stream.
- tbuf *close();
- // attach stream to buffer. Stream is assumed to
- // be opened in read/write mode.
- tbuf *attach(T &);
- // write buffer to stream and reset pointers.
- virtual int overflow(int = EOF);
- // read data into buffer and reset pointers.
- virtual int underflow();
- // sync input and output.
- virtual int sync();
- // seek to offset and flush output buffers.
- virtual long seekoff(long, ios::seek_dir, int);
- // set buffer. For unbuffered i/o set char * to 0.
- virtual streambuf *setbuf(char *, int);
- protected:
- int putBackSize()
- {
- return (blen() > 8) ? 4 : 1;
- }
- void resetpg(int end = 0);
- void makbuf()
- {
- setbuf(new char[tbufSize], tbufSize);
- }
- int unbuffered()
- {
- return streambuf::unbuffered() !base();
- }
- T *stream;
- int mode;
- short opened;
- char lookAhead[2];
-
- };
-
- template <class T>
- const int tbuf<T>::tbufSize = 516;
- template <class T>
- const int tbuf<T>::openProtect = 0;
-
- // Attach an open stream to this buffer. When this
- // buffer is closed don't try to close stream. If
- // not yet buffered, try to create a buffer. Reset
- // the put and get pointers. Return 0 on error, or
- // this if OK.
- template <class T>
- tbuf<T>* tbuf<T>::attach(T &t)
- {
- if(opened) // if already opened, return 0
- return 0;
- stream = &t; // attach stream.
- opened = 1; // set to opened.
- mode = 0; // buffer doesn't own stream.
- if(!base()) // if no buffer yet...
- makbuf(); // try to make one.
- else
- resetpg(); // reset put and get pointers.
- return this;
- }
-
- // Close buffer, and optionally stream attached to it.
- // Flush buffer if data inside. Return 0 on error or
- // this if OK.
- template <class T>
- tbuf<T>* tbuf<T>::close()
- {
- if(!*stream) // if stream closed,
- opened = 0; // set opened off.
- if(!opened) // if not on return 0.
- return 0;
- int ret = 0;
- if(out_waiting()) // if output in buffer.
- // flush output buffer.
- ret = (overflow(EOF) == EOF) ? 1 : 0;
- if(mode) // if mode is 0, don't
- delete stream; // close or delete stream.
- opened = 0; // set opened off.
- return ret ? 0 : this;
- }
- // Write data from buffer to stream. This function
- // is called when the buffer is full and we need to
- // output more characters. The parameter c is the
- // character that caused the overflow. If the
- // stream is buffered, it will be placed in the
- // buffer, otherwise it is written to the stream.
- // If input char is EOF, don't write, just flush.
- // Returns EOF on error, or 1 on success.
- template <class T>
- int tbuf<T>::overflow(int c)
- {
- if(!opened // check to see if stream
- mode == 0 // is on and mode is out.
-
- !(mode&ios::out))
- return EOF;
- if(unbuffered())
- {
- if(c ! = EOF)
- { // if unbuffered,
- char b = c; // write single char
- if(stream->write(&b, 1) != 1)
- return EOF;
- }
- }
- else // else if buffered.
- {
- if(sync() != 0) // sync input and output
- return EOF; // when writing
- resetpg(1); // reset the put/get pointers
- if(c != EOF)
- {
- sputc(c); // add c to the buffer
- gbump(1); // move the get pointer
- }
- }
- return 1; // return OK
- }
-
- // Open stream. If mode ios::ate (at end) is on,
- // seek to end
- template <class T>
- tbuf<T>* tbuf<T>::open(const char* n, int m, int p)
- {
- if(opened !m) //if already on, or no mode,
- return 0; // return error.
- stream = new T; // make new stream pointer.
- stream->open(n, m, p);// open stream.
- if(!*stream) // if stream not open,
- return 0; // return error.
- opened = 1; // set to on.
- mode = m; // remeber mode.
- if((mode & ios::ate) &&
- stream->seek(OL, ios::end) == EOF)
- return 0; // seek to end if ios::ate.
- resetpg(); // reset put/get pointers.
- return this; // return OK.
- }
-
- // Set the buffer, reset the put/get pointers.
- // Return 0 on error, this if OK.
- template <class T>
- streambuf* tbuf<T>::setbuf(char* b, int l)
- {
- if(!b) // turn off buffering.
- {
- streambuf::unbuffered(1);
- return this;
- }
- if(opened && base())// check if stream is opened,
- return 0; // , and no buffer yet.
- setb(b, b+l, 0); // set buffer pointers.
- resetpg(); // reset put/get pointers.
-
- return this; // return OK.
- }
-
- // Seek to offset. dir indicates the relativity of
- // the offset, either from beginning, current position,
- // or end (ios::beg, ios::cur, ios::end).
- // First make sure there's nothing in the buffer.
- template <class T>
- long tbuf<T>::seekoff(long off, ios::seek_dir dir,
- int /* mode ignored */)
- {
- long loff = off;
- int count = out_waiting(); // flush output first.
- if(count)
- {
- if(stream->write(pbase(), count) != count)
- return EOF;
- }
- else if(dir == ios::cur && // if relative seek,
- (count = in_avail()) != 0)
- loff -= count; // discount input.
- resetpg(); // reset pointers.
- return stream->seek(loff, dir);
- }
- // sync input and output buffer pointers. If output
- // is waiting, write it, if input is waiting,
- // back up to read it again
- template <class T>
- int tbuf<T>::sync()
- {
- if (!opened) // check if opened.
- return EOF;
- int count = out_waiting(); // check for output,
- if(count) // in buffer.
- {
- if(stream->write(pbase(), count) != count)
- return EOF; // write output.
- resetpg(1);
- }
- else if(in_avail()) // check for input
- { // in buffer
- long pos = stream->seek(long(-in_avail()),
- ios::cur);
- setg(eback(), gptr(), gptr());
- setp(gptr(), gptr()); // set up to re-read.
- if(pos == EOF)
- return EOF;
- }
- return 0;
- }
-
- // Read from stream to fill buffer. Must be opened and
- // in input mode. If there is input in the buffer,
- // return it. Otherwise call sync, read from stream,
- // and set pointers. If the input is unbuffered,
- // use the lookahead buffer.
- template <class T>
- int tbuf<T>::underflow()
- {
-
- if(!opened mode == 0 !(mode&ios::in))
- return EOF; // check for correct mode.
- if(in_avail()) // if available from buffer,
- return *gptr()&0xFF; // don't bother.
- int c;
- int count;
- if(unbuffered()) // if unbuffered.
- {
- count = stream->read(lookAhead, 1);
- if(count == EOF) // read one char
- { // into look ahead
- c = EOF; // buffer.
- setg(0, 0, 0);
- }
- else
- {
- c = lookAhead[0]&0xFF;
- setg(lookAhead, lookAhead, lookAhead+1);
- }
- }
- else // else buffered.
- {
- if(sync() != 0) // sync pointers.
- return EOF;
- int pb = putBackSize();
- char *b = base(); // read into buffer.
- count = stream->read(b+pb, blen()-pb);
- if(count == EOF) // check read return.
- return EOF;
- setg(b, b+pb, b+pb+count);
- setp(b+pb, b+pb); // set pointers.
- if(count) // return first char.
- c = *gptr()&0xFF;
- }
- if(!count)
- c = EOF;
- return c;
- }
-
- // reset the put and get pointers. If end is
- // true set the end pointers to the end of
- // the buffer.
- template<class T>
- void tbuf<T>::resetpg(int end)
- {
- char *buf = base(); // get the start of buffer.
- char *sbuf = buf + (buf ? putBackSize() : 0);
- char *ebuf; // set start and end pointers.
- if(end)
- ebuf = buf + blen();
- else
- ebuf = sbuf;
- setp(sbuf, ebuf); // set put pointer
- setg(buf, sbuf, sbuf);// set get pointer
- }
-
- // tstreambase is virtually derived from ios. ios
- // does not define any functionality for tstreambase
- // but allows communication between tstreambase and
-
- // istream, ostream and stream classes. The functions
- // defined in tstreambase typically call the same
- // named function for its tbuf<> member and set ios
- // status flags. When a tstreambase is constructed
- // ios is initialized with a pointer to the tbuf<>
- // member. ios see this pointer as a streambuf
- // pointer, and only has access to tbuf<> through the
- // virtual functions, overflow, underflow, sync, and
- // seekoff.
- template <class T>
- class tstreambase : virtual public ios {
- public:
- // Default constructor. Make an unattached
- // buffer, and initialize ios with it's pointer.
- tstreambase()
- : tbbuf()
- {
- ios::init(&tbbuf);
- }
- // Make a buffer attach to named stream, and open
- // stream
- tstreambase(const char* n, int m,
- int p = tbuf<T>::openProtect)
- : tbbuf()
- {
- ios::init(&tbbuf);
- open(n, m, p);
- }
- // Make a buffer attached to already opened stream.
- tstreambase(T &f)
- : tbbuf(f)
- {
- ios::init(&tbbuf);
- }
- // Make a buffer using parameters, and attach
- // to already opened stream.
- tstreambase(T &f, char* b, int l)
- : tbbuf(f, b, l)
- {
- ios::init(&tbbuf);
- }
- // Destroy buffer
- ~tstreambase()
- {
- ;
- }
- // close attached stream and set ios flags.
- void close()
- {
- if(tbbuf.close())
- clear(ios::goodbit);
- else
- setstate(ios::failbit);
- }
- // set buffer and set ios flags.
- void tstreambase::setbuf(char* b, int l)
- {
- if(tbbuf.setbuf(b, l))
- clear(ios::goodbit);
-
- else
- setstate(ios::failbit);
- }
- // open stream and set ios flags.
- void open(const char *, int,
- int = tbuf<T>::openProtect);
- // attach opened stream and set ios flags.
- void attach(T &);
- // return the buffer.
- tbuf<T> *rdbuf()
- {
- return &tbbuf;
- }
- private:
- tbuf<T> tbbuf;
- };
-
- // Attempt to open tbuf<>, and set ios flags
- // accordingly. Opening an already open stream
- // results in an error. If ios::app (append to
- // file) is set, also set ios::out. If ios::out
- // is set, and ios::app, ios::in, and ios::ate
- // (start at end) are not set, set the ios::trunc bit.
- template <class T>
- void tstreambase<T>::open(const char *n, int m, int p)
- {
- if(m & ios::app)
- m = ios::out;
- else if((m & (ios::outios::ateios::appios::in))
- == ios::out)
- m = ios::trunc;
- if(tbbuf.is_open())
- clear(ios::failbit);
- else if(tbbuf.open(n, m, p))
- clear(ios::goodbit);
- else
- clear(ios::badbit);
- }
-
- // Attach an opened stream to buffer, and set the ios
- // bits accordingly.
- template <class T>
- void tstreambase<T>::attach(T &f)
- {
- if(tbbuf.is_open())
- setstate(ios::failbit);
- else if(tbbuf.attach(f))
- clear(ios::goodbit);
- else
- clear(ios::badbit);
- }
-
- // The itstream, otstream and tstream class are merely
- // "shell" classes, and serve only to combine the
- // functionality of it's base class. The only functions
- // defined are the constructor and destructors, and
- // open and rdbuf. There are no addition data members
- // and all functions call directly the functions of the
- // base class. The default open mode is ios::in for
-
- // itstream, ios::out for otstream, and both for
- // tstream.
- template <class T>
- class itstream : public tstreambase<T>,
- public istream {
- public:
- itstream()
- {
- ;
- }
- itstream(const char* n, int m = ios::in,
- int p = tbuf<T>::openProtect)
- : tstreambase<T>(n, m ios::in, p)
- {
- ;
- }
- itstream(T &f)
- : tstreambase<T>(f)
- {
- ;
- }
- itstream(T &f, char* b, int l)
- : tstreambase<T>(f, b, l)
- {
- ;
- }
- ~itstream()
- {
- ;
- }
- tbuf<T> *rdbuf()
- {
- return tstreambase<T>::rdbuf();
- }
- void open(const char *n, int m = ios::in,
- int p = tbuf<T>::openProtect)
- {
- tstreambase<T>::open(n, m ios::in, p);
- }
- };
- template <class T>
- class otstream : public tstreambase<T>,
- public ostream {
- public:
- otstream()
- {
- ;
- }
- otstream(const char* n, int m = ios::out,
- int p = tbuf<T>::openProtect)
- : tstreambase<T>(n, m ios::out, p)
- {
- ;
- }
- otstream(T &f)
- : tstreambase<T>(f)
- {
- ;
- }
-
- otstream(T &f, char* b, int l)
- : tstreambase<T>(f, b, l)
- {
- ;
- }
- ~otstream()
- {
- ;
- }
- tbuf<T> *rdbuf()
- {
- return tstreambase<T>::rdbuf();
- }
- void open(const char *n, int m = ios::out,
- int p = tbuf<T>::openProtect)
- {
- tstreambase<T>::open(n, m ios::out, p);
- }
- };
-
- template <class T>
- class tstream : public tstreambase<T>,
- public iostream {
- public:
- tstream()
- {
- ;
- }
- tstream(const char *n, int m,
- int p = tbuf<T>::openProtect)
- : tstreambase<T>(n, m, p)
- {
- ;
- }
- tstream(T &f)
- : tstreambase<T>(f)
- {
- ;
- }
- tstream(T &f, char *b, int l)
- : tstreambase<T>(f, b, l), iostream()
- {
- ;
- }
- ~tstream()
- {
- ;
- }
- tbuf<T> *rdbuf()
- {
- return tstreambase<T>::rdbuf();
- }
- void open(const char *n, int m,
- int p = tbuf<T>::openProtect)
- {
- tstreambase<T>::open(n, m, p);
- }
- };
- #endif
-
- /* End of File */
-
-
- Listing 2 tserial.h -- serial stream class definitions
- #if !defined SERIAL_H
- #define SERIAL_H
-
- #include <iostream.h>
-
- class ComBuffer; // forward declaration
-
- // This class represents everything needed for the
- // template parameter. It has a default constructor,
- // open, close, read, write, seek, and status.
- class SerialStream {
- public:
- // Default constructor. Initialize an unopened stream
- SerialStream()
- : port(-1)
- {
- }
- // Destructor. calls close.
- ~SerialStream()
- {
- close();
- }
- // The open function sets up the default serial port
- // parameters, and hook up the interrupts. The name
- // is check for "COM1" and "COM2". mode and prot are
- // ignored.
- int open(const char *name, int mode, int prot);
- // Read fills buf with len characters from the
- // circular buffer. This class and any class that
- // cannot seek should be unbuffered, so read typically
- // asks for one character at a time.
- int read(char *buf, size_t len);
- // Write sends the characters in buf to the port.
- int write(char *buf, size_t len);
- // Seek is a dummy function and always retruns error.
- long seek(long, ios::seek_dir) { return -1; }
- // close set the port number to -1 for open testing.
- int close();
- // This function is the status function.
- operator const void *() { return port == -1 ? 0 :
- this; }
- private:
- int port;
- ComBuffer *buffPtr;
- };
-
- #endif
-
- /* End of File */
-
-
- Listing 3 tserial.cpp -- serial port stream classes
- #include <iostream.h>
- #include "tserial.h"
- #include <string.h>
-
- #include <dos.h>
- #include <bios.h>
- #include <conio.h>
-
- // interrupt functions.
- typedef void interrupt far intFunc(...);
-
- // Programmed Interrupt Controller constants.
- const PICO : 0x20;
- const PIC1 : 0x21;
- const EOI : 0x20;
-
- // Offsets from port address.
- const InterruptEnable = 1,
- InterruptIdent = 2,
- ModemControl = 4,
- LineStatus = 5,
- ModemStatus = 6;
-
- // Value for the modem control register.
- const ModemControlValue = 11;
-
- // A serial port circular buffer class. There
- // are only two instances of this class, one
- // for each port COM1, and COM2. They are
- // static globals, and are initailized before
- // main(). They contain a circular buffer,
- // the port number and port address, and the
- // interrupt vector they replace, so that
- // they can be restored at destruction. This
- // is not a complete or robust class, and is
- // private to this module. It is intended
- // only for use by SerialStream.
- class ComBuffer {
- public:
- // Size of circular buffer
- enum{ BufSize = 0x100 };
- // Default constructor.
- ComBuffer();
- ~ComBuffer();
- // Set up vector, and enables interrupts
- void hookInterrupt();
- // Get a single character from the buffer
- int getc();
- // Send a single character to the port
- void putc(int c);
- // Read the line status register.
- int status() const
- {
- return ::inp(portAddr+LineStatus);
- }
- // Check if characters are available
- int avail() const
- {
- return in != out;
- }
- // Receive a character from port. This function
- // is called by the interrupt routine.
- void receive();
-
- private:
- // The mask needed for setting the PIC.
- int interruptMask(int port) const
- {
- return 1 << (4 - port);
- }
- // The interrupt number for each port.
- int interruptVec(int port) const
- {
- return 12 - port;
- }
- char *in, *out, *buff; // The circular buffer
- int port; // The port number 0-1
- int portAddr; // The port address
- intFunc *oldVector; // The previos vector
- static int initPort;
- };
-
- // initPort is used by the constructor to initialize
- // the ports in sequence.
- int ComBuffer::initPort = 0;
- // Only two ports are set up here. If you add more
- // you need to identify the port addresses and
- // interrupt vectors.
- static ComBuffer CommPorts[2];
-
- // Initialize the buffers. Allocate the circular
- // buffer, set the in/out pointers, set the port
- // and port address values, and clear the old
- // vector value.
- ComBuffer::ComBuffer()
- : buff(new char[BufSize]), port(initPort++),
- oldVector(0)
- {
- // this line is for sceptics to see initializing.
- cout << "initializing port #" << port << endl;
- portAddr = port ? 0x2f8 : 0x3f8;
- in = out = buff;
- }
-
- // Delete the circular buffer. If the old vector
- // is a valid address, restore it.
- ComBuffer::~ComBuffer()
- {
- // another line for sceptics.
- cout << "de-initializing port #" << port << endl;
- if(oldVector)
- ::setvect(interruptVec(port),oldVector);
- delete[] buff;
- }
-
- // These two routines reset to PIC, and put
- // the new character in the buffer
- void interrupt far comInterrupt0(...)
- {
- ::outp(PICO,EOI);
- CommPorts[0].receive();
- }
- void interrupt far comInterruptl(...)
-
- {
- ::outp(PICO,EOI);
- CommPorts[1].receive();
- }
-
- // This function sets up the interrupts, and
- // enables the PIC
- void
- ComBuffer::hookInterrupt()
- {
- if(oldVector==0)
- oldVector = ::getvect(interruptVec(port));
- switch(port)
- {
- case 0:
- ::setvect(interruptVec(port),comInterrupt0);
- break;
- case 1:
- ::setvect(interruptVec(port),comInterrupt1);
- break;
- default:
- return;
- }
- ::outp(portAddr+ModemControl,
- ::inp(portAddr+ModemControl)ModemControlValue);
- ::outp(PIC1,::inp(PIC1) & ~interruptMask(port));
- ::outp(portAddr+InterruptEnable,1);
- ::outp(PICO, EOI);
- ::inp(portAddr);
- ::inp(portAddr+InterruptIdent);
- ::inp(portAddr+ModemStatus);
- status(); // clear status reg.
- }
-
- // Receive a byte from the port and place in the
- // circular buffer, called from the interrupt
- // handler.
- void
- ComBuffer::receive()
- {
- if(in == buff + BufSize)
- in = buff; // circular buffer
- *in++ = ::inp(portAddr);
- }
-
- // Get a character from the circular buffer.
- int
- ComBuffer::getc()
- {
- while(!avail())
- if(::kbhit())
- return -1;
- if(out == buff + BufSize)
- out = buff; // circular buffer
- return *out++;
- }
-
- // Send a character directly to the port
- void
-
- ComBuffer::putc(int c)
- {
- while((status() & 0x20) == 0)
- if(::kbhit())
- return;
- ::outp(portAddr, c);
- }
-
- const default_init =_COM_1200_COM_CHR8
- _COM_STOP1_COM_NOPARITY;
-
- // The open function needs to translate the name
- // to the port number, initialize the port, and
- // enable interrupts.
- int SerialStream::open(const char *name, int, int)
- {
- if(::stricmp(name,"COM1") == 0)
- port = 0;
- else if(::stricmp(name, "COM2") == 0)
- port = 1;
- else
- return -1;
- unsigned int stat = :: bios_serialcom(
- _COM_INIT,port,default_init);
- buffPtr = CommPorts+port;
- buffPtr->hookInterrupt();
- if(stat & 0xFF00)
- return -1;
- return 0;
- }
-
- // The close routine sets the port number to -1
- // to indicate it's closed
- int
- SerialStream::close()
- {
- port = -1;
- return 0;
- }
-
- // The read routine repeatedly calls ComBuffer::getc
- // to fill it's buffer. len should always be 1.
- int SerialStream::read(char *b, size_t len)
- {
- for(int i = 0; i < len; i++)
- b[i] = buffPtr->getc();
- return i;
- }
-
- // The write routine repeatedly calls ComBuffer::putc
- // to empty it's buffer. len should alway be 1.
- int SerialStream::write(char *b, size_t len)
- {
- for(int i = 0; i < len; i++)
- buffPtr->putc(b[i]);
- return i;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Safer setjmp in C++
-
-
- Philip J. Erdelsky
-
-
- Philip J. Erdelsky is an R&D engineer with over eight years experience in C
- programming. He can be reached at Data/Ware Development, Inc., 9449 Carroll
- Park Dr., San Diego, CA 92121, or on Compuserve at 75746,3411.
-
-
- Calling setjmp to mark a place in your C program and then calling longjmp to
- return to it, even from a deeply-nested function call, is considered useful
- but slightly hazardous. If control has left the function that called setjmp,
- longjmp will usually crash. Yet the careful use of this perverse pair is an
- efficient way to transfer control when an error detected in a low-level
- function must be handled at a much higher level. When using the alternative,
- passing the error condition laboriously up the chain of functions, testing a
- flag at each step, you must be careful not to go flying past an intermediate
- function's garbage collection, leaving memory blocks unreleased, files open,
- and motors running.
- Surprisingly, setjmp and longjmp are still available in C++, and they still
- work the same way. However, C++ provides a way around the principal danger of
- setjmp and longjmp.
-
-
- Technique
-
-
- The most important part of this technique is to encapsulate the jmp_buf buffer
- in a class object that has a constructor and a destructor:
- #include <setjmp.h>
-
- class error
- {
- error *previous;
- public:
- static error *current;
- jmp_buf jmpbuf;
- error() { previous = current; current = this; }
- ~error() { current = previous; }
- };
-
- error *error::current;
- The class has one static member called error::current, that points to the
- current buffer, the one an error exit will use to make its escape.
- If a function needs to handle an error discovered at a lower level, just begin
- it this way:
- function f(...)
- {
- <variable declarations>
- error error_x;
- // calls constructor for
- // error_x
- if (setjmp(error_x.jmpbuf))
- {
- <garbage collection>
- return;
- // calls destructor for
- // error_x
- }
- <function code>
- <garbage collection>
- // destructor for error_x also
- // called here
- }
- When the buffer error_x is created, its constructor is called. The constructor
- code links the buffer to a chain of previous error buffers, if any, and makes
- it the one pointed to by error::current. The if statement puts the context
- into its jmpbuf member and returns a zero, so processing continues with the
- function code.
- When a lower level function detects an error, it calls
- longjmp (error::current->jmpbuf,1);
- This brings control back to the garbage-collection routines after the if
- statement. When the return statement is executed, the destructor for error_x
- is called, the buffer is removed from the chain, and any subsequent longjmp
- (error::current->jmpbuf, 1) will use the previous buffer, not this one.
- Moreover, if control returns from the function f without detecting an error,
- the destructor for error_x is called automatically. These are the principal
- safety features of this technique.
- Actually, an important part of the garbage collection is performed
- automatically by C++. The destructors for other variables, if any, are called
- automatically.
- If garbage collection is needed in some intermediate function, you have to be
- a little more careful. You might put something like this at the head of the
- intermediate function:
- error error_x;
-
- if (setjmp (error_x.jmpbuf))
- {
- error *p =
- error:: current->previous;
- <garbage collection>
- error::~error(error_x);
- <calls on other destructors>
- longjmp (p->jmpbuf,1);
- }
- You can safely reuse the name error_x because each is local to its own
- function.
- Here is where you run into a little difficulty. C++ is very good about calling
- destructors for local objects when control leaves their scope, but not when
- exit is made via longjmp. Therefore, the destructors for error_x and other
- local variables, if any, must be called explicitly before bailing out.
- You can avoid this drudgery if you are willing to sacrifice portability and
- are using a compiler like Borland C++ 2.0 that uses negative frame addressing.
- Just use the function declare_error, followed by an immediate return, as shown
- in Listing 1.
- Negative frame addressing makes it possible for a carefully coded (but
- admittedly nonportable) function like declare_error to find the return address
- and patch it so that when the following return statement is executed, the
- destructors for error_x, and other local variables, if any, are called, and
- then return is made, not to calling function, but to the special function
- error_exit, which then calls longjmp. Of course, this involves some additional
- runtime overhead, but error exits are not common and need not be fast.
- The function declare_error, followed by an immediate return statement, can
- also be called when the error is first discovered, if the function from which
- it is called needs to call destructors for some of its local variables.
- Even if you have to do this for every intermediate function, you can still
- save a lot of code if each intermediate function is called from many places.
- Occasionally, an error occurs in a non-function block and needs to be handled
- in a larger enclosing block. This technique doesn't work so well because there
- is no practical way for declare_error to find the block exit. However, the old
- break and goto statements will usually suffice in such cases.
-
-
- Limitations
-
-
- This technique isn't foolproof. It can still fail if an error is declared in a
- function called by a constructor or initializer for a variable declared before
- error_x:
- f(...)
- {
- class something old;
- int x = g();
- error error_x;
- if (setjmp (error_x.jmpbuf))
- {
- declare_error();
- return;
- }
- If an error is declared in g, control will pass to a higher level, and the
- destructor for old won't get called.
- Don't even think of trying to get around this by declaring error_x first. If
- an error is declared in g, an attempt will be made to pass control with an
- uninitialized error_x.jmpbuf.
- If memory deallocation is the only garbage collection involved, a more
- sophisticated memory allocator should be able to straighten things out, but
- such a technique is beyond the scope of this article.
- This is a safer setjmp, about the safest available in C++. For a much safer
- setjmp, you have to use other languages that may keep you from doing what you
- really want to do.
-
- Listing 1 An example of using negative frame addressing via the declare_error
- function in Borland C++ to find a return address
- static void near error_return(void)
- {
- longjmp(error::current->jmpbuf, 1);
- }
-
- void declare_error(void)
- {
- _AX = (int) error_return;
- asm MOV BP,[BP]; // move frame pointer up one level
- asm MOV [BP+2],AX; // patch return address
- #if defined(__MEDIUM__) defined(__LARGE__) defined(__HUGE__)
- asm MOV [BP+4],CS; // if return address is far, patch segment, too
- #endif
- }
-
- error error_x;
- if (setjmp(error_x.jmpbuf))
- {
- <garbage collection>
- declare_error();
- return;
-
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The UUCP g Protocol
-
-
- Ian Lance Taylor
-
-
- Ian Lance Taylor took a course in COBOL at the Cambridge Rindge and Latin High
- School, from which he graduated in 1982. Since then he has written an ANSI C
- library for a comuter system which nobody has ever heard of, has ported the
- DECUS version of Zork from Fortran to C, has written a complete (UNIX only)
- UUCP package, and has read Knuth from cover to cover. He works for Cygnus
- Support, a company dedicated to supporting free software. He can be reached at
- ian@airs.com.
-
-
-
-
- Introduction
-
-
- The UUCP suite of programs is widely used in the UNIX world to transfer mail
- and news between computers. UUCP implementations are also available for many
- types of personal computers. This article is a detailed description of the
- original, and most frequently used, UUCP transport-layer protocol.
- The basic UUCP service is unattended file transfer over telephone lines or a
- network connection. Other programs build on that base to provide remote
- program execution. The first UUCP implementation was written by Mike Lesk at
- AT&T in 1976. UUCP has been rewritten and enhanced several times since then,
- most notably by Peter Honeyman, David A. Nowitz, and Brian E. Redman in 1983,
- who wrote what is known as HoneyDanBer UUCP. I recently rewrote the program
- suite from scratch, in order to distribute it in sourcecode form under the GNU
- Public License. My implementation is modestly entitled Taylor UUCP.
- UUCP is a point-to-point protocol, which means that it is used between two
- computers at a time. The protocol has two layers, a session layer and a
- transport layer. The two computers use the session layer to identify each
- other and to agree on which files should be transferred. The session layer
- relies on the transport layer to provide error-free data transfer across the
- communication link.
- UUCP uses several different transport-layer protocols, which are optimized for
- different types of communication links. Not all UUCP implementations support
- every protocol. When two computers begin a UUCP session, they negotiate which
- transport layer protocol to use. The transport protocols are known by single
- character names. The most common ones are g, f, t, and e.
- In the article I am going to discuss the g protocol. It is the original UUCP
- transport-layer protocol, and it is the only one supported by all UUCP
- implementations. It is based on a packet-driver protocol originally written by
- G.L. Chesson at Bell Labs. It is intended to work over unreliable connections,
- such as phone lines, but it requires an eight-bit transparent connection. If
- any special characters cannot be sent over the line, such as XON or XOFF, it
- will not work.
- There is not enough room here to present the complete g protocol
- implementation used by Taylor UUCP, so this article merely shows the high
- points. Listing 1 shows a number of typedefs, macro definitions, variables,
- and prototypes that are used in the later code. The complete Taylor UUCP code
- is available for anonymous FTP from various sites, including ftp.uu.net.
- Since the g protocol works over noisy phone lines, it has to be prepared for
- data to be modified, or even lost, as it is sent from one computer to the
- other. (Some types of network connections can also duplicate data. The g
- protocol does not always handle this correctly).
- This protocol provides flow control. The computer sending data is permitted to
- send only a certain number of bytes to the receiving computer before it must
- wait for an acknowledgement. That prevents a fast computer from overwhelming a
- slower computer with more data than the latter can handle.
- The g protocol communicates in packets. There are two types of packets,
- control packets and data packets. The header of a data packet tells the
- receiver how much data is present. It also has a checksum which is used to
- detect transmission errors.
-
-
- Packet Header
-
-
- All packets begin with a six-byte header. Control packets have no attached
- data, and are therefore exactly six bytes long. Data packets contain data
- following the header bytes. The header bytes are shown in Figure 1.
- The first header byte is always the ASCII character DLE, which is octal 020 or
- control-P. If there is a transmission error, this byte can be used to locate
- the start of the next header.
- The second header byte is known as K. For a control packet, the value is
- always 9. For a data packet, K indicates how many bytes of data follow the
- six-byte header. The amount of data is 2K+4. The K value is always between 1
- and 8, which means that the amount of data in a single packet can be any power
- of 2 between 32 (21+4) and 4,096 (28+4).
- The third and fourth header bytes form a two-byte (16-bit) checksum. The third
- byte is CHECKLOW and the fourth byte is CHECKHIGH. The full checksum is
- (CHECKHIGH << 8) CHECKLOW
- The checksum differs for control and data packets, and is described further
- below.
- The fifth header byte is the control byte. It is composed of three bit fields,
- known as TT (2 bits), XXX (3 bits), and YYY (3 bits). The value of the control
- byte is
- (TT << 6) (XXX << 3) YYY
- TT indicates the type of the packet:
- 0 -- control packet
- 1 -- alternate data channel
- 2 -- data packet
- 3 -- short data packet
- The meanings of XXX and YYY depend upon the type of packet, and are described
- further below. The alternate-data-channel packet type (with a TT value of 1)
- is not used by UUCP.
- The sixth and last header byte is a simple check on the validity of the
- header. It is the exclusive-OR of the second through fifth header bytes (the
- first byte is always DLE, and so does not have to be checked further). If the
- exclusive-OR check fails, the header data is invalid and is ignored.
-
-
- Windows and Flow Control
-
-
- Each data packet has a packet sequence number, which is the XXX value. The
- packet sequence number is always between 0 and 7. At the beginning of the
- session it starts at 1, goes up to 7, then wraps around to 0. The sequence
- numbers for incoming and outgoing packets are independent. After one computer
- sends out packet 7, the next packet it sends out is packet 0, regardless of
- how many intervening packets it has received.
- The packet sequence number is used to detect data loss. If the packet
- following packet 5 is not packet 6, then information has been lost. Some
- protocols, such as the Internet TCP protocol, permit packets to arrive out of
- order, but the g protocol considers this an error. Control packets have no
- sequence number, so there is no way to detect a lost control packet.
- The packet sequence number is also used for flow control. During protocol
- initialization each computer announces a window size. The window announced by
- one computer is the number of packets the other computer may send before it
- sees an acknowledgement.
- There are two ways to send an acknowledgement. One is the YYY field of a data
- packet. The other is the YYY field of an RR control packet. (Control packets
- are described further below.) The two methods allow the acknowledgement to be
- either combined with data or not combined, depending on whether the
- acknowledging computer has any data to send. In either case, the
- acknowledgement gives the last packet number which was correctly received.
- Every packet must be acknowledged, and every packet must be acknowledged in
- order.
- For example, suppose that computer A has announced a window size of 3, and
- that at some point during the conversation A has sent an acknowledgement for
- packet 6. This means that computer B may send 3 more packets--namely packets
- 7, 0, and 1--but it may not send packet 2 until it has received an
- acknowledgement for packet 7. Note that the window size announced by A is a
- restriction on which packets B is permitted to send.
- The window size may range from 1 to 7. A window size of 1 means that each
- packet must be acknowledged before the next one is sent. To see why the window
- size may not be 8, suppose that computer A has just sent packet 0, and
- received an acknowledgement for it. If the window size were 8, A could then
- send packets 1, 2, 3, 4, 5, 6, 7, and 0. If all eight packets were lost, B
- might time out, assume that its acknowledgement was lost, and acknowledge
- packet 0 again. When A saw the acknowledgement, it would not know whether the
- eight packets it sent were lost or whether B saw them all but all the
- acknowledgements but the last one were lost. With a maximum window size of 7,
- each packet acknowledgement is unambiguous.
- The original UUCP implementation always used a window size of 3, and some
- implementations have followed its lead. Many newer implementations default to
- the maximum window size of 7.
- The window size prevents a fast computer from overwhelming a slow computer. If
- there were no way to slow down the fast computer by forcing it to wait for an
- acknowledgement, it could eventually fill the input buffers of the slow
- computer and cause data to be lost. The problem would be detected, since there
- would be checksum failures, but the time required for the computers to get
- back in sync would slow the overall data transfer rate drastically.
- Many systems rely on XON/XOFF handshaking for flow control. Data transmission
- stops when an XOFF character (control-S) is received, and starts again when an
- XON character (control-Q) is received. This does not work with the g protocol,
- which requires an eight-bit transparent communication line. (For example, one
- of the checksum bytes in the header might be XON or XOFF.)
-
- Some modern error-correcting modems and serial ports can use various forms of
- hardware handshaking for flow control. This does not make the g protocol
- useless, since error detection is still necessary for the connection between
- the modem and the computer. In any case, a large window is relatively
- efficient even when it is not needed.
-
-
- Data Packets
-
-
- A data packet is indicated by a TT value of 2 or 3. The K value must be
- between 1 and 8, for a packet size between 32 and 4,096 bytes. This is the
- number of bytes which follow the header. For example, a header with a K value
- of 2 indicates a total packet size of six header bytes plus 64 data bytes for
- a total of 70 bytes.
- During protocol initialization, each side announces the largest data packet it
- is prepared to receive. The original UUCP implementation always set the
- maximum data-packet size to just 64 bytes. Many later implementations have
- followed suit. While this small packet size was once reasonable, modern
- high-speed error-correcting modems are much more efficient when larger packet
- sizes are used.
- Not every file contains a multiple of 32 bytes, which is the minimum packet
- size. Therefore, there has to be a way to transfer fewer bytes in a single
- packet. This is done with a short data packet, with a TT value of 3. The K
- value always indicates the physical number of bytes that are sent over the
- communication link. However, a short data packet contains a smaller number of
- logical bytes.
- If the first data byte of a short data packet is less than 128, then that byte
- is subtracted from the physical packet length to get the logical packet
- length. The real data starts with the second byte. Otherwise a 15-bit value is
- formed with the low-order seven bits of the first byte and the complete second
- byte. The logical packet length is this value subtracted from the physical
- packet length, and the real data starts with the third byte. The logical
- length is given using subtraction because, if the logical length is only one
- byte less than the physical length, there is only a single byte available to
- specify the logical length.
- The data-packet layout is designed so that the data can be manipulated as a
- block, without having to consider each individual byte (other than when
- computing the checksum). Some other communications protocols, such as Zmodem,
- use escape characters embedded within the data. This forces the code to
- examine each data byte and make a decision about it, which is less efficient.
- Listing 2 shows the fgsenddata function, which sends out a data packet. The
- zdata argument points to the data, and the six bytes before zdata are
- available to hold the header (ensuring that these six bytes are available
- saves a call to memcpy in the common case). The cdata argument is the length
- of the data in zdata. If the packet size is larger than 64, then this code
- assumes that it is dealing with a newer UUCP implementation which will be able
- to handle different packet sizes within the same session.
- Listing 2 shows a call to the igchecksum function, which is shown in Listing
- 3. Note that every byte sent with the data packet participates in the
- checksum, even though the values of some of the bytes may be unimportant in a
- short data packet.
- The g protocol checksum is an ad hoc hashing scheme. It is the most
- time-consuming part of the entire protocol, and it is not even particularly
- good at detecting errors. A cyclic redundancy check would be more efficient to
- compute and would also catch more errors. For a brief but interesting
- discussion of this, see the book Design and Validation of Computer Protocols,
- by Gerard J. Holzmann. Note that after the checksum is computed it is
- manipulated still further before being placed in the packet header, as shown
- in Listing 2.
-
-
- Control Packets
-
-
- A control packet is indicated by a TT value of 0 and a K value of 9. The XXX
- value indicates the type of control packet:
- 0 -- not used
- 1 -- CLOSE
- 2 -- RJ
- 3 -- SRJ
- 4 -- RR
- 5 -- INITC
- 6 -- INITB
- 7 -- INITA
- A CLOSE packet is sent when the conversation is finished, and the g protocol
- is shutting down. It is also sent if some fatal error is forcing one computer
- to drop the connection. The YYY value is ignored.
- The RJ control is also known as NACK. It means that there was some problem
- with a received packet, such as a checksum error, and that data must be
- resent. The YYY value is the last successfully received packet. Every
- subsequent packet must be resent.
- The SRJ control is actually not used by UUCP. Most UUCP programs will not
- recognize it. It is supposed to request retransmission of just packet YYY.
- The RR control is also known as ACK. It is used to acknowledge a data packet
- without sending a data packet in return. (Acknowledgements were discussed
- above in the section on windows and flow control). The YYY value is the packet
- being acknowledged.
- The INIT controls are used during protocol initialization. When the protocol
- starts up, each computer exchanges pairs of each type of INIT packet. The
- computer which placed the call sends an INITA, then the other computer
- responds with an INITA, and similarly for INITB and INITC. The YYY value for
- an INITA or INITC packet is the window size that the other computer should
- use. The YYY value for an INITB packet indicates the packet size that the
- other computer should use. This is encoded as one less than the K value for
- the desired packet size, so that it is a value from 0 to 7. The initialization
- exchange is not a negotiation. Each computer simply makes a request. Both
- computers may wind up using different window and packet sizes.
- Listing 4 shows the code to send a control packet, and shows how the checksum
- value is computed.
-
-
- Error Handling
-
-
- Error handling is an important aspect of any communications protocol. The bulk
- of the Taylor UUCP implementation is devoted to it, although there is
- unfortunately not enough space to present the actual code. A protocol must be
- able to detect communication errors to ensure that it receives correct data.
- After an error occurs, the protocol must be able to quickly recover and
- continue transmitting information.
- There is no way to ensure completely reliable data transfer across telephone
- lines. They can, in theory, arbitrarily corrupt data. In practice, however,
- telephone lines are fairly reliable. A fairly simple checksum is thus adequate
- to detect data corruption. The g protocol relies on checksums for error
- detection, and negative acknowledgements and timeouts for error recovery.
- An error in a packet header can be detected by an invalid exclusive-OR byte or
- an invalid K value. After an error, the receiving computer must start looking
- for a new DLE byte. It must start looking at the second byte of the header. It
- may not skip the rest of the header, and it may certainly not skip the entire
- packet. If data were lost, then the start of the next packet might appear in
- the skipped data. Another DLE byte might be found that is actually part of the
- data in the corrupted packet, but the exclusive-OR byte should prevent it from
- being treated as a legitimate packet header.
- An error in packet data is detected by an invalid checksum. If an invalid
- packet is seen, the receiving computer will send an RJ packet to tell the
- sending computer that the packet must be resent. The receiver must then start
- looking for a new DLE byte. Once again, it must not skip the packet data,
- since the checksum error might have been caused by lost data. The next packet
- header might be somewhere within the bytes which were assumed to be the data
- for the erroneous packet.
- An RJ packet includes the sequence number of the last correctly-received
- packet. All subsequent packets must be resent. This is referred to as a
- go-back-N protocol. After an error, the sending computer must resend all the
- packets from a particular point, rather than just resend the erroneous packet.
- An SRJ packet could be used to request just the erroneous packet, but most
- UUCP implementations do not implement it. A go-back-N protocol can be less
- efficient than just resending a single packet, but it is easier to implement.
- There are some concerns with a go-back-N protocol. If computer A is
- temporarily overloaded with work, it might not find time to process its input
- buffers, and data might be lost. When it notices the loss, it will send an RJ
- packet. Computer B is permitted to respond with an entire window full of data,
- but if it does computer A might simply get overloaded again. Taylor UUCP uses
- a simple slow-start algorithm, which temporarily shrinks B's sending window to
- a single packet and opens it up slowly as each acknowledgement is received.
- Part of this code can be seen at the end of Listing 2.
- A lost packet will cause the next packet to be seen as an out-of-order packet,
- a case which requires a bit of thought. If A detects an invalid packet, it
- will send an RJ packet. It will then expect to see another copy of the invalid
- packet. However, B will probably have already sent the next few packets. It
- may even have sent an entire window full of packets. If A sends an RJ packet
- for each out-of-order packet, B will see a sequence of RJ packets all
- requesting the same invalid packet, which it will have to send multiple times.
- Taylor UUCP simply ignores out-of-order packets, and relies on a timeout to
- detect completely lost packets. More sophisticated approaches are possible.
- Although phone lines cannot duplicate data, certain types of network
- connections can. This can confuse the g protocol. Suppose that the window size
- is 7, and that A sends out packets 4, 5, 6, 7, 0, 1, 2. After A received an
- acknowledgement for packet 4, it will send out packet 3. If packet 4 is
- duplicated, and the duplicate arrives at B just after packet 3 arrives, B will
- see the wrong data. This is a potential problem for any windowing protocol. It
- is generally avoided by making the window large enough to ensure that any
- duplicate packets will arrive before a complete window is sent. For example,
- the sequence number used by the Internet TCP protocol is a full 32 bits.
-
-
- The UUCP Session Layer
-
-
- The g protocol is used by the UUCP session layer to transmit UUCP commands and
- data files. UUCP commands are simple strings. When using the g protocol, they
- are sent as a sequence of data packets (not short data packets). The last data
- byte of the last packet in the sequence is a null byte, and the string is
- itself terminated by a null byte. Normally only one or two packets are
- required.
- UUCP sends a file by simply sending the data in a sequence of data packets or
- short data packets. At the end of the file a short data packet is sent with a
- logical packet length of zero bytes.
- References
- Holzmann, Gerard J. 1991. Design and Validation of Computer Protocols.
- Englewood Cliffs, NJ: Prentice-Hall.
- Figure 1 UUCP g data-packet header bytes
- DLE K CHECK CHECK CONTROL XOR
- LOW HIGH
-
-
- Listing 1 Various typedefs, macro definitions, variables, and prototypes
-
- /* The boolean type. */
- typedef int boolean;
- #define TRUE (1)
- #define FALSE (0)
-
- /* Names for the bytes in the frame header. */
- #define IFRAME_DLE (0)
- #define IFRAME_K (1)
- #define IFRAME_CHECKLOW (2)
- #define IFRAME_CHECKHIGH (3)
- #define IFRAME_CONTROL (4)
- #define IFRAME_XOR (5)
-
- /* Length of the frame header. */
- #define CFRAMELEN (6)
-
- /* Macros to break apart the control bytes. */
- #define CONTROL_TT(b) ((int)(((b) >> 6) & 03))
- #define CONTROL_XXX(b) ((int)(((b) >> 3) & 07))
- #define CONTROL_YYY(b) ((int)((b) & 07))
-
- /* DLE value. */
- #define DLE ('\020')
-
- /* Get the length of a packet given a pointer to the header. */
- #define CPACKLEN(z) (1 << ((z)[IFRAME_K] + 4))
-
- /* <k> field value for a control message. */
- #define KCONTROL (9)
-
- /* Get the next sequence number given a sequence number. */
- #define INEXTSEQ(i) ((i + 1) & 07)
-
- /* Compute i1 - i2 modulo 8. */
- #define CSEQDIFF(i1, i2) (((i1) + 8 - (i2)) & 07)
-
- /* Packet types. These are from the TT field. */
- #define CONTROL (0)
- #define ALTCHAN (1)
- #define DATA (2)
- #define SHORTDATA (3)
-
- /* Control types. These are from the XXX field if the type
- (tt field) is CONTROL. */
- #define CLOSE (1)
- #define RJ (2)
- #define SRJ (3)
- #define RR (4)
- #define INITC (5)
- #define INITB (6)
- #define INITA (7)
-
- /* Maximum amount of data in a single packet. */
- #define CMAXDATAINDEX (8)
- #define CMAXDATA (1 << (CMAXDATAINDEX + 4))
-
-
- /* Maximum window size. */
- #define CMAXWINDOW (7)
-
- /* The timeout to use when waiting for a packet.
- Protocol parameter ''timeout''. */
- #define CTIMEOUT (10)
-
- /* The number of times to retry waiting for a packet.
- Each time the timeout fails we send a copy of our
- last data packet or a reject message for the packet
- we expect from the other side, depending on whether
- we have any unacknowledged data. This is the number
- of times we try doing that and then waiting again.
- Protocol parameter ''retries''. */
- #define CRETRIES (6)
-
- /* Next sequence number to send. */
- int iGsendseq = 1;
-
- /* Last sequence number that has been acked. */
- int iGremote_ack = 0;
-
- /* Last sequence number to be retransmitted. */
- int iGretransmit_seq = -1;
-
- /* Last sequence number we have received. */
- static int iGrecseq;
-
- /* Remote segment size (set during protocol initialization).
- This is one less than the value in a packet header. */
- int iGremote_segsize;
-
- /* Remote packet size (set based on iGremote_segsize). */
- int iGremote_packsize;
-
- /* Remote window size (set during handshake). */
- static int iGremote_winsize;
-
- /* Timeout (seconds) for receiving a data packet.
- Protocol parameter ''timeout''. */
- static int cGtimeout = CTIMEOUT;
-
- /* Maximum number of timeouts when receiving a data packet
- or acknowledgement. Protocol parameter ''retries''. */
- static int cGretries = CRETRIES;
-
- /* Get a packet. This is called to wait for a packet to come
- in when there is nothing to send. If freturncontrol is
- TRUE, this will return after getting any control packet.
- Otherwise, it will continue to receive packets until a
- complete file or a complete command has been received.
- The timeout and the number of retriesare arguments.
- The function returns FALSE if an error occurs or if
- cretries timeouts of ctimeout seconds were exceeded. */
- boolean fgwait_for_packet (boolean freturncontrol,
- int ctimeout, int cretries);
-
- /* Send data to the other system. If the fdoread argument
- is true, this will also read data into abPrecbuf; fdoread
-
- is passes as TRUE if the protocol expects data to be
- coming back, to make sure the input buffer does not fill
- up. Returns FALSE on error. */
- boolean fsend_data (const char *zsend, int csend,
- boolean fdoread);
- /* End of File */
-
-
- Listing 2 The fgsenddata function sends out a data packet.
- boolean
- fgsenddata (zdata, cdata)
- char *zdata;
- int cdata;
- {
- char *z;
- int itt, iseg, csize;
- unsigned short icheck;
-
- itt = DATA;
- csize = iGremote_packsize;
- iseg = iGremote_segsize + 1;
-
- if (cdata < iGremote_packsize)
- {
- /* If the remote packet size is larger than 64,
- the default, we can assume they can handle a
- smaller packet as well, which will be more
- efficient to send. */
- if (iGremote_packsize > 64)
- {
- /* The packet size is 1 << (iseg + 4). */
- iseg = 1;
- csize = 32;
- while (csize < cdata)
- {
- csize <<= 1;
- ++iseg;
- }
- }
-
- if (csize != cdata)
- {
- int cshort;
-
- /* We have to move the data within the packet,
- unfortunately. It only happens once per
- file transfer. It would also be nice if we
- computed the checksum as we move. We zero
- out the unused bytes.
- */
- itt = SHORTDATA;
- cshort = csize - cdata;
- if (cshort <= 127)
- {
- memmove (zdata + 1, zdata, cdata);
- zdata[0] = (char) cshort;
- memset (zdata + cdata + 1, 0, cshort - 1);
- }
- else
-
- {
- memmove (zdata + 2, zdata, cdata);
- zdata[0] = (char) (0x80 (cshort & 0x7f));
- zdata[1] = (char) (cshort >> 7);
- memset (zdata + cdata + 2, 0, cshort - 2);
- }
- }
- }
-
- z = zdata - CFRAMELEN;
-
- z[IFRAME_DLE] = DLE;
- z[IFRAME_K] = (char) iseg;
-
- icheck = (unsigned short) igchecksum (zdata, csize);
-
- /* Wait until there is room in the receiver's window
- for us to send the packet. We do this now so that
- we send the correct value for the last packet
- received. Note that if iGsendseq == iGremote_ack,
- this means that the sequence numbers are actually
- 8 apart, since the packet could not have been
- acknowledged before it was sent; this can happen
- when the window size is 7. */
- while (iGsendseq == iGremote_ack
- CSEQDIFF (iGsendseq, iGremote_ack) > iGremote_winsize)
- {
- if (! fgwait_for_packet (TRUE, cGtimeout,
- cGretries))
- return FALSE;
- }
-
- /* Ack all packets up to the next one, since the UUCP
- protocol requires that all packets be acked in
- order. */
- while (CSEQDIFF (iGrecseq, iGlocal_ack) > 1)
- {
- iGlocal_ack = INEXTSEQ (iGlocal_ack);
- if (! fgsend_control (RR, iGlocal_ack))
- return FALSE;
- }
- iGlocal_ack = iGrecseq;
-
- z[IFRAME__CONTROL] = (char) ((itt << 6) (iGsendseq << 3) iGrecseq);
-
- iGsendseq = INEXTSEQ (iGsendseq);
-
- icheck = ((unsigned short)
- ((0xaaaa - (icheck ^ (z[IFRAME_CONTROL] & 0xff))) & 0xffff));
- z[IFRAME_CHECKLOW] = (char) (icheck & 0xff);
-
- z[IFRAME_CHECKHIGH] = (char) (icheck >> 8);
-
- z[IFRAME_XOR] = (char) (z[IFRAME_K] ^ z[IFRAME_CHECKLOW]
- ^ z[IFRAME_CHECKHIGH] ^ z[IFRAME_CONTROL]);
-
- /* If we're waiting for acks of retransmitted
- packets, then don't send this packet yet. The
- other side may not be ready for it yet. Instead,
-
- code infggot_ack will send the outstanding packets
- when an ack is received. */
- if (iGretransmit_seq != -1)
- return TRUE;
-
- return fsend_data (z, CFRAMELEN + csize, TRUE);
- }
-
- /* End of File */
-
-
- Listing 3 The igchecksum function
- int
- igchecksum (z, c)
- register const char *z;
- register int c;
- {
- register unsigned int ichk1, ichk2;
-
- ichk1 = 0xffff;
- ichk2 = 0;
-
- do
- {
- register unsigned int b;
-
- /* Rotate ichk1 left. */
- if ((ichk1 & 0x8000) == 0)
- ichk1 <<= 1;
- else
- {
- ichk1 <<= 1;
- ++ichk1;
- }
-
- /* Add the next character to ichk1. */
- b = *z++ & 0xff;
- ichk1 += b;
-
- /* Add ichk1 xor the character position in
- the buffer counting from the back to ichk2. */
- ichk2 += ichk1 ^ c;
-
- /* If the character was zero, or adding it to
- ichk1 caused an overflow, xor ichk2 to ichk1. */
- if (b == 0 (ichk1 & 0xffff) < b)
- ichk1 ^= ichk2;
- }
- while (--c > 0);
-
- return ichk1 & 0xffff;
- }
-
- /* End of File */
-
-
- Listing 4 This listing shows the code or sending a
- static boolean
- fgsend control (ixxx, iyyy)
-
- int ixxx;
- int iyyy;
- {
- char ab[CFRAMELEN];
- int ict1;
- unsigned short icheck;
-
- ab[IFRAME_DLE] = DLE;
- ab[IFRAME_K] = KCONTROL;
-
- ict1 = (CONTROL << 6) (ixxx << 3) iyyy;
- icheck = (unsigned short) (0xaaaa - ict1);
- ab[IFRAME_CHECKLOW] = (char) (icheck & 0xff);
- ab[IFRAME_CHECKHIGH] = (char) (icheck >> 8);
-
- ab[IFRAME_CONTROL] = (char) ict1;
-
- ab[IFRAME_XOR] = (char) (ab[IFRAME_K]
- ^ ab[IFRAME_CHECKLOW]
- ^ ab[IFRAME_CHECKHIGH]
- ^ ab[IFRAME_CONTROL]);
-
- return fsend_data (ab, CFRAMELEN, TRUE);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- An Essential String Function Library
-
-
- William Smith
-
-
- William Smith is the engineering manager at Montana Software, a software
- development company specializing in custom applications for MS-DOS and
- Windows. You may contact him by mail at P.O. Box 663, Bozeman, MT 59771-0663.
-
-
- The include file <string.h> in the Standard C library defines 22 functions for
- manipulating character strings. Seventeen of these functions begin with the
- prefix str and another five begin with the prefix mem. The functions that
- begin with the prefix str work on null-terminated strings. They accomplish
- such critical tasks as finding the length of a string, concatenating strings,
- or comparing strings, to name just a few of the tasks. The functions that
- begin with mem work on any buffer of memory. These functions do not interpret
- a null character as a terminator.
- At first, the functions in <string.h> appear to offer a broad smorgasbord of
- functionality. I originally expected them to satisfy most string-processing
- requirements I would encounter. In actuality, I repeatedly encountered
- situations where what I needed to do I could not accomplish with a single call
- to one of the Standard C functions. But they are good building blocks. I found
- myself using and grouping them to accomplish what I really needed.
- Most of the string processing tasks I am faced with center around manipulating
- text data for input and output. I nearly always have to parse and convert some
- text script file or user input into a data structure and vice versa. Over
- time, in just about every program I wrote, the specific needs for text
- processing started to repeat themselves. I frequently needed to delete and
- insert strings, or trim leading and trailing tabs and spaces from text. These
- and many other requirements were common to nearly every project I would work
- on. After almost ten years of programing in C, a group of about an additional
- 20 functions has precipitated and become a crucial part of my C function
- library. I am going to share with you the most recent incarnation of my bare
- bones but essential string function library. These functions complement the
- Standard C functions defined in <string. h>
-
-
- Dynamic Memory Issues
-
-
- When writing string functions, you can go in a couple of different directions
- with regards to dynamic memory. You can dynamically allocate memory to store
- the string that results from a function's execution of a task. This approach
- allows you to avoid modification of the original string passed to the
- function. For example, when performing search and replace, you can use dynamic
- memory to store the string that contains the modifications. The function can
- then leave the original string unchanged. However, when using this approach,
- the programmer must keep track of memory allocation and make sure to release
- the allocated memory eventually. This can be a challenge in certain
- situations. Use of dynamic memory may be more suitable in C++. C++ is better
- organized to provide object creation and deletion. This helps with dynamic
- memory management and relieves some of the burden on the programmer.
- Since the functions presented here are pure Standard C, I choose to avoid
- dynamic memory. In fact, I also choose to avoid creating buffers on the stack
- as a scratch or work space. Some of the editing functions require a temporary
- work space, but I get around this by using the memmove function defined in
- <string.h>. memmove provides safe memory copying of overlapping buffers. You
- would need a temporary copy of the source buffer to do it yourself. Although
- convenient, using memmove has the disadvantage of being more costly with
- respect to processor time. This varies from system to system, but generally
- there are usually faster ways to accomplish the same task as memmove.
- Modifying the functions to avoid the use of memmove can wring a bit more
- performance and efficiency out of them.
- In the future, it might be worthwhile to create object wrappers in C++ for
- these string functions. For now, I will leave them in standard C. This means
- that all the functions assume that the strings that you pass to them are
- NULL-terminated. The functions also assume that the strings are pointers to
- memory areas that are large enough to accommodate the resulting string
- generated by the function. The burden of avoiding buffer overflows rests on
- the programer.
-
-
- Implementation
-
-
- I break the string functions up into two categories. I group functions for
- extracting or finding a substring in a string into the file named STR_NGET.C
- (Listing 1 and Listing 2). The second group, in STR_EDIT.C (Listing 3 and
- Listing 4), contains functions that I use for editing strings.
-
-
- Functions for Getting Substrings
-
-
- STR_NGET. C contains the functions str_nleft, str_nmid, str_nright and
- str_rstr. The first three functions extract a specified number of characters
- from a string. These functions modify the string itself by moving the desired
- characters into it. str_nleft extracts the n left- most characters. str_nright
- extracts the n right-most characters. str_nmid extracts n characters from a
- specified position.
- str_rstr resembles the function strstr defined in <string.h>. But instead of
- finding the first occurrence of a substring, str_rstr finds the last
- occurrence of the substring. The relationship between strstr and str_rstr is
- analogous to the relationship between strchr and strrchr. I have seen a
- function called strrstr in some libraries that come with commercial compilers.
- It is equivalent to my function str_rstr. It is not a part of the standard.
-
-
- Functions for Editing Strings
-
-
- All the 13 functions in the file STR_EDIT.C do some type of modification or
- editing to a string. The functionality ranges from the simple padding of
- strings to a fixed length for justification to complete search and replace.
- str_center, str_ljust, and str_rjust justify strings. These functions first
- trim leading and trailing spaces and tabs from a string. They then move the
- string so it is either centered, left-justified, or right-justified within a
- specified length.
- The trimming functions, str_ltrim, str_rtrim and str_trim execute the trimming
- tasks required by the justification functions mentioned above. These functions
- trim all characters from the end or ends of a string that match a list of
- characters to trim.
- The function str_delete removes a specified number of characters from a string
- starting at a designated location within the string. The function str_insert
- inserts a string into a string at a designated location in the string. The
- function str_rplc uses both str_delete and str_insert implement a search and
- replace capability. str_mrplc does search and replace for all matches.
- str_rplc just replaces the first match. The function str_repeat builds a
- string of desired length by repeating a string.
- The function str_vcat is a variable-argument version of the Standard C
- function strcat. This function concatenates a list of strings. The last string
- or parameter passed to str_vcat must be a null-pointer. str_ocat is a version
- of strcat that can handle overlapping strings. An example of overlapping
- strings would be a single string with multiple pointers to different locations
- in the string. Depending on the compiler vendor, sometimes strcat will work
- with overlapping strings, sometimes it will not. For safety and constancy I
- created the function str_ocat. str_ocat is just wrapper for memmove.
-
-
- Conclusions
-
-
- Nearly every major program I have written has involved text processing in some
- form. The Standard C library provides a useful, but shallow group of
- string-manipulation functions. Over time and out of need, I have come up with
- the group of string functions presented here. These functions build upon the
- standard library functions and provide the functionality that I have found
- important in practice.
- There are an endless number of more functions you can invent. And you can
- probably find more efficient ways to implement the functions demonstrated
- here. Nevertheless, these are the functions I have found useful and essential
- in my work with C.
-
- Listing 1 STR_NGET.C - functions for extracting or finding a substring
- /*****************************************************
- File Name: STR_NGET.C
- Description: Library of functions for geting
- substrings in a string
- Global Function List: str_nleft
-
- str_nmid
- str_nright
- str_rstr
- Portability: Standard C
- *****************************************************/
-
- #include <stdlib.h>
- #include <string.h>
- #include <str_nget.h>
-
- /*****************************************************
- Name: str_nleft
- Expanded Name: Get Left N Characters
- Parameters: Str - string to get left characters
- Num - number of characters to get
- Return: Str
- Description: Get Num leftmost charcters in Str.
- Modifies Str.
- *****************************************************/
- char *str_nleft( char *Str, size_t Num )
- {
-
- if ( Num < strlen( Str ))
- {
- Str[Num] = '\0';
- }
-
- return ( Str );
-
- } /* function str_nleft */
-
- /*****************************************************
- Name: str_nmid
- Expanded Name: Get Middle N Characters
- Parameters: Str - string to get substring in
- Pos - index into Str of start of midstr
- Num - count of charcters to get
- Return: Str
- Description: Get Num chars from middle of string.
- *****************************************************/
- char *str_nmid( char *Str, size_t Pos, size_t Num )
- {
-
- char *Mid;
- size_t Len = strlen( Str );
-
- if ( Pos >= Len )
- {
- /* Outside of string */
- *Str = '\0';
- return ( Str );
- }
-
- /* Adjust count if it extends outside of string */
- if ( Pos + Num > Len )
- {
- Num = Len - Pos;
- }
-
-
- Mid = &Str[Pos];
- memmove( (void *)Str, (void *)Mid, Num );
- Str[Num] = '\0';
-
- return ( Str );
-
- } /* function str_nmid */
-
- /*****************************************************
- Name: str_nright
- Expanded Name: Get Right N Characters
- Parameters: Str - string to get right characters
- Num - number of characters to get
- Return: Str
- Description: Get Num righmost characters in Str.
- Modifies Str.
- *****************************************************/
- char *str_nright( char *Str, size_t Num )
- {
-
- size_t Len = strlen( Str );
-
- return ( str_nmid( Str,
- ( Num > Len ? 0 : Len - Num ),
- min( Num, Len ) ) );
-
- } /* function str_nright */
-
- /*****************************************************
- Name: str_rstr
- Expanded Name: String Right (Reverse) Search
- Parameters: Str - string to search
- Find - string to search for
- Return: Pointer to last occurrence of substring
- Find in Str or NULL if not found
- Description: Searches for last occurrence of sub
- string Find within Str.
- *****************************************************/
- char *str_rstr( char *Str, char *Find )
- {
-
- char *StrResult = NULL, *StrWork = Str;
-
- while ( ( StrWork =
- strstr( StrWork, Find ) ) != NULL )
- {
- StrResult = StrWork;
- StrWork++;
- }
-
- return ( StrResult );
-
- } /* function str_rstr */
-
- /* End of File */
-
-
- Listing 2 STR_NGET.H - contains function prototypes for Listing 1
- /*****************************************************
-
- File Name: STR_NGET.H
- Description: Include file for STR_NGET.C
- *****************************************************/
-
- #if !defined ( STR_NGET_DEFINED )
-
- #define STR_NGET_DEFINED
-
- char *str_nleft( char *Str, size_t Num );
- char *str_nmid( char *Str, size_t Pos, size_t Num );
- char *str_nright( char *Str, size_t Num );
- char *str_rstr( char *Str, char *Find );
-
- #endif
-
- /* End of File */
-
-
- Listing 3 STR_EDIT.C - functions for editing strings
- /****************************************************
- File Name: STR_EDIT.C
- Description: Library of functions for editing
- strings
- Global Function List: str_center
- str_delete
- str_insert
- str_ljust
- str_ltrim
- str_mrplc
- str_ocat
- str_repeat
- str_rjust
- str_rplc
- str_rtrim
- str_trim
- str_vcat
- Portability: Standard C
- ****************************************************/
-
- /* Standard C */
- #include <stdarg.h>
- #include <stdlib.h>
- #include <string.h>
-
- /* Own */
- #include <str_edit.h>
-
- /*****************************************************
- Name: str_center
- Parameters: Str - string to center
- Len - num of chars of centered string
- Return: Str
- Description: Centers a string in a desired length
- by removing tabs and adding spaces
- to both sides of string.
- *****************************************************/
- char *str_center( char *Str, size_t Len )
- {
-
-
- size_t LenOrg;
-
- /* Trim spaces and tabs off the string */
- str_trim( Str, "\t" );
-
- LenOrg = strlen( Str );
- if ( Len <= LenOrg )
- {
- /* The desired string length is shorter than
- ** the original so return */
- return ( Str );
- }
-
- /* Add the spaces to each side */
- str_rjust( Str,( LenOrg + Len ) / 2 );
- str_ljust( Str, Len );
-
- return ( Str );
-
- } /* function str_center */
-
- /*****************************************************
- Name: str_delete
- Parameters: Str - string to edit
- Pos - index to start deleting chars at
- Num - number of charcters to delete
- Return: Str
- Description: Modifies Str, by deleting Num chars
- beginning at Pos.
- *****************************************************/
- char *str_delete( char *Str, char *Pos, size_t Num )
- {
-
- size_t Len = strlen( Str );
-
- if ( ( Pos >= &Str[Len] ) ( Num == 0 ) )
- {
- /* Outside string or no chars to delete */
- return ( Str );
- }
-
- Num = min( Num, strlen( Pos ) );
- if ( Num )
- {
- /* Delete characters by contactenating */
- memmove( Pos, &Pos[Num],
- strlen( &Pos[Num] ) + 1 );
- }
-
- return ( Str );
-
- } /* function str_delete */
-
- /*****************************************************
- Name: str_insert
- Parameters: Str - string to edit
- Pos - pointer to location withing Str
- Insrt - string to insert into Str
- Return: Str
-
- Description: Inserts a string Insrt into Str at Pos
- *****************************************************/
- char *str_insert( char *Str, char *Pos, char *Insrt )
- {
-
- size_t Len = strlen( Insrt );
- char *Tmp = &Pos[Len];
-
- memmove( Tmp, Pos, strlen( Pos ) + 1 );
- memmove( Pos, Insrt, Len );
-
- return ( Str );
-
- } /* function str_insert */
-
- /*****************************************************
- Name: str_ljust
- Parameters: Str - string to left justify
- Len - length of string
- Return: Str
- Description: Pads right end of Str with spaces to
- left justify Str to a new length Len.
- *****************************************************/
- char *str_ljust( char *Str, size_t Len )
- {
-
- size_t LenOrg = strlen( Str );
- char *StrEnd = &Str[LenOrg];
-
- Len = max( Len, LenOrg ) - LenOrg;
- StrEnd[Len] = '\0';
-
- while ( Len )
- {
- Len--;
- StrEnd[Len] = ' ';
- }
-
- return ( Str );
-
- } /* function str_ljust */
-
- /*****************************************************
- Name: str_ltrim
- Parameters: Str - string to trim
- Trim - string containing chars to trim
- Return: Str
- Description: Delete characters from the left end of
- Str that are contained in Trim
- *****************************************************/
- char *str_ltrim( char *Str, char *Trim )
- {
-
- size_t Num = strspn( Str, Trim );
-
- str_delete( Str, Str, Num );
-
- return ( Str );
-
-
- } /* function str_ltrim */
-
- /*****************************************************
- Name: str_mrplc
- Expanded Name: String Multiple Search and Replace
- Parameters: Str - string to edit
- Find - search string
- Rplc - replacement string
- Return: Str
- Description: Multiple search and replace. All
- occurrences of Find within Str are
- replaced with Rplc.
- *****************************************************/
- char *str_mrplc( char *Str, char *Find, char *Rplc )
- {
-
- char *StrWork = Str;
- size_t LenRplc = strlen( Rplc );
- while ( ( StrWork =
- strstr( StrWork, Find ) ) ! = NULL )
- {
- str_delete( Str, StrWork, strlen( Find ) );
- str_insert( Str, StrWork, Rplc );
- StrWork += LenRplc;
- }
-
- return ( Str );
-
- } /* function str_mrplc */
-
- /*****************************************************
- Name: str_ocat
- Expanded Name: Concatenate overlapped strings
- Parameters: Dest - destination string
- Str - string to concat
- Return: Dest
- Description: Behaves the same as strcat in string.h.
- This version will work for strings that
- overlap in memory.
- *****************************************************/
- char *str_ocat( char *Dest, char *Str )
- {
-
- return ( (char *)memmove(
- (void *)&Dest[strlen( Dest )],
- (void *)Str, strlen( Str) + 1 ) );
-
- } /* function str_ocat */
-
- /*****************************************************
- Name: str_repeat
- Parameters: Str - string buffer to load
- Rpt - repetition string
- Num - number of repetitions
- Return: Str
- Description: Builds a string of length Num, by
- repeating a substring Rpt.
- *****************************************************/
- char *str_repeat( char *Str, char *Rpt, size_t Num )
-
- {
-
- size_t Len = strlen( Rpt );
-
- if ( Len == 1 )
- {
- /* The string is only one character */
- memset( Str, *Rpt, Num );
- }
- else
- {
-
- size_t i, j;
-
- /* Build Str with repetitions of Rpt */
- for ( i = 0, j = 0; i < Num; i++ )
- {
- Str[i] = Rpt[j++];
- j %= Len;
- } /* for i */
-
- } /* else */
-
- Str[Num] = '\0';
-
- return ( Str );
-
- } /* function str_repeat */
-
- /**************************************************
- Name: str_rplc
- Expanded Name: String Search and Replace
- Parameters: Str - string to edit
- Find - search string
- Rplc - replacement string
- Return: Str
- Description: Search and replace. First
- occurrences of Find within Str is
- replaced with Rplc.
- ***************************************************/
- char *str_rplc( char *Str, char *Find, char *Rplc )
- {
-
- char *StrWork = strstr( Str, Find );
-
- if ( StrWork )
- {
- str_delete( Str, StrWork, strlen( Find ) );
- str_insert( Str, StrWork, Rplc );
- }
-
- return ( Str );
-
- } /* function str_rplc */
-
- /*****************************************************
- Name: str_rjust
- Expanded Name: String Right Justify
- Parameters: Str - string to edit
-
- Len - new length of string
- Return: Str
- Description: Pads the left end of string so that it
- is right justified to a total length of
- Len.
- *****************************************************/
- char *str_rjust( char *Str, size_t Len )
- {
-
- size_t LenOrg = strlen( Str );
-
- Len = max( LenOrg, Len ) - LenOrg;
-
- if ( Len )
- {
- memmove( &Str[Len], Str, LenOrg + 1 );
- while ( Len )
- {
- Len--;
- Str[Len] = ' ';
- }
- }
-
- return ( Str );
-
- } /* function str_rjust */
-
- /*****************************************************
- Name: str_rtrim
- Parameters: Str - string to trim
- Trim - string of characters to trim
- Return: Str
- Description: Delete characters from the right end
- of Str that are contained in Trim
- *****************************************************/
- char *str_rtrim( char *Str, char *Trim )
- {
-
- char *StrWork = &Str[strlen( Str) - 1];
-
- /* Look for last character in string not being
- ** in trim string */
- while ( (Str != StrWork ) &&
- ( strspn( StrWork, Trim ) != 0 ) )
- {
- *StrWork-- = '\0';
- }
-
- return ( Str );
-
- } /* function str_rtrim */
-
- /*****************************************************
- Name: str_trim
- Parameters: Str - string to trim
- Trim - string of characters to trim
- Return: Str
- Description: Delete characters from both ends of Str
- *****************************************************/
-
- char *str_trim( char *Str, char *Trim )
- {
-
- str_ltrim( Str, Trim );
- str_rtrim( Str, Trim );
-
- return ( Str );
-
- } /* function str_trim */
-
- /*****************************************************
- Name: str_vcat
- Parameters: Dest - destination string
- Strl - required first string
- Return: Dest
- Description: Variable argument version of strcat.
- Concatenates a list of strings into
- a destination string. The last
- argument must be a NULL pointer.
- *****************************************************/
- char *str_vcat( char *Dest, char *Strl, .... )
- {
-
- va_list VarArgList;
- char *Str;
-
- /* Initialize variable arguments */
- va_start( VarArgList, Dest );
-
- /* Get first var arg string */
- Str = va_arg( VarArgList, char * );
-
- strcat( Dest, Strl );
- while ( Str != NULL )
- {
-
- /* Loop though all the arguments */
- strcat( Dest, Str );
-
- /* Get the next string */
- Str = va_arg( VarArgList, char * );
-
- }
-
- /* Clean up */
- va_end( VarArgList );
-
- return ( Dest );
-
- } /* function str_vcat */
-
- /* End of File */
-
-
- Listing 4 STR_EDIT.H - contains function prototypes for Listing 3
- /******************************************************
- File Name: STR_EDIT.H (Listing 4)
- Description: Include file for STR_EDIT.C
- ******************************************************/
-
-
- #if !defined( STR_EDIT_DEFINED )
-
- #define STR_EDIT_DEFINED
-
- char *str_center( char *Str, size t_Len );
- char *str_delete( char *Str, char *Pos, size_t Num );
- char *str_insert( char *Str, char *Pos, char *Insrt );
- char *str_ljust( char *Str, size_t Len );
- char *str_ltrim( char *Str, char *Trim );
- char *str_mrplc( char *Str, char *Find, char *Rplc );
- char *str_ocat( char *Dest, char *Str );
- char *str_repeat( char *Str, char *Rpt, size_t Num );
- char *str_rjust( char *Str, size_t Len );
- char *str_rplc( char *Str, char *Find, char *Rplc );
- char *str_rtrim( char *Str, char *Trim );
- char *str_trim( char *Str, char *Trim );
- char *str_vcat( char *Dest, char *Strl, ... );
-
- #endif
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Hiding ISAM Function Libraries with OOP
-
-
- Thomas Murphy
-
-
- Thomas J. Murphy has a bachelor's degree in Physics from St. Ambrose College,
- Davenport, IA, and a Ph.D. in Biophysics from the University of Illinois. He
- has worked in the software-development field for 13 years and has owned his
- own computer-consulting and custom software business for over seven years. His
- major area of expertise is database systems. He can be reached at Computer
- Management Consultants, Ltd, Box 132, RR #10, Oswego, NY 13126.
-
-
- Function libraries each have a unique set of functions, unique lists of
- arguments, and a unique user manual. Quality of documentation ranges from
- excellent to so indecipherable that you must analyze the source code to
- successfully use the product. On a project of any size, project programmers
- have to surmount the learning curve, not only for the application and
- hardware/operating system environment but for the function libraries in use.
- In addition, you may be stuck with your first choice of libraries or face some
- rather nasty problems if you want to switch to another library. Should you
- convert the code for all your past clients so your current staff of
- programmers (even if that is only you) can continue to maintain it? Should you
- keep knowledgeable about every library you've ever used? Or should you just
- stick with your original choice?
- This article presents an example programmer interface to a special-purpose
- function library designed to ease these problems for the case of a B+Tree,
- Indexed Sequential Access Method (ISAM) data-handling function library. The
- interface uses the standard Object-Oriented Programming (OOP) approach to
- protecting the application programmer and the function library from each
- other. It handles all the gory details of dealing with the function library by
- inserting a layer of function calls between the application programmer and the
- library itself. The functions called by the application programmer do not
- depend on the particular library product in use. They depend only on what the
- application programmer wants to do.
- OOP is not a language; it is an approach to programming. The class presented
- in this article is written in C++, but it does not use any of the flashier
- bells and whistles most often talked about when one is discussing C++. There
- is no operator overloading, no inheritance, no polymorphism. All this class
- has is a little bit of data and function hiding and an OOP approach to doing
- business. The code could be rewritten in Standard C by throwing all the
- class-member data into a structure, declaring instances of the structure, and
- passing as an argument the address of the declared structure to what are
- presented here as member functions.
- The examples in this article are for a specific product (CBTREE v3.0 by
- Peacock Systems, Inc.). Without changing the calling parameters or the names
- of the functions called by the application programmer, however, you could
- rewrite the body of these functions for any of the several ISAM function
- libraries I have used, and for many (no doubt) that I have not. The
- application code that uses the class is no longer dependent on what data
- handler is in use. The details of the data handler are hidden from the
- application programmer.
-
-
- What the Class Does
-
-
- Before looking at the code, look at what it was designed to do. Class Isam is
- a programmer interface to a data handler that uses ISAM files. After the
- application programmer/analyst has set up the ISAM files in the application
- (designed the record layouts, decided on index keys, created the files, etc.),
- I want him/her to be as free as possible from the nitty-gritty details of
- working with the data files themselves. The application programmer:
- needs to be able to gain access to the data and index files with a single,
- simple statement. If the files are already opened in another part of the
- program, they should not be opened again; the programmer should just have
- access to them.
- after gaining access to a file, needs to have space automatically allocated
- for the records he/she will be reading from the file. If the programmer is
- going to be dealing with 20 records at once, space must be allocated for 20
- records.
- if modifying, deleting, and adding records, needs to be able to put the new
- set of records in an array and write them. The programmer should not have to
- keep track of which records have been worked on or what has been done to them.
- The programmer should not have to keep track of which keys have to be removed
- and added to the btree indices. In other words, the programmer needs to be
- able to use indices without having to think about them.
- should not have to write programs to re-index files. The class should do most
- of the work. If the programmer had to re-index the file because he/she added
- another index, all the remaining code should not need to be modified.
- should have the freedom to write a function to create a key that manipulates
- the data as desired.
- be able to address the indices by name, instead of an index number.
-
-
- How the Class Does It
-
-
- Listing 1 contains the header file defining class Isam. Notice that one of the
- member data items and one of the member functions is marked as specific to
- CBTREE. The rest of the member data and functions are generic and would be
- present regardless of the library being used. The code for the member
- functions is shown in Listing 2.
- The class contains ten public functions, eight of which are routinely called
- by the application programmer. The destructor, ~Isam, is typically called only
- automatically when the instance of the class goes out of scope. Isam::reindex
- is called to recover from hopefully infrequent disasters. public data consists
- of one string array, rec, which will contain records read from the ISAM file.
-
-
- The Constructor
-
-
- The programmer gains access to an ISAM file by declaring an instance of the
- class, such as
- Isam employeefile ("employee");
- If the programmer will be dealing with more than one employee record at a
- time, such as all the employees in a department (for a small company), the
- maximum number can be specified in the declaration. The constructor, Isam,
- will allocate space for that number, as in
- Isam employeefile ("employee", 20);
- The first time the Isam constructor is called in a program, it calls a static
- function, isam_init, which loads a static array of structures, btparms[], with
- file and index parameters for all files in the application. This information
- is read from a setup file for the application. As it happens, CBTREE already
- uses such a file in its data-file and b-tree creation utilities and,
- optionally, in its runtime library. I found that CBTREE's parameter file,
- btparms.btr, contained almost all the information needed for the Isam class.
- Moreover, CBTREE provided a utility to display and modify these parameters.
- The two parameters missing from the file were kstart, the starting character
- position in the data record for a key, and keygen, the name of an application
- function that will generate a key if specified.
- Rather than re-invent the wheel, I modified the data entry and display utility
- for the file provided by CBTREE to include these two parameters. Because the
- data entry and display utility is proprietary, the modified code is not
- presented here; but modification was completely straightforward. Adding the
- parameters on the end of each record in the file had no ill effects on
- CBTREE's use of the file. If you are not using CBTREE, you may have to create
- your own parameter file.
- In addition to file and index parameters, the btparms array also contains a
- count (int count) of how many instances of class Isam are currently active
- that access each data file. If the count is greater than zero for a particular
- data file, btparms[] contains the file handles for the data and index files
- (datafd and indxfd). Thus, multiple instances of Isam for the same data file
- (in the same program) can share the file handles. The class constructor and
- destructor (Isam and ~Isam) manage all bookkeeping involved.
- The constructor, based on the parameters in btparms[] and on the callers
- specification for the number of records to be handled at a time, allocates all
- required space, thus satisfying the requirements for easy access and
- automatically-allocated space.
-
-
- Read and Write Functions
-
-
- Isam::read and Isam::write work as a pair for reading records to be modified
- and writing them back to the file. You can retrieve a record, modify it, and
- write it back to the file using the get... functions (see the first if
- statement in Isam::write), but the result would be the addition of the new
- version of the record, not the replacement of the old version.
- Isam::read asks the caller for a key (the only required parameter).
- Optionally, the caller can specify how many records are to be read (default is
- zero), what index to use (default is the first one), and where in the rec
- array to start storing records (default is the beginning). The function
- returns the number of records found matching the specified (perhaps partial)
- key, regardless of how many records were specified to be read. Many would have
- set the default number of records to be read at one; but I tend to do a lot of
- "authority checking," reading a cross-reference file to determine whether or
- not there is a record with the key entered by the user in a data entry
- program. In this instance, I don't even care about the record; I just want to
- know whether or not it is there. If you'd like the default to be one, simply
- change the declaration in Listing 1.
- The maximum number of records to be read plus the starting location in the rec
- array must not exceed Isam::elements. (This check is made in the code.) If you
- don't know the number of records to expect and you want all matches, you must
- create an instance of Isam with room for one record, make a test read, and
- create another instance of Isam with room for the number of records found
- (integer returned by Isam::read).
- Isam::read places each record read in public array, rec. It makes a second
- copy in private array, oldrec. Isam::write uses the copy in oldrec to compare
- the original version of the record as read with the new version to be written,
- and to generate values for old keys to be removed from the b-tree(s).
- A record can be added by calling Isam::clear, then placing the new record in
- the array. Isam::rec and calling Isam::write. A record can also be added after
- a failed attempt to read a record using a user-entered value for a key.
- Records can be deleted by reading them using lsam::read, setting the
- appropriate array element of rec to a null string, and calling lsam::write.
- Modifying a record involves reading it using Isam::read, changing the data in
- rec, and calling Isam::write. A typical calling sequence (using non-Isam
- function names now) might look like
- Isam invoice ("invoice");
-
- Isam lineitm ("lineitm", 20);
- while (1) {
- paint_invoice_screen();
- if (!get_invnum (inv_num))
- break;
- invoice.clear();
- lineitm.clear();
- if (invoice.read (inv_num,1)){
- get_inv_flds(invoice.rec[0]);
- lineitm.read(inv_num, 20);
- get_lin_flds(lineitm.rec);
- fill_inv_screen();
- }
- if (inv_edit()){
- package_inv(invoice.rec[0]);
- package_lin(lineitm.rec);
- invoice.write();
- lineitm.write();
- }
- else
- break;
- }
- In this code segment, get_inv_flds extracts the fields from the record,
- get_lin_flds extracts fields from the whole array of line item records, and
- package_inv and package_lin put the (perhaps modified) fields back into the
- records. These functions are still the responsibility of the application
- programmer.
- Application functions get_inv_flds, get_lin_flds, and inv_edit may have their
- own instances of the Isam class (e.g., for data generation from customer and
- product files based on customer and product numbers in the invoice and
- lineitem records).
- Once Isam::read has been called, the class should be cleared (Isam::clear) in
- order to forget what it read if the next write is not related to the records
- read. Notice in Listing 2 that Isam::write calls Isam::clear when its work is
- done.
- All key generation is done in Isam::write. The function generates both keys to
- add to the b-tree(s) and keys to be removed from the b-tree(s). If a function
- is specified (btparms[].keygen), that function is called to generate the key.
- If not, a strnncpy from rec[] or oldrec[] is executed using btparms[].kstart
- and btparms[].keylen (Function strnncpy came in the CBTREE library. It works
- exactly like strncpy except that it null-terminates the copied string). Before
- any keys are added to the database, all characters in the keys are converted
- to upper case. Before any keys are used to retrieve records (in Isam::read or
- Isam::getge), the same case conversion is executed automatically.
- Should an error occur during execution of Isam::write (like disk full),
- Isam::backout is called to reverse any changes to the index or data file made
- for the record before the error occurred. Only the record being operated on at
- the time of the error is backed out.
- Listing 3 shows the source file for the catalog and cataloged programs used to
- generate keys. The application programmer is assumed to have access to this
- file (though not necessarily to the source file shown in Listing 2). In order
- to set up a function to generate keys, the function name must be entered in
- the parameter file (btparms.btr), the function prototype and code must be
- added to Listing 3, and finally the name and address must be added to the
- Catolog[] array in Listing 3. This process is referred to as cataloging the
- function. Listing 3 shows a sample function, descwds, that returns a list of
- words used in a description type field, ready for insertion into the b-tree.
- The application programmer must insure that the strings returned by cataloged
- functions are the correct length. The correct length can be any multiple of
- btparms[].keylen. Multiples larger than one indicate multiple values in the
- b-tree for the same record. Note how these are processed in the key sections
- of Isam::write (Listing 2).
-
-
- Re-Indexing Function
-
-
- Once we've put key generation totally in the hands of Isam::write, it is a
- relatively easy job to create a generic re-indexing function. (See the last
- function in Listing 2.) Listing 4 shows a sample program that uses this
- function. Note in Listing 4 that, not only are the parent ("invoice") and
- child ("line item") files re-indexed, but all orphan line items (lineitem
- records with an inv_num that corresponds to no record in the parent file) are
- eliminated.
-
-
- Index Keys
-
-
- For performance considerations, the read function and the getfirst and getnext
- functions described above require an integer argument specifying what index to
- use. Some addition and deletion of indices can take place for a file without
- the requirement for change to all code that uses the file. Thus, the
- programmer may not know what the current or future value for the integer
- corresponding to a particular index is. The Isam::keynum function returns the
- integer corresponding to the argument btname. Note in Listing 2 that I've
- chosen to make the name comparison case insensitive (stricmp).
- It takes time to compare a specified name with the list of indices (stored in
- Isam::btnames). Isam::keynum improves performance because it is typically
- called once and the returned integer used several times in an application
- function.
- If the function fails to find the specified btname, there is obviously a
- problem. The function executes a fatal-error exit (eprintf). Conceivably,
- however, the function could be called in response to entry of an index name
- specified by an end-user. If this type of interaction with the end-user is
- included in the code, the function should be written to return a negative
- integer to indicate failure rather than fatally exiting.
-
-
- Report-Creation Functions
-
-
- The functions, Isam::getfirst, lsam::getge, and Isam::getnext are designed for
- application procedures that go through the file in the order of an index
- (reports and data display programs). Isam::getfirst puts into Isam::rec[0] the
- first record in the file (as viewed through the specified index). Isam::getge
- puts into Isam:: rec[0] the first record with a key greater than or equal to
- the specified key. Isam::getnext gets the record following the one read by the
- last call to a function starting with get. These functions are the bare-bones
- minimum for putting together reasonable reports. All of the ISAM data-handling
- libraries have more functions of this type (getlast, getprevious, etc.). Add
- them to the class if you use them. A typical code segment using the get
- functions might look like
- Isam inv("invoice");
- int idx=inv.keynum("DATE_INVNUM");
-
- inv.getge(idx, begin_date);
- get_inv_flds(inv.rec[0]);
- print_inv_summary_header();
- while (inv_date <= end_date) {
- print_inv_summary_line();
- if (!inv.getnext (idx))
- break;
-
- get_inv_flds(inv.rec [0]);
- }
-
-
- Utility Functions
-
-
- There are three non-class-member functions used and shown in Listing 2.
- Function nospace removes all spaces from a character string. Function ToUpper
- converts all characters in a string to upper case. (It's a big version of
- toupper.) Function eprintf, as it is shown in Listing 2, works like members of
- the printf family, except that, after printing the error message, it exits the
- program. My own version of eprintf uses the window class in my toolbox to
- print the error message in a bright red window, sound an error buzzer, and
- wait for the user to write down the error message and press a key before
- exiting.
-
-
- Conclusion
-
-
- The Isam class presented in this article is an example of how OOP can be used
- to set up a function-library interface. With the interface, application code
- can be independent of the function library. More importantly, with the
- interface, a function library written by somebody else can work the way you
- decide it should work.
- Using CBTREE with C++
- The CBTREE function library was developed in standard C. In order to use it
- with C++, all CBTREE headers must be included using the extern "C"
- modification (see Listing 1).
- The code presented in this article was compiled using Borland's C/C++ 3.1
- compiler. In my hands, the CBTREE library was originally compiled using
- Borland's Turbo C 2.0. While preparing this article, I retrieved the original
- distribution disks and recompiled the CBTREE library programs using Borland's
- C/C++ 3.1 compiler rebuilt the library. I recompiled and linked an application
- using the code presented here using the newly rebuilt library. Aside from a
- small reduction in the size of the library, I saw no change.
- To successfully compile and link my programs, I had to make one change to the
- original CBTREE code. In the main header file, CBTREE.H, there is a
- superfluous declaration for function lockunlk (line 142) that does not include
- the argument list prototype. In C mode, the Borland compiler ignored this. In
- C++ mode, the compiler interpreted this as an overloaded function declaration
- and refused to continue because of the extern C modification. Removing the
- declaration (I commented mine out) made everybody happy.
-
- Listing 1 isam.h
- ////////////
- // isam.h //
- ////////////
-
- #ifndef isam_h
- #define isam_h
- extern "C" {
- #include <cbtree.h> // CBTREE header
- #include <btfio.h> // CBTREE header
- }
-
- typedef char *(*t_func)(char *);
- typedef int (*rel_func)(char *);
-
- class Isam
- {
- private:
- int elements, fd[2], btr[10], indices, backingout;
- long * loc;
- char ** oldrec, * okey, * nkey, ** inames;
- BTC * btc; // CBTREE specific
- int getxxx (int index, int opt); // CBTREE specific
- void backout (int ele, char op, int index = -1,
- int result = 0);
- public:
- char ** rec;
-
- Isam (const char *datafilename, int e = 1);
- ~Isam ( );
-
- int read (const char *key, int ele_limit = 0,
- int idx = 0, int ele = 0);
- int write ();
- void clear ();
- int getfirst (int index = 0);
- int getnext (int index = 0);
- int getge (char *key, int index = 0);
- int keynum (const char *btname);
- void reindex (rel_func func);
-
- };
-
- char * nospace(const char *arg);
- int eprintf(const char *format, ...);
- char * ToUpper(const char *c);
-
- ///////////////////////////////
- // catalog utility functions //
- ///////////////////////////////
-
- int catalog_number (char *name);
- t_func cataloged_func (int f );
-
- #endif
-
- /* End of File */
-
-
- Listing 2 isam. cpp
- //////////////
- // Isam.cpp //
- //////////////
-
- #include "isam.h"
- #include <fstream.h>
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <stdarg.h>
- #include <ctype.h>
-
- static struct BTParms {
- char name[9], filename[9];
- int keylen, dreclen, maxrecs, indxfd, datafd,
- kstart, count;
- t_func keygen;
- } btparms[40];
-
- static int items = 0;
-
- extern "C" {
- int (*Write)(int,const void *,unsigned) = write;
- int (*Read)(int,void *,unsigned) = read;
- }
- static void isam_init(void);
-
- static void isam_init(void)
- {
- ifstream btr("btparms.btr");
- char nwln, item[81], buf[20], fname[13], fcname[9];
- int i;
-
- if (!btr)
- eprintf("\n\nUnable to open btparms.btr.\n");
-
- for (i = 0; i < 40; i++) {
- btr.get (item, 80);
- if (!strlen(item))
- break;
-
- btr.get(nwln);
- strcpy(btparms[i].name, strtok(item, "^"));
- strtok(NULL, "^");
- strcpy(buf, strtok(NULL, "^"));
- btparms[i].keylen = atoi(buf);
- strtok(NULL, "^");
- strcpy(buf, strtok(NULL, "^"));
- btparms[i] .dreclen = atoi (buf);
- strtok(NULL, "^");
- strcpy(fname, strtok(NULL, "^"));
- strtok(NULL, "^");
- strcpy(buf, strtok(NULL, "^"));
- btparms[i].kstart = atoi(buf);
- strcpy(fcname, nospace(strtok(NULL, "^")));
- strcpy(btparms[i].filename, strtok(fname,"."));
- btparms[i].count = O;
- btparms[i].indxfd = btparms[i].datafd = -1;
- if (strlen(fcname))
- btparms[i].keygen = cataloged_func (
- catalog_number(fcname));
- else
- btparms[i].keygen = (t_func) NULL;
- }
-
- items = i;
- btr.close();
-
- if (!items)
- eprintf("\n\nUnable to initialize isam system.\n");
-
- for (i = items; i < 40; i++) {
- strcpy(btparms[i].name,"");
- strcpy(btparms[i].filename,"");
- btparms[i].keygen = NULL;
- btparms[i].keylen = btparms[i].dreclen =
- btparms[i].count = btparms[i].kstart = 0;
- btparms[i].indxfd = btparms[i].datafd = -1;
- }
- }
-
- Isam::Isam(const char *datafilename, int e)
- {
- int i, j;
- char buf[20];
-
- backingout = 0;
- elements = e;
- if (!items) // this is the first Isam
- isam_init(); // initialize btparms
-
- strcpy(buf, nospace(datafilename));
- indices = 0;
- // find btree params
- for (i = 0; i < items; i++)
- if (!stricmp(buf, btparms[i].filename))
- btr[indices++] = i;
- if (!indices) // couldn't find any, fatal
- eprintf("\n\nCan't find parameters for %s!\n",
- datafilename);
-
- // set up btree interfaces
-
- if( !(btc = new BTC [indices])
- !(inames = new char* [indices])
- !(loc = new long [elements])
- !(oldrec = new char* [elements])
- !(rec = new char* [elements])
- !(okey = new char [btparms[*btr].dreclen])
- !(nkey = new char [btparms[*btr].dreclen]) )
- eprintf("\n\nOut of memory.\n");
- for (i = 0; i < indices; i++) {
- inames[i] = btparms[btr[i]].name;
- btc[i].btvcount = 1;
- if (btrinit(inames[i], btc+i) == ERR)
- eprintf("\n\nCouldn't initialize %s.\n",
- inames[i]);
- btc[i].btmulti = 0; // record locking off
- }
- for (i = 0; i < elements; i++) {
- loc[i] = OL;
- if (!(oldrec[i] = new char
- [btparms[*btr].dreclen + 1])
- !(rec[i] = new char
- [btparms[*btr].dreclen + 1]) )
- eprintf("\n\nOut of memory.\n");
- }
- if (btparms[*btr].count) { // open files if neccesary
- fd[0] = btparms[*btr].datafd;
- fd[1] = btparms[*btr].indxfd;
- }
- else { // yup, it's neccesary
- strcpy(buf, btparms[*btr].filename);
- strcat(buf, ".dat");
- if ((fd[0]:bt_open(buf, 0_RDWR,S_IRDWR)) == ERR)
- eprintf("\n\nCan't open %s.\n", buf);
- strcpy(buf, btparms[*btr].filename);
- strcat(buf, ".idx");
- if ((fd[1]=bt_open(buf, 0_RDWR,S_IRDWR)) == ERR)
- eprintf("\n\nCan't open %s.\n", buf);
- btparms[*btr].datafd = fd[0];
- btparms[*btr].indxfd = fd[1];
- }
-
- (btparms[*btr].count)++; // record that we are here
- clear();
- }
-
- Isam::~Isam()
- {
- int i, j;
- // clean up out mess
- for (i = 0; i < indices; i++)
- btrterm(btc+i);
- for (i = 0; i < elements; i++) {
- delete [] oldrec[i];
- delete [] rec[i];
- }
- delete [] btc;
- delete [] inames;
-
- delete [] loc;
- delete [] oldrec;
- delete [] rec;
- delete [] nkey;
- delete [] okey;
- // if we're last ones out, turn off the lights
- if (!(--(btparms[*btr].count))) {
- close (fd[0]);
- close (fd[1]);
- btparms[*btr].indxfd = -1;
- btparms[*btr].datafd = -1;
- }
- }
-
- int Isam::write ()
- {
- int deltakey, oldl, new1, ele, index, result, len,
- start, notfirst;
- t_func fcn;
- if (*loc < 0) // programmer hasn't followed rules
- eprintf ("No writes after gets!");
- for (ele = 0; ele < elements; ele++) {
- old1 : strlen(oldrec[ele]);
- new1 = strlen( rec[ele]);
- if (!(old1 new1)
- !strcmp(oldrec[ele], rec[ele]))
- continue;
- for (index = 0; index < indices; index++) {
- // generate the old and/or new key
- len = btparms[btr[index]].keylen;
- start = btparms[btr[index]].kstart;
- if ((fcn = btparms[btr[index]].keygen) !=
- (t_func) NULL) {
- if (old1)
- strcpy(okey, fcn(oldrec[ele]));
- if (new1)
- strcpy(nkey, fcn( rec[ele]));
- }
- else {
- if (old1)
- strnncpy(okey, oldrec[ele] + start, len);
- if (new1)
- strnncpy(nkey, rec[ele] + start, len);
- }
- deltakey = (!old1!new1stricmp(okey,nkey));
-
- if (old1 && deltakey) {
- notfirst = 0;
- while (strlen(okey) >= len) {
- strnncpy(btc[index].btkey, ToUpper(okey),
- len);
- btc[index].btoptype = (new1 index
- notfirst++ backingout) ? DELTKY
- : DELETE;
- btc[index].btloc = loc[ele];
- result=cbtree(fd[O], fd[1], btc+index);
- if (result != BTCALLOK)
- backout (ele, 'D', index, result);
- strcpy(okey, okey + len);
-
- }
- }
- if (new1 && deltakey) {
- notfirst = 0;
- while (strlen(nkey) >= len) {
- strnncpy(btc[index].btkey, ToUpper(nkey),
- len);
- btc[index].btoptype = (loc[ele] == OL) ?
- INSERT : ISRTKY;
- btc[index].btloc = loc[ele];
- result = cbtree(fd[0], fd[1], btc+index);
- if (result == BTCALLOK)
- loc[ele] = btc[index].btloc;
- else
- backout(ele, 'I', index, result);
- strcpy(nkey, nkey+ len);
- }
- }
- }
- if (!new1)
- continue;
- if (btseek (fd[0], loc[ele], btc->btdtalen)
- == -1L)
- backout(ele, 'S');
- if(Write(fd[0],rec[ele],(unsigned)btc->btdtalen)
- !=btc->btdtalen)
- backout(ele,'W');
- }
- clear();
- return 0;
- }
-
- void Isam::backout(int ele,char op,int index,int result)
- { //backs out record add/change/delete on error
- char *trec;
- if(!(trec = new char [btparms[*btr].dreclen])
- backingout)// Iquit! Error in error backout!
- eprintf("\n\nBackout error, error type was %c\n",
- backingout);
- if (index>=0)
- indices = index + 1; // those beyond index are ok
- backingout = op;
- elements = 1;
- strcpy(trec,rec[ele]);
- strcpy(rec[0],oldrec[ele]);
- strcpy(oldrec[0],trec);
- loc[0] = loc[ele];
- write();
- switch(backingout) {
- case 'I':
- eprintf(
- "\n\nINSERT error, element %d, index %s, result %d\n",
- ele, inames[index],result);
- case 'D':
- eprintf(
- "\n\nDELETE error, element %d, index %s, result %d\n",
- ele, inames[index],result);
- case 'S':
- eprintf ("\n\nSeek error, element %d\n", ele);
-
- case 'W':
- eprintf ("\n\nWrite error, element %d\n", ele);
- }
- }
-
- int Isam::read(const char *key, int ele_limit, int idx,
- int ele)
- {
- int i = 0, j = 0;
-
- if ((ele_limit + ele) > elements)
- eprintf("\n\nNot enough elements!\n");
- free_svkey(btc+idx);
- btc[idx].btoptype = GETALL;
- strcpy(btc[idx].btkey, ToUpper(key));
- btc[idx].btloc = OL;
- while (cbtree(fd[0], fd[1], btc + idx) == BTCALLOK) {
- while (btc[idx].btrecnum[i-j] != OL) {
- if (i < ele_limit) {
- loc[i+ele] = bte[idx].btrecnum[i-j];
- btseek(fd[0], loc[i+ele], btc[idx].btdtalen);
- Read (fd[0], rec[i+ele], btc[idx].btdtalen);
- }
- i++;
- if (!((i-j) < btc[idx].btmax))
- break;
- }
- if ((i-j) < btc[idx].btmax)
- break;
- j += btc[idx].btmax;
- }
- for (j = 0; j < ele_limit;j++)
- strcpy(oldrec[j], rec[j]);
- return i;
- }
-
- int Isam::getfirst(int index)
- {
- return getxxx (index, GETFRST);
- }
-
- int Isam::getnext(int index)
- {
- return getxxx(index, GETNXT);
- }
-
- int Isam::getge (char *key, int index)
- {
- *loc = -1;
- btc[index].btoptype = GETGE;
- strcpy(btc[index].btkey,ToUpper(key));
- if (!(cbtree(fd[0], fd[1], btc + index) == BTCALLOK))
- return 0;
- btseek(fd[0], btc[index].btloc, btc[index].btdtalen);
- Read (fd[0], rec[0], btc[index].btdtalen);
- return 1;
- }
-
- int Isam::getxxx(int index, int opt)
-
- {
- *loc = -1;
- btc[index].btoptype = opt;
- if (!(cbtree(fd[0], fd[1], btc + index) == BTCALLOK))
- return 0;
- btseek(fd[0], btc[index].btloc, btc[index].btdtalen);
- Read (fd[0], rec[0], btc[index].btdtalen);
- return 1;
- }
-
- void Isam::clear()
- {
- int i;
- for (i = 0; i < elements; i++) {
- loc[i] = OL;
- memset (oldrec[i], '\0', btparms[*btr].dreclen +
- 1);
- memset ( rec[i], '\0', btparms[*btr].dreclen + 1);
- }
- }
-
- int Isam::keynum (const char *btname)
- {
- int i;
-
- if (!items)
- eprintf("\n\nBtparms not initialized!\n");
- if (indices < 1)
- eprintf("\n\nNo indices.\n");
- for (i = 0; i <indices; i++)
- if (!stricmp(inames[i], nospace(btname)))
- return i;
- eprintf ("\n\nIndex %s not found.\n", btname);
- return -1;
- }
-
- char* nospace(const char *arg)
- {
- static char rtn[80];
- int i, j = 0, k = strlen(arg);
- for (i = 0; i < k; i++)
- if (arg[i] != ' ')
- rtn[j++] = arg[i];
- rtn[j] = '\0';
- return rtn;
- }
-
- int eprintf(const char *format, ...)
- {
- int rtn;
- va_list argptr;
- va_start(argptr, format);
- rtn = vprintf(format, argptr);
- va_end (argptr);
- exit (1);
- return rtn;
- }
-
- char *ToUpper(const char *c)
-
- {
- static char d[257];
- int i;
-
- for (i = 0; i < 256, c[i] != '\0'; i++)
- d[i] = toupper(c[i]);
- d[i] = '\0';
- return d;
- }
-
- void Isam::reindex(rel_func func)
- {
- char buf[20], cmd[80];
- int tad, i, rlen = btparms[*btr].dreclen, x, y;
- long l;
-
- clear();
- strcpy(buf, btparms[*btr].filename);
- strcat(buf, ".tad");
- if (bt_open(buf, 0_RDWR,S_IRDWR) != ERR)
- // I can't cook in a dirty kitchen!
- // Besides that, the last re-index
- // must have failed and I don't know
- // where the real data is, now.
- eprintf("\n\n%s still exists!\n", buf);
- sprintf(cmd, "ren %s.dat %s.tad",
- btparms[*btr].filename, btparms[*btr].filename);
- close (fd[0]);
- system (cmd); // ren .dat to .tad
- if ((tad = bt_open(buf, 0_RDWR,S_IRDWR)) == ERR)
- eprintf("n\nCan't open %s\n", buf);// ren didn't go
- strcpy(buf, btparms[*btr].filename); // recreate .dat
- strcat(buf, ".dat");
- if((fd[0]=bt_open(buf, 0_NEW/0_RDWR, S_IRDWR) ) == ERR)
- eprintf ("\n\nCouldn't recreate %s.dat\n",
- btparms[*btr].filename);
- initdat(fd[0], btc); // create file header
- btparms[*btr].datafd = fd[0];
- strcpy(buf, btparms[*btr].filename); // unlink .idx
- strcat(buf, ".idx");
- close (fd[1]);
- unlink (buf);
- for (i = 0; i < indices; i++) {
- btrterm(btc + i); // to btrinit() with no .idx
- if (btrinit(inames, btc+i) == ERR)
- eprintf("\n\nCouldn't re-initialize %s.\n",
- inames[i]);
- creatbtr(btc + i);
- }
- for (i = 0; i < indices; i++) { // re-initialize
- btrterm(btc + i);
- if (btrinit(inames[i], btc+i) == ERR)
- eprintf("\n\nCouldn't re-initialize %s.\n",
- inames[i]);
- } // We now have empty datafile and indexfile
- strcpy(buf, btparms[*btr].filename); // re-open .idx
- strcat(buf, ".idx");
-
- if((fd[1]:bt_open(buf, 0_RDWR, S_IRDWR)) == ERR)
-
- eprintf ("\n\nCouldn't recreate %s.idx\n",
- btparms[*btr].filename);
- btparms[*btr].indxfd = fd[1];
- clrscr(); // we're ready
- l = 2L; i = 0;
- while (1) {
- if (!(l%10)) {
- if (l < 11L) {
- gotoxy(10,10);
- cprintf("Processing %s location ",
- btparms[*btr].filename);
- x = wherex(); y = wherey();
- }
- gotoxy(x, y);
- cprintf("%ld", l);
- }
- btseek(tad, 1++, rlen);
- if (Read (tad, *rec, rlen) == rlen) {
- if (**rec != '~') {
- if (func)
- if (func(*rec))
- continue;
- write();
- i++;
- }
- }
- else
- break;
- }
- printf ("\n\n%d records added.\n", i);
- close (tad);
- strcpy(buf, btparms[*btr].filename);
- strcat(buf, ".tad");
- unlink (bur); // clean kitchen for next time
- }
- /* End of File */
-
-
- Listing 3 catalog.cpp
- /////////////////
- // catalog.cpp //
- /////////////////
-
- #include "isam.h"
- #include <ctype.h>
-
- static struct cat {
- char *name;
- t_func func;
- } Catalog[] = { { "descwrds", descwrds },
- { NULL, NULL }
- };
-
- ///////////////////////
- // Catalog utilities //
- ///////////////////////
-
- int catalog_number (char *name)
- {
-
- int i = 0;
- while (Catalog[i].name) {
- if (!strcmp(nospace(name), Catalog[i].name))
- return i;
- i++;
- }
- return -1;
- }
-
- t_func cataloged_func(int f)
- {
- return Catalog[f].func;
- }
-
- /////////////////////////
- // Cataloged functions //
- /////////////////////////
-
- char * descwrds (char *r);
-
- char *descwrds (char *r)
- { // Extracts up to 20 seven-letter words from
- // desc field. The description field starts at
- // posn 37 and goes on for 120 characters.
- static char keys[141];
- int i = 37, j = 0, k = 0;
-
- memset (keys, ' ' , 140);
- while ((i < 157) && (k < 20)){
- while ((ispunct(r[i]) isspace(r[i])) && i < 157)
- i++;
- while (!ispunct(r[i]) && !isspace(r[i]) && i < 157
- && j < 7)
- keys[(7 * k) + j++] = r[i++];
- while (!ispunct(r[i]) && !isspace(r[i]) && i < 157)
- i++;
- k++;
- j = 0;
- }
- keys[7 * k] = '\0';
- return keys;
- }
-
- /* End of File */
-
-
- Listing 4 A sample program using the re-index function Isam::re-index
- #include "isam.h"
-
- int orphan (char *rec);
-
- static Isam i ("invoice");
-
- void main(void)
- {
- Isam l ("lineitem");
-
- i.reindex (rel_func NULL);
- l.reindex (orphan);
-
- }
-
- int orphan (char *rec)
- {
- char inv_num[10];
- strnncpy(inv_num, rec + 7, 9);
- return (i.read(inv_num)) ? 0 : 1;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- The Header <time.h>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
- ANSI C standards committee, X3J11, and convenor of the ISO C standards
- committee, WG14. His latest books are The Standard C Library, published by
- Prentice-Hall, and ANSI and ISO Standard C (with Jim Brodie), published by
- Microsoft Press. You can reach him at pjp@plauger.com.
-
-
-
-
- Background
-
-
- Time and date calculations achieved a new level of sophistication under the
- UNIX operating system. Several of the developers of that system were amateur
- astronomers. They were sensitive to the need for representing times over a
- span of decades, not just years. They automatically reckoned time as Greenwich
- Mean Time (once GMT, now UTC), not just by the clock on the wall. They were,
- in short, more finicky than most about measuring and representing time on a
- computer.
- That same attention to detail has spilled over into the Standard C library.
- Its scope is basically whatever was available in C under UNIX that didn't
- depend on the peculiarities of UNIX. As a consequence, you can do a lot with
- times and dates in Standard C. The functions declared in <time. h> provide the
- relevant services.
- It stretches the truth a bit to say that these functions don't depend on the
- peculiarities of UNIX. Not all operating systems distinguish between local
- time and UTC. Even fewer allow different users to display times relative to
- different time zones. Some of the smallest systems can't even give you the
- time of day. Yet all implementations of C must take a stab at telling time
- wisely if they want to claim conformance to the C Standard.
- The C Standard contains enough weasel words to let nearly everybody off the
- hook. A system need only provide its "best approximation" to the current time
- and date, or to processor time consumed, to conform to the C Standard. A
- vendor could argue that 1 January 1980 is always the best available
- approximation to any time and date. A customer can rightly quarrel about the
- low quality of such an approximation, but not whether it satisfies the C
- Standard.
- What this means in practice is that a program should never take times too
- seriously. It can enquire about the current time (by calling time) and display
- what it gets in a variety of attractive formats. But it can't know for sure
- that the time and date are meaningful. If you have an application that depends
- critically upon accurate time stamps, check each implementation of Standard C
- closely.
- There are too many functions declared in <time.h> to cover in one installment.
- So I've divided the presentation into three parts:
- the basic time functions--those that yield results of type clock_t, time_t, or
- double
- the conversion functions--those that convert times between scalar and
- structured forms
- the formatting functions--those that yield text representations of encoded
- times
- The topic for this month is the basic functions.
-
-
- What the C Standard Says
-
-
-
-
- 7.12 Date and time <time.h>
-
-
-
-
- 7.12.1 Components of time
-
-
- The header <time.h> defines two macros, and declares four types and
- severalufunctions for manipulating time. Many functions deal with a calendar
- time that represents the current date (according to the Gregorian calendar)
- and time. Some functions deal with local time, which is the calendar time
- expressed for some specific time zone, and with Daylight Saving Time, which is
- a temporary change in the algorithm for determining local time. The local time
- zone and Daylight Saving Time are implementation-defined.
- The macros defined are NULL (described in 7.1.6); and
- CLOCKS_PER_SEC
- which is the number per second of the value returned by the clock function.
- The types declared are size_t (described in 7.1.6);
- clock t
- and
- time_t
- which are arithmetic types capable of representing times; and
- struct tm
- which holds the components of a calendar time, called the broken-down time.
- The structure shall contain at least the following members, in any order. The
- semantics of the members and their normal ranges are expressed in the
- comments.137
- int tm_sec;
- /* seconds after the minute- [0, 61] */
-
- int tm_min;
- /* minutes after the hour- [0, 59] */
- int tm_hour;
- /* hours since midnight- [0, 23] */
- int tm_mday;
- /* day of the month- [1, 31] */
- int tm_mon;
- /* months since January- [0, 11] */
- int tm_year;
- /* years since 1900 */
- int tm_wday;
- /* days since Sunday- [0, 6] */
- int tm_yday;
- /* days since January 1- [0, 365] */
- int tm_isdst;
- /* Daylight Saving Time flag */
- The value of tm_isdst is positive if Daylight Saving Time is in effect, zero
- if Daylight Saving Time is not in effect, and negative if the information is
- not available.
-
-
- 7.12.2 Time manipulation functions
-
-
-
-
- 7.12.2.1 The clock function
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- clock_t clock(void);
-
-
- Description
-
-
- The clock function determines the processor time used.
-
-
- Returns
-
-
- The clock function returns the implementation's best approximation to the
- processor time used by the program since the beginning of an
- implementation-defined era related only to the program invocation. To
- determine the time in seconds, the value returned by the clock function should
- be divided by the value of the macro CLOCKS_PER_SEC. If the processor time
- used is not available or its value cannot be represented, the function returns
- the value (clock_t) -1.138
-
-
- 7.12.2.2 The difftime function
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- double difftime(time_t time1, time_t time0);
-
-
-
- Description
-
-
- The difftime function computes the difference between two calendar times:
- time1 - time0.
-
-
- Returns
-
-
- The difftime function returns the difference expressed in seconds as a double.
- .....
-
-
- 7.12.2.4 The time function
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- time_t time(time_t *timer);
-
-
- Description
-
-
- The time function determines the current calendar time. The encoding of the
- value is unspecified.
-
-
- Returns
-
-
- The time function returns the implementation's best approximation to the
- current calendar time. The value (time_t)-1 is returned if the calendar time
- is not available. If timer is not a null pointer, the return value is also
- assigned to the object it points to.
- Footnotes
- 137. The range [0, 61] for tm_see allows for as many as two leap seconds.
- 138. In order to measure the time spent in a program, the clock function
- should be called at the start of the program and its return value subtracted
- from the value returned by subsequent calls.
-
-
- Using the Basic Time Functions
-
-
- The functions declared in <time.h> determine elapsed processor time and
- calendar time. They also convert among different data representations. You can
- represent a time as:
- type clock_t for elapsed processor time, as returned by the primitive function
- clock
- type time_t for calendar time, as returned by the primitive function time or
- the function mktime
- type double for calendar time in seconds, as returned by the function difftime
- type struct tm for calendar time broken down into separate components, as
- returned by the functions gmtime and localtime
- a text string for calendar time, as returned by the functions asctime, ctime,
- and strftime
- You have a rich assortment of choices. The hard part is often identifying just
- which data represention, and which functions, you want to use for a particular
- application. For this installment, I ignore functions that produce a struct tm
- or a text string.
- Here is a brief description of the individual types and macros defined in
- <time.h>. It is followed by brief notes on how to use the basic time functions
- declared in <time.h>.
- NULL--See "The Header <stddef. h>," CUJ December 1991.
- CLOCKS_PER_SEC--The expression clock() / CLOCKS_PER_SEC measures elapsed
- processor time in seconds. The macro can have any arithmetic type, either
- integer or floating point. Type cast it to double to ensure that you can
- represent fractions of a second as well as a wide range of values.
- clock_t--This is the arithmetic type returned by clock, described below. It
- represents elapsed processor time. It can have any integer or floating-point
- type, which need not be the same type as the macro CLOCKS_PER_SECOND, above.
- size_t--See "The Header <stddef.h >," CUJ December 1991.
- time_t--This is the arithmetic type returned by time, described below. Several
- other functions declared in <time.h> also manipulate values of this type. It
- represents calendar times that span years, presumably to the nearest second
- (although not necessarily). Don't attempt to perform arithmetic on a value of
- this type.
- tm--A structure of type struct tm represents a "broken-down time." Several
- functions declared in <time.h> manipulate values of this type. You can access
- certain members of struct tm. Its definition looks something like:
- struct tm {
-
- int tm_sec;
- /* seconds after the minute (from 0) */
- int tm_min;
- /* minutes after the hour (from 0) */
- int tm_hour;
- /* hour of the day (from 0) */
- int tm_mday;
- /* day of the month (from 1) */
- int tm_mon;
- /* month of the year (from 0) */
- int tm_year;
- /* years since 1900 (from 0) */
- int tm_wday;
- /* days since Sunday (from 0) */
- int tm_yday;
- /* day of the year (from 0) */
- int tm_isdst;
- /* DST flag */
- The members may occur in a different order, and other members may also be
- present. The DST flag is greater than zero if Daylight Savings Time (DST) is
- in effect, zero if it is not in effect, and less than zero if its state is
- unknown. The unknown state encourages the functions that read this structure
- to determine for themselves whether DST is in effect.
- clock--This function measures elapsed processor time instead of calendar time.
- It returns -1 if that is not possible. Otherwise, each call should return a
- value equal to or greater than an earlier call during the same program
- execution. It is the best measure you can get of the time your program
- actually consumes. See the macro CLOCKS_PER_SEC, above.
- difftime--The only safe way compute the difference between two times t1 and t0
- is by calling difftime(t1, t0). The result, measured in seconds, is positive
- if t1 is a later time than t0.
- time--This function determines the current calendar time. It returns -1 if
- that is not possible. Otherwise, each call should return a value at the same
- time or later than an earlier call during the same program execution. It is
- the best estimate you can get of the current time and date.
-
-
- Implementing the Basic Time Functions
-
-
- The functions declared in <time.h> are quite diverse. Many wrestle with the
- bizarre irregularities involved in measuring and expressing times and dates.
- Be prepared for an assortment of coding techniques (at least in future
- installments).
- Listing 1 shows the file time.h. As usual, it inherits from the internal
- header <yvals.h> definitions that are repeated in several standard headers. I
- discuss the implementation of both the macro NULL and the type definition
- size_t in "The Header <stddef. h>," CUJ December 1991.
- <yvals.h> also defines two macros that describe properties of the primitive
- functions clock and time:
- The macro _CPS specifies the value of the macro CLOCKS_PER_SECOND.
- The macro_TBIAS gives the difference, in seconds, between values returned by
- time and the time measured from 1 January 1900. (This macro name does not
- appear in <time.h>.)
- The values of these macros depend strongly on how you implement clock and
- time. This implementation represents elapsed processor time as an unsigned int
- (type clock_t). It represents calendar time as an unsigned long (type time_t)
- that counts UTC seconds since the start of 1 January 1900. That represents
- dates from 1900 until at least 2036. You have to adjust whatever the system
- supplies to match these conventions.
- The macro _TBIAS is a kludge. Normally, you want to set it to zero. The
- version of time you supply should deliver calendar times with the appropriate
- starting point. UNIX, however, measures time in seconds since 1 January 1970.
- Many implementations of C offer a function time that matches this convention.
- If you find it convenient to use such a time function directly, then <yvals.h>
- should contain the definition:
- #define _TBIAS
- ((70 * 365LU + 17) * 86400
- That counts the 70 years, including 17 leap days, that elapsed between the two
- starting points. In several places, the functions declared in <time.h> adjust
- a value of type time_t by adding or subtracting _TBIAS.
- Listing 2 shows the file time.c. It defines the function time for a UNIX
- system. As usual, I assume the existence of a C-callable function with a
- reserved name that peforms the UNIX system service. For this version of time,
- the header <yvals.h> can define the macro _TBIAS to be zero.
- UNIX also provides an exact replacement for the function clock. So do many
- implementations of C modeled after UNIX. Thus, you may not have to do any
- additional work. Just define the macro _CPS appropriately. For a PC-compatible
- computer, for example, the value is approximately 18.2.
- Listing 3 shows the file clock.c. It defines a version of clock you can use if
- the operating system doesn't provide a separate measure of elapsed processor
- time. The function simply returns a truncated version of the calendar time. In
- this case, the header <yvals.h> defines the macro _CPS to be 1.
- Listing 4 shows the file difftime.c. It is careful to correct the biases of
- both times before comparing them. It is also careful to develop a signed
- difference between two unsigned integer quantities. Note how the function
- negates the difference t1 - t0 only after converting it to double.
- The remaining functions all include the internal header "xtime.h". Listing 5
- shows the file xtime.h. It includes the standard header <time.h> and the
- internal header "xtinfo.h". That internal header defines the type _Tinfo. It
- also declares the data object _Times, defined in the file asctime.c. _Times
- specifies locale-specific information on the category LC_TIME. Listing 6 shows
- the file xtinfo.h.
- The header "xtime.h" defines the macro WDAY that specifies the weekday for 1
- January 1900 (Monday). It defines the type Dstrule that specifies the
- components of an encoded rule for determining Daylight Savings Time. And it
- declares the various internal functions that implement this version of
- <time.h>.
-
-
- Conclusion
-
-
- The basic time functions I have shown so far meet a number of needs. You can
- measure execution times, obtain time and date stamps, and compare those
- stamps. That's more time support than many programming languages offer. For
- Standard C, however, it's only the beginning.
- References
- W.M. O'Neil. 1975. Time and the Calendars. Sydney, N.S.W.: Sydney University
- Press. Calendars are notoriously idiosyncratic. This book tells you more than
- you probably want to know about the history of measuring calendar time. It
- also explains why days and dates are named and determined the way they are
- today.
- This article is excerpted in part from P.J. Plauger, The Standard C Library,
- (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
-
- Listing 1 time. h
- /* time.h standard header */
- #ifndef _TIME
- #define _TIME
- #ifndef _YVALS
- #include <yvals.h>
-
- #endif
- /* macros */
- #define NULL _NULL
- #define CLOCKS_PER_SEC _CPS
- /* type definitions */
- #ifndef _SIZET
- #define _SIZET
- typedef _Sizet size_t;
- #endif
- typedef unsigned int clock_t;
- typedef unsigned long time_t;
- struct tm {
- int tm_sec;
- int tm_min;
- int tm_hour;
- int tm_mday;
- int tm_mon;
- int tm_year;
- int tm_wday;
- int tm_yday;
- int tm_isdst;
- };
- /* declarations */
- char *asctime(const struct tm *);
- clock_t clock(void);
- char *ctime(const time_t *);
- double difftime(time_t, time_t);
- struct tm *gmtime(const time_t *);
- struct tm *localtime(const time_t *);
- time_t mktime(struct tm *);
- size_t strftime(char *, size_t, const char *,
- const struct tm *);
- time_t time(time_t *);
- #endif
- /* End of File */
-
-
- Listing 2 time.c
- /* time function -- UNIX version */
- #include <time.h>
-
- /* UNIX system call */
- time_t_Time(time_t *);
-
- time_t (time)(time_t *rod)
- { /* return calendar time */
- time_t t = _Time(NULL) + (70*365LU+17)*86400;
-
- if (tod)
- *tod = t;
- return (t);
- }
- /* End of File */
-
-
- Listing 3 clock.c
- /* clock function -- simple version */
- #include <time.h>
-
-
- clock_t (clock)(void)
- { /* return CPU time */
- return ( (clock_t) time(NULL) );
- }
- /* End of File */
-
-
- Listing 4 difftime.c
- /* difftime function */
- #include <time.h>
-
- double (difftime)(time_t t1, time_t t0)
- { /* compute difference in times */
- t0 -= _TBIAS, t1 -= _TBIAS;
- return (t0 <= t1 ? (double)(t1 - t0)
- : -(double) (t0 - t1));
- }
- /* End of File */
-
-
- Listing 5 xtime.h
- /* xtime.h internal header */
- #include <time.h>
- #include "xtinfo.h"
- /* macros */
- #define WDAY 1 /* to get day of week right */
- /* type definitions */
- typedef struct {
- unsigned char wday, hour, day, mon, year;
- } Dstrule;
- /* internal declarations */
- int _Daysto(int, int);
- const char *_Gentime(const struct tm *, _Tinfo *,
- const char *, int *, char *);
- Dstrule *_Getdst(const char *);
- const char *_Gettime(const char *, int, int *);
- int _Isdst(const struct tm *);
- const char *_Getzone(void);
- size_t _Strftime(char *, size_t, const char *,
- const struct tm *, _Tinfo *);
- struct tm *_Ttotm(struct tm *, time_t, int);
- time_t _Tzoff(void);
- /* End of File */
-
-
- Listing 6 xtinfo.h
- /* xtinfo.h internal header*/
-
- /* type definitions */
- typedef struct {
- const char *_Ampm;
- const char *_Days;
- const char *_Formats;
- const char *_Isdst;
- const char *_Months;
- const char *_Tzone;
- } _Tinfo;
- /* declarations */
- extern _Tinfo _Times;
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- The Function operator[]
-
-
-
-
- Dan Saks
-
-
- Dan Saks is the owner of Saks & Associates, which offers consulting and
- training in C and C++. He is secretary of the ANSI and ISO C++ standards
- committees, and also contributing editor for Windows/DOS Developer's Journal.
- Dan recently finished his first book, C++ Programming Guidelines, written with
- Thomas Plum. You can write to him at 393 Leander Dr., Springfield, OH 45504,
- or dsaks@wittenberg.edu (Internet), or call (513)324-3601.
-
-
- Dynamic array classes let you create arrays for which you can delay choosing
- the number of elements until runtime. In my last column, I described reasons
- why you might want to use such things in your programs (see "Dynamic Arrays",
- CUJ, November, 1992). I also presented a fairly typical implementation for
- class float_array, a simple dynamic array of float.
- Each float_array has two data members: array, a pointer to the first element
- of an actual array of float allocated from the free store, and len, the number
- of elements in that array. Listing 1 shows the class definition. It has two
- constructors, a destructor, an assignment operator, a subscript operator, and
- a function that returns the number of elements.
-
-
- const Member Functions Revisited
-
-
- Last time I also introduced const member functions. For the most part, you can
- think of a const member function as one that promises not to change the object
- addressed by its this pointer. Although you can call a const member function
- for both const and non-const objects, you can call a non-const member function
- only for non-const objects.
- You declare a member function as const by placing the keyword const after the
- parameter list in the member function declaration. Class float_array in
- Listing 1 has two const member functions: operator[] and length. You must also
- use the keyword const when you write the function definitions, as shown in
- Listing 2.
- If you accept my belief that you should declare objects const whenever
- feasible, then clearly the length function should be const. length simply
- returns the number of elements in the array without changing anything.
- It also appears that operator[] should be const. If you don't declare it
- const, you can't subscript a const float_array. The operation appears to be
- perfectly safe. After all, it doesn't change anything. It simply returns a
- reference to the selected element. But, when I concluded last time, I said
- that the operator[] in Listing 2 is an accident waiting to happen. It lets you
- write an expression, without a cast, that inadvertently modifies a const
- float_array. For example, given
- const float_array fb(fa);
- you can write
- fb[0] = 0;
- which overwrites the value of the fb's first element. This is valid C++. The
- assignment statement compiles and executes without error. But that assignment
- is probably an accident, and you might find the results surprising.
-
-
- constness is Shallow
-
-
- To see how the accident can happen, let's take a closer look at what it means
- for a member function to be const. In a non-const, non-static member function
- for class X, the type of this is X *const. That is, the function cannot change
- the value of its this pointer, but it can modify each non-const data member of
- the object addressed by this.
- In a const member function of class X, this has type const X *const. Not only
- is the this pointer const, but so is the object it points to. Each data member
- of a const object is in turn const. This applies recursively to any data
- members that are objects of a class type. Thus, a const member function cannot
- change the value of any data members in the object addressed by this. (A const
- member function must be non-static because the const qualifier modifies the
- object addressed by the this pointer. A static member function has no this
- pointer, so it cannot be const.)
- Aye, there's the rub: constness is shallow. A pointer member in a const object
- is a const pointer, but it is not pointer to const unless explicitly declared
- so. In other words, inside operator[], the object's array data member (that
- is, this>array) has type float_array *const, not the desired const float_array
- *. You cannot change the pointer, but you can change the values of the array
- elements. This violates the intuitive notion that the elements of a const
- array are themselves const.
- operator[] in Listing 2 is a const member function. Inside the function, the
- array member is const, but the element array[i] is not. The function returns a
- float &, not a const float &. Thus, even though operator[] doesn't change any
- values, it returns a modifiable reference to an array element that the caller
- might accidentally use to change a value in a const float_array.
- From the programming language's standpoint, this is not an error. The behavior
- of the float_array class, even with this questionable operator[], is
- well-defined. For a non-class type T, writing over a const T object (or a part
- thereof) yields undefined behavior because you might be writing into ROM
- (read-only memory). But C++ only places a const class object in ROM if that
- class has neither constructors nor a destructor. Since class float_array has
- constructors and a destructor, the translator will never put const float_array
- objects in ROM. In this case, const float_array objects behave at runtime just
- like non-const objects. However, if class float_array had neither constructors
- nor a destructor, the translator might place const float_array objects in ROM,
- and calling operator[] for a const float_array would have undefined behavior.
- C++ has no notation for specifying "deep" constness. That is, in a class X
- with pointer member T *p, you can't make the p member of a const X object have
- type const T *. However, you can design X to simulate "deep" constness.
-
-
- Overloading with const
-
-
- Let's consider correcting the problem in operator[] (Listing 2) by changing
- the function's return type from float & to const float & in both Listing 1 and
- Listing 2. Then when you declare
- const float_array fb(fa);
- and write
- fb[0] = 0;
- you will get a compilation error because the expression fb[0] refers to a
- const float object, which you cannot modify. Unfortunately, now every
- float_array subscripting expression is const, so you cannot do that assignment
- even if fb were non-const.
- What you need is two different subscripting operators: one for const
- float_arrays, and another for non-const float_arrays. C++ lets you overload a
- member function as const and non-const. That is, you can declare two member
- functions in a given class with identical names and parameter lists, provided
- one of the functions is const and the other is not. In this case, you overload
- operator[] as a const function returning a const float &, and as a non-const
- function returning a (plain) float &. Listing 3 shows the float_array class
- definition with both declarations for operator[]. Listing 4 shows the
- corresponding function definitions.
- When the compiler encounters a float_array subscripting expression like fb[i],
- it chooses an operator[] by the constness of fb. It does not matter whether
- fb[i] appears as an lvalue or rvalue. For example, given
- float_array fa(fx);
- const float_array fb(fy);
- then
- fa[i] = fb[i];
- calls the non-const operator[] on the left of the assignment, and calls the
- const operator[] on the right. Everything works fine. However, the assignment
-
- fb[i] = fa[i];
- calls the const operator[] on the left side and calls the non-const operator[]
- on the right. But the const operator[] returns a non-modifiable lvalue (a
- const float &), so it cannot appear as the destination of the assignment, and
- you get a compile-time error.
- Furthermore, the compiler selects the operator[] based on the constness of the
- float_array expression that refers to the object, and not the constness of the
- object itself. For example,
- const float_array *p = &fa;
- binds p, a pointer to const, to a non-const object, fa. Then (*p) [i] calls
- the const operator[] because *p has type const float_array, even though p
- points to a float_array that's declared non-const.
-
-
- Arrays That Grow
-
-
- When I first introduced operator[] in my previous column, I suggested that the
- function could handle a subscript out-of-bounds in a number of ways. Thus far,
- my float_array class has treated subscripts out of bounds as an error caught
- by assert. Now let's consider rewriting operator[] to automatically extend the
- array so that the subscript is in bounds.
- For example, if you declare
- float_array fa(4);
- then fa has four elements whose indices are 0 through 3. Now if you write
- fa[6] = 1.0;
- then operator[] adds three more elements to fa at indices 4, 5, and 6, so the
- assignment operator has some place to store the 1.0.
- Rewrite only the non-const operator[]. Extending an array changes the array
- (by making it bigger), and a const member function should not change the
- object addressed by its this pointer. Subscripting out of range in a const
- float_array should remain a runtime error.
- Listing 5 shows my revised implementation for the non-const operator[]. If the
- function determines that the subscript is out of bounds, it allocates a new,
- larger array. It copies the values of the elements of the old array into their
- corresponding positions in the new array, and fills the remaining elements in
- the new array with zeros. Then it deletes the old array, thus returning that
- storage to the free store.
- Listing 6 shows a trivial test program that uses operator[] to extend an
- array. Figure 1 shows output from that program. The second for loop in main
- (Listing 6) overwrites the values in fb and then adds new elements. The output
- expression just before the return statement forces a subscripting error on the
- const float_array fc. I added that expression just to reinforce that
- operator[] is different for const and non-const float_arrays.
- The subscripting error makes the assertion in operator[] fail. assert displays
- an error message and aborts the program. Unlike the exit function, abort does
- not flush and close the standard output stream, cout. Thus I changed the body
- of the display function from
- cout << s << " = " << fa << '\n';
- (as it was in my previous column), to
- cout << s << " = " << fa << endl;
- endl is a special kind of function called a manipulator. It's defined in
- iostream.h. The expression
- cout << endl;
- writes a newline to cout and flushes cout's output buffer to standard output.
- Typically, you won't see the difference between \n and endl unless you
- redirect standard output to a file.
- You might recognize an opportunity to use realloc in the non-const operator[].
- realloc is a Standard C library function that changes the size of a
- dynamically-allocated block of memory. However, realloc only works with
- storage previously allocated by calloc, malloc, or realloc. Many C++
- environments implement the new operator in terms of malloc, so realloc might
- work with new. But there is no guarantee that it always will.
- For float_arrays, you could simply abandon the new operator and use malloc and
- realloc. The code for class float_array will still be portable. However, using
- malloc and realloc only works for arrays of objects without constructors and
- destructors. The new operator applies a default constructor to each element of
- the array it allocates. malloc just grabs storage.
- On occasion, members of the C++ standards committee have suggested adding a
- renew operator that works with new as realloc works with malloc. So far,
- nothing has come of it.
-
-
- Dangling Pointers and References
-
-
- A dangling pointer or reference is one that remains bound to an address after
- the program deallocates the object at that address. The potential occurrence
- of a dangling pointer or reference is an ever present fact of life for C++
- programmers, as it is for C programmers. operator[] offers yet another
- opportunity for the unwary to get bitten.
- Consider, for example, the following code fragment:
- float_array fa, fb;
- ...
- float *p = &fa[i];
- ...
- fa = fb;
- ...
- *p = 1.0;
- p's declaration binds it to the address of an element of fa. (Remember, fa[i]
- is a reference to an element, and so &fa[i] is the address of the referenced
- element.) If fb and fa have a different number of elements, the assignment fa
- = fb deletes the old elements of fa with new ones at a different address. This
- leaves p dangling.
- An even subtler problem occurs in the test program in Listing 7 when using the
- (non-const) operator[] that may extend a float_array (Listing 5). If you run
- the program with 4 as the input value for size, it initializes fa with four
- elements { 0 1 2 3 }. The subsequent statements
- size_t n = fa.length();
- fa[n] = fa[n - 1] = 123;
- look like they extend fa by one element and copy 123 into the last two
- elements, so that
- fa = { 0 1 2 123 123}
- I tested the assignment against three MS-DOS C++ compilers (Borland, Comeau,
- and Zortech), and they all produced that result.
- The next two statements
- n = fa.length();
- fa[n - 1] = fa[n] = 456;
- look like they should extend fa by one more element, and store 456 in the last
- two elements, so that
- fa = { 0 1 2 123 456 456 }
- Interestingly, only Borland and Zortech produced that result; Comeau produced
- fa = { 0 1 2 123 123 456 }
- Apparently, Comeau evaluates
- fa[n - 1] = fa[n] = 456;
-
- more or less as follows:
- float &t1 = fa[n - 1];
- float &t2 = fa[n];
- t2 = 456;
- t1 = t2;
- But the call to fa[n] in the second step extends fa and moves its elements to
- a different place in the free store. Unfortunately, t1 has already been bound
- to the original location of fa[n - 1], and the call to fa[n] leaves t1
- dangling. Thus, the final step (t1 = t2), copies 456 from fa[n] into the
- original location of fa[n - 1], not the current one.
- The Comeau compiler is generating correct code. C++ compilers, like C
- compilers, have considerable freedom about the order in which they evaluate
- operands between sequence points, and the Comeau compiler is merely exercising
- its freedom. The problem is in the test program, because the expression
- depends on a particular evaluation order for success.
-
-
- Subscripting Objects
-
-
- operator[] opens the door for dangling pointers and references because it
- returns a reference to part of a float_array's internal representation. That
- representation tends to move around, but the references don't rebind to the
- new locations. You can eliminate many opportunities for dangling references by
- rewriting operator[] to return a subscripting object instead of a reference.
- A reference to a float can only keep track of a single float element in a
- float_array. The reference has no way of knowing if the value it's referring
- to has moved. A subscripting object keeps track of both the float_array and
- the value of the subscript, so it always subscripts using the current location
- of the array.
- Listing 8 is the header for class float_array using a subscripting class,
- fa_index. fa_index has two data member: fa, a pointer to a float_array, and
- ix, a subscript for an element of that float_array. fa_index has a
- two-argument constructor that fills in values for members fa and ix.
- Notice that the constructor is private. Thus, clients (users) of fa_index
- can't create fa_index objects. How, then, does the program ever create an
- fa_index? fa_index declares float_array as a friend class. Hence, every member
- function of class float_array is a friend of class fa_index. float_array
- member functions has access rights so they can construct fa_index objects.
- Listing 9 presents the implementations for both the fa_index and float_array
- member functions. (The listing includes all the float_array member functions
- for completeness.) The only float_array member function that changes to use
- fa_index is the non-const operator[]. Rather than return a float & as before,
- it returns an fa_index object.
- The fa_index class has a conversion operator that converts an fa_index to a
- float. When you use an expression like fa[i] as an rvalue in a context
- expecting an arithmetic expression, the program implicitly calls operator
- float to convert the fa_index object into the value of element that the object
- designates. That is, the compiler translates the assignment in
- float x;
- ...
- x = fa[i];
- into something like
- fa_index t1(fa[i]);
- x = float(t1);
- The program constructs a temporary fa_index t1 to hold the result of fa[i].
- Then it applies fa_index::operator float to t1 to get the value of the array
- element.
- When fa[i] appears to the left of an assignment, the program calls
- fa_index::operator=(float) to write through the fa_index object and into the
- float_array element it designates. For example, an assignment like
- fa[i] = 1;
- translates into
- fa_index t1(fa[i]);
- t1.operator=(float(1));
- Using this new implementation for float_array with the test program in Listing
- 7 eliminates the dangling reference. It enables all three compilers to produce
- the same (intended!) result.
- But you pay for subscripting objects. They slow down every subscripting
- operation on a non-const float_array. Furthermore, you lose some functionality
- that you have to reconstruct by writing more member functions for fa_index.
- For example, fa_index::operator= only lets you use an expression like fa[i] as
- the left side of an = operator. It doesn't cover any other uses as an lvalue,
- such as fa[i] *= 10 or ++fa[i]. If you want these operators, you must define
- them for class fa_index.
-
-
- Other Uses for operator[]
-
-
- Some C++ applications use operator[] as a file positioning operator, replacing
- explicit calls to a function like lseek. For example,
- File f("myfile");
- ...
- f[10] = 'X';
- writes an X into the tenth character position of File f. Coplien (1992, 49-52)
- outlines a simple implementation for this scheme using subscripting objects.
- Cargill (1992, 91-112) provides an excellent description of what goes wrong
- with poorly-designed subscripting objects. His example uses a file object
- similar to Coplien's. Cargill also provides some thoughtful discussion on
- whether using operator[] this way is just too clever. The pitfalls may
- out-weigh the notational advantages.
- References
- Cargill, Tom. 1992. C++ Programming Style. Reading, MA: Addison-Wesley.
- Coplien, James O. 1992. Advanced C++ Programming Styles and Idioms. Reading,
- MA: Addison-Wesley.
- Figure 1 Output from the test program in Listing 6
- size? 3
- fa = { 0 1 2 }
- fb = { 0 1 2 }
- fb = { 0 1 2 }
- fb = { 0 1 2 }
- fb = { 0 1 4 }
- fb = { 0 1 4 9 }
- fb = { 0 1 4 9 16 }
- fb = { 0 1 4 9 16 25 }
- fc = { 0 1 2 }
- Assertion failed: i < len, file fa4.cpp, line 59
- Abnormal program termination
-
-
- Listing 1 Class definition of float_array
- // fa2.h - a dynamic array of float
-
- #include <iostream.h>
-
- class float_array
- {
- public:
- float_array(size_t n = 0);
- float_array(const float_array &fa);
- ~float_array();
- float_array &operator=(const float_array &fa);
- float &operator[](size_t i) const;
- inline size_t length() const;
- private:
- float *array;
- size_t len;
- };
-
- ostream &operator<<(ostream &os, float_array &fa);
-
- inline size_t float_array::length() const
- {
- return len;
- }
-
- /* End of File */
-
-
- Listing 2 float_array::operator[] as a const member function
- // fa2.cpp - a dynamic array of float
-
- #include "fa2.h"
- #include <assert.h>
-
- // other float_array member function definitions ...
-
- float &float_array::operator[](size_t i) const
- {
- assert(i < len);
- return array[i];
- }
- /* End of File */
-
-
- Listing 3 Class definition of float_array with operator[] overloaded as const
- and non-const
- // fa3.h - a dynamic array of float with operator[]
- // as both const and non-const member functions
-
- #include <iostream.h>
-
- class float_array
- {
- public:
- float_array(size_t n = 0);
- float_array(const float_array &fa);
- ~float_array();
- float_array &operator=(const float_array &fa);
-
- const float &operator[](size_t i) const;
- float &operator[](size_t i);
- inline size_t length() const;
- private:
- float *array;
- size_t len;
- };
-
- ostream &operator<<
- (ostream &os, const float_array &fa);
-
- inline size_t float_array::length() const
- {
- return len;
- }
- /* End of File */
-
-
- Listing 4 float_array function definitions
- // fa3.cpp - a dynamic array of float with operator[]
- // as both const and non-const member functions
-
- #include "fa3.h"
- #include <assert.h>
-
- // other float_array member function definitions ...
-
- const float &float_array::operator[](size_t i) const
- {
- assert(i < len);
- return array[i];
- }
-
- float &float_array::operator[](size_t i )
- {
- assert(i < len);
- return array[i];
- }
-
- /* End of File */
-
-
- Listing 5 An implementation of float_array that extends the size of the array
- when it detects subscript out-of-bounds
- // float_array::operator[] that extends the array on
- // subscript out of bounds
- float &float_array::operator[](size_t i)
- {
- if (i >= len)
- {
- float *new_array = new float[i + 1];
- assert(new_array != 0);
- size_t j;
- for (j = 0; j < len; ++j)
- new_array [j] = array[j];
- for (; j < i + 1; ++j)
- new_array[i] = 0;
- delete [] array;
- array = new_array;
- len = i + 1;
-
- }
- return array[i];
- }
- /* End of File */
-
-
- Listing 6 A trivial float_array test program
- #include <iostream.h>
- #include "fa4.h"
-
- void display(const char *s, const float_array &fa)
- {
- cout << s <<" = "<< fa << endl;
- }
-
- int main()
- {
- size_t i, size;
- cout << "size? ";
- cin >> size;
-
- float_array fa(size);
- for (i = 0; i < fa.length(); ++i)
- fa[i] = i;
- display("fa", fa);
- float_array fb = fa;
- display("fb", fb);
- for (i = 0; i < 2 * size; ++i)
- {
- fb[i] = i * i;
- display("fb", fb);
- }
- const float_array fc = fa;
- display("fc", fc);
- cout << "fc[size] = " << fc[size] << '\n';
- return 0;
- }
- /* End of File */
-
-
- Listing 7 Another float_array test program
- #include <iostream.h>
- #include "fa4.h"
-
- void display(const char *s, const float_array &fa)
- {
- cout << s << " = " << fa << endl;
- }
-
- int main()
- {
- size_t i, size;
- cout << "size? ";
- cin >> size;
-
- float_array fa(size);
- for (i = 0; i < fa.length(); ++i)
- fa[i] = i;
- display("fa", fa);
-
- i = fa.length();
- fa[i] = fa[i - 1] = 123;
- display("fa", fa);
- i = fa.length();
- fa[i - 1] = fa[i] = 456;
- display("fa", fa);
- return 0;
- }
- /* End of File */
-
-
- Listing 8 Class definition for float_array using a subscripting class
- // fa5.h - a dynamic array of float using a
- // subscripting object
-
- #include <iostream.h>
-
- class fa_index
- {
- friend class float_array;
- public:
- fa_index &operator=(float f);
- operator float();
- private:
- fa_index(float_array *f, size_t i);
- float_array *fa;
- size_t ix;
- };
-
- class float_array
- {
- friend class fa_index;
- public:
- float_array(size_t n = 0);
- float_array(const float_array &fa);
- ~float_array();
- float_array &operator=(const float_array &fa);
- float operator[](size_t i) const;
- fa_index operator[](size_t i);
- inline size_t length() const;
- private:
- void extend(size_t i);
- float *array;
- size_t len;
- };
-
- ostream &operator<<(ostream &os, const float_array &fa);
-
- inline size_t float_array::length() const
- {
- return len;
- }
- /* End of File */
-
-
- Listing 9 Implementation of float_array using a subscripting object
- // fa5.cpp - a dynamic array of float using a
- // subscripting object
-
-
- #include "fa5.h"
- #include <assert.h>
-
- fa_index::fa_index(float_array *f, size_t i)
- : fa(f), ix(i) { }
-
- fa_index &fa_index:: operator=(float f)
- {
- if (ix >= fa->len)
- fa->extend(ix);
- fa->array[ix] = f;
- return *this;
- }
-
- fa_index::operator float()
- {
- if (ix >= fa->len)
- fa->extend(ix);
- return fa->array[ix];
- }
-
- fa_index float_array::operator[](size_t i)
- {
- return fa_index(this, i);
- }
-
- float float_array::operator[](size_t i) const
- {
- assert(i < len);
- return array[i];
- }
-
- ffloat_array::float_array(size_t n)
- {
- if ((len = n) == 0)
- array = 0;
- else
- {
- array = new float[n];
- assert(array != 0);
- for (int i = 0; i < n; ++i)
- array[i] = 0;
- }
- }
-
- float_array::float_array (const float_array &fa)
- {
- if ((len = fa.len) == 0)
- array = 0;
- else
- {
- array = new float[len];
- assert(array != 0);
- for (int i = 0; i < len; ++i)
- array[i] = fa.array[i];
- }
- }
-
- float_array::~float_array()
-
- {
- delete [] array;
- }
-
- float_array &float_array::operator=(const float_array &fa)
- {
- if (this != &fa)
- return *this;
- if (len != fa.len)
- {
- delete [] array;
- if ((len = fa.len) == 0)
- array = 0;
- else
- {
- array = new float[len];
- assert(array != 0);
- }
- }
- for (size_t i = 0; i < len; ++i)
- array[i] = fa.array[i];
- return *this;
- }
-
- ostream &operator<<(ostream &os, const float_array &fa)
- {
- os << '{';
- size_t i;
- for (i = 0; i < fa.length(); ++i)
- os << ' ' << fa[i];
- return os << " }";
- }
-
- void float_array::extend(size_t i)
- {
- float *new_array = new float[i + 1];
- assert(new_array ! = 0);
- size_t j;
- for (j = 0; j < len; ++j)
- new_array [j] = array [j];
- for (; j < i + 1; ++j)
- new_array[i] = 0;
- delete [] array;
- array = new_array;
- len = i + 1;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Linked Lists, Strings, and Internationalization
-
-
-
-
- Ken Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++
- language courses for corporations. He is the author of C Language for
- Programmers and All On C, and was a member on the ANSI C committee. He also
- does custom C programming for communications, graphics, image databases, and
- hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707.
- You may fax questions for Ken to (919) 489-5239. Ken also receives email at
- kpugh@dukemvs.ac.duke.edu (Internet).
-
-
- Q
- Just got the August CUJ, so please excuse the response delay. To sort a linked
- list quickly, count the elements, allocate an array to hold them all, then
- sort the array. Pick off the elements to rebuild the list. Or else use a radix
- ("Postman's") sort, if possible. Or use a merge sort.
- Any sort not relying on element exchange will serve to sort a linked list,
- though it will of course need minor modifications. Quicksort becomes a merge
- sort, except that partitioning is based on a pivot rather than being blind
- (half the elements go in each sublist).
- Next, in your "array of strings" answer you have the statement:
- printf(get_string(1));
- SHAME! If there are any % characters in the string, nasty things may happen.
- Use:
- puts(get_string(i)); /*adds \n */
- or
- printf ("%s", get_string(1));
- Keep up the (otherwise) good work.
- David Chapman
- San Jose, CA
- A
- Thanks for your reply. As I suggested in my original article, if one needs to
- sort a linked list, it might be just as simple to keep an array of pointers to
- the data items and sort that instead.
- Thanks also for pointing out the potential problem with the format specifier.
- Most likely, the percent character (%) will not appear in text followed by any
- character other than a space. This will be an invalid conversion specifier
- whose behavior is undefined by the ANSI standard. This is another "little
- gotcha" of the type described in the answer to the next question.
- On some machine/compiler combinations, the percent sign will show up on the
- output. On others, it will disappear. Of course if % is followed by a
- legitimate conversion character, garbage will appear on the output or the
- program may bomb.
- If the specifier was for a float number (e.g. %f), then it is possible that
- the value picked up will be an invalid floating-point number and a
- floating-point exception will occur.
- In practice, the return value of get_string will not usually be passed to
- printf. Instead, it will be a parameter to some display function that will
- display it with an attribute and/or in some window.
- For those further interested in making programs portable, you might want to
- investigate the internationalization functions that are specified under the
- Xopen Portability Guidelines (XPG). These functions are available under OSF
- UNIX, and probably are available on several other systems. Xopen is a
- consortium of European computer vendors.
- A brief description of the design of the functions may help you develop your
- own if you are on a system that does not support them.
- The functions use a message catalog file that is specific to a single
- language. The catalog file contains all strings that need to be translated to
- another language. The files for each language are kept in a separate
- directory. The strings in the catalog are arranged in sets. Each set has an
- identifying number and each string within the set has a unique number within
- that set.
- The initial function, catopen, opens the appropriate file corresponding to the
- filename passed to it. The directory for the file (i.e. the language of the
- file) is derived using the environment variables NLSPATH and LANG. catopen
- returns a catalog identifier used by the remainder of the functions.
- The function corresponding to the get_string example is catgets. Its four
- parameters are the catalog identifier, a set number within the catalog, a
- string number with the set, and a default string. It returns a pointer to the
- corresponding string. If the routine is unable to access the particular
- string, it returns a pointer to the default string.
- Listing 1 and Listing 2 give an example of the use of the file. The example
- code produces:
- String to output
- on standard output if the file is accessible and
- I can't find it
- if the file is not accessible.
- The message catalog is actually composed of two files. The first file is an
- ASCII file. A compilation program uses the ASCII file as input and creates the
- actual file read by the program. This makes the lookup quicker as either a
- single or double seek is all that is required. The compiled file can be read
- in as a whole if it is not too large.
- The catalog can use symbolic names, rather than absolute numbers. The
- compilation program generates a header file for use by the calling functions.
- Obviously this is a far superior method, as it is easier to match the strings
- with their intended usage. Listing 3 and Listing 4 give an example of the use
- of symbols.
-
-
- Variable Argument Lists, Portability, and Gotchas
-
-
- Q
- Regarding the answer you gave to reader Willi Fleischer of Moerfelden, Germany
- (CUJ, September 1992, page 114) on the use of variable parameter list, I have
- tried to solve a problem of the same kind: to pass the variable arguments from
- a function to a function (which can be nested). The solution I found, though
- it needs to be verified on different platforms (I tested on SGI and IBM UNIX
- workstations), is to call the function that needs the variable argument list
- and to prevent it from adding any element to the calling function's stack.
- For example, in Listing 5, Msg will call PrintString in order to convert the
- printf argument list into a static string. As you can see, PrintString is the
- first function/statement to be executed in Msg after declaring line. If not,
- you will fail (try if(1) line = PrintString;).
- I understand this is somewhat restrictive, but it can be handled easily in
- many cases. Its advantages are: making PrintString syntax simple (no need for
- _direct and _va_list functions of the same kind), and making the functions
- that call it not have to deal with va_start and so on.
- Joseph Z. Wang
- White Plains, NY
- A
- I consider this in the land of serendipitous trouble. A particular set of
- operations works on a couple of computers, but is not really part of ANSI
- standard. The dead giveaway is the fact that it does not work if another
- statement is inserted into the code.
-
- PrintString is only being passed one parameter (fmt). The way the parameters
- are passed on this particular set of machines, the va_start macro
- coincidentally sets up args to point to the first parameter after fmt. If the
- stack pointer is altered by the calling function (such as you suggested), the
- coincidence disappears.
- Unfortunately (or fortunately, depending on how you look at it), the only
- portable ways to pass the variable parameter list is by the method suggested
- or one that follows along a similar line.
- I teach a class in how to make C portable. It turns out there are a few
- "gotchas" that can appear while using the language. Code appears portable, as
- it works on a few machines, but in reality, it is not. Your code is one
- example. The previous question regarding the % in a string passed to printf is
- another example of non-portable behavior.
- The C standard lists nine pages of unspecified, undefined, or
- implementation-defined behavior. Many of the items concern situations that a
- good C programmer would avoid anyway. Some others are of the "gotcha" variety.
- For example, the order of two equal members in an array sorted by qsort is
- unspecified. Some implementations may leave equal members in the original
- order. Going to an implementation that does not may produce some unexpected
- surprises if your programming was counting on the original behavior.
- Many of the portability problems can be pointed out by a good lint. For those
- new to C, this program is a source-code analysis program that comes with many
- UNIX systems and is available separately on PC systems. I have used PC-Lint by
- Gimpel and found it very satisfactory, in many cases superior to UNIX lints I
- have tried.
- For example, the following loop may set each element of the array to the value
- of its index:
- int array[10];
- int i;
- for (i = 0; i < 10;)
- {
- array[i] = i++;
- }
- Then again, it may not. The value of i may be incremented before or after it
- is used as the index value for array. The order of evaluation of the
- assignment operator is not specified by the language. Nor is the time
- specified that the increment will take place except that it is after the value
- of i on the right hand side is obtained and before the semicolon is reached.
- Any of the lint programs should point out this error of dependence on order of
- evaluation.
- My suggestion for writing portable code is to keep it simple. Another author
- suggests that "well written code is portable." In a paper by A. Dolenc, A.
- Lemmke, D. Keepl and G.V. Reilly, "Notes on Writing Portable Programs in C,"
- the authors state that the expression f(p=&a, p=&b, p=&c) is ambiguous. The
- standard permits it to be interpreted as f(&a, &b, &c) or f(&c, &c, &c) [or
- many others--pjp]. My immediate response is that one should have coded it in
- whichever of the forms was really required, followed by the appropriate
- assignment for p.
- By the way, the paper includes some excellent suggestions for making your
- program portable. They would permit your program to be backfitted to old K&R
- compilers, if you need to do that.
- I will admit that I do not code in an absolute portable fashion. It takes a
- lot of hard work. The one facet which I ignore entirely is the limitation of
- six characters of significance in an external name. Although the ANSI C
- library must adhere to this limitation, I find it difficult to obey it while
- writing a maintainable program. If I come across that odd system that requires
- it, I will simply make up some header files that look like:
- #define my_reasonably_named_function(a,b,c)\
- IHE345(a,b,c)
- #define another_useful_function(d,e) IHE346(d,e)
-
-
- Truncating a File in Place and Portability
-
-
- Is it possible to truncate a file in place using ANSI C? Currently, I am
- copying the truncated data to a temporary file, removing the original file,
- and renaming the temporary file to the original file's name. Is there a more
- sophisticated method? BSD4.3 UNIX provides the (non-portable) system function
- calls truncate and ftruncate. Do these implement the method described above?
- Jeff Dragovich
- Urbana, Illinois
- A
- The answer is as you have just described it. The BSD functions can play games
- with the inode (which contains the length of the file and data
- block-allocation information). Obviously you can run out of space when you
- truncate the file, as you will be making a copy of it.
- Since our theme is portability this month, let me suggest how to make
- functions like these portable. You can create a compatibility library of
- functions that you commonly use. For example, instead of using ftruncate, you
- might make up functions as:
- int file_truncate_by_fd(int file_descriptor,\
- unsigned int size);
- int file_truncate_by_name(char *file_name,\
- unsigned int size);
- These functions would be kept in a package of file functions whose
- implementation might vary from machine to machine. A header file, say file.h
- would be used for the function prototypes and any other #defines or typedefs
- required by your functions.
- In your BSD version of the library, these functions could call truncate and
- ftruncate. Alternatively, you could #define them in the header file to be the
- appropriate call, so the functions will be called inline, without an extra
- function call layer. For the MS-DOS version, the functions would perform the
- operations you listed.
- For all versions, the functions should return an error indication if the
- truncation was unable to be performed. Note that it is logically impossible
- for the BSD version to fail to truncate, while the MS-DOS version may fail if
- there is insufficient disk space.
-
-
- scanf and ANSI
-
-
- I have some comments about the sscanf question in the September 1992 CUJ. I
- realize this is probably the thousandth letter you've received about this, but
- I've run into similar sscanf problems in the past and have a vested interest
- in these questions.
- According to my reading of Harbison and Steele, (I don't have a copy of the
- ANSI Standard document), sscanf should return 4 on string[0], and 5 on
- string[1]. I suspect the return of 4 may be the result of an error in the code
- as published (should string[0] b e garbageR A 3 0 4 ?), but, as you pointed
- out, string[1] scanning using the [^PR] conversion operator will stop
- immediately upon seeing the R; no conversion will take place. Since the
- assignment-suppression operator is involved, this won't affect the rest of the
- conversion. No pointers would be consumed in either case. Conversion will then
- pick up at the first %c, and continue with the other char and the three ints,
- all of which are present in string[1]. Am I correct in this, or am I missing
- yet one more subtlety in sscanf formatting?
- By the way, sscanf behaves this way in Borland C++ version 3.1; previous
- versions of Turbo C/C++ would, believe it or not, cause a null-pointer error
- with this usage of sscanf.
- Thanks for your attention in this.
- Dan Rempel
- Victoria, B.C. Canada
- A
- Thank you for your sharp eye in catching the code error. I'll repeat the
- relevant code here, so our readers won't have to go back. The strings (with
- your correction) are:
- char *string[] = {
- "some garbageR A 3 0 4",
- "R A 3 0 4"
- };
- The sscanf with its format looked like:
- const char *format = "%*[^PR]%c %c %d %d %d";
- ret = sscanf (string[i], format, &l, &q, &k, &m, &n);
- When scanning string[0], the * suppresses the assignment of any set of
- characters which match the [^PR] specifier. That specifier matches any
- nonempty sequence of characters that are not P or R. This directive eats up
- the characters "some garbage" in string[0]. The value R is then assigned to 1,
- and the remaining assignments take place normally.
- For string[1], there is no sequence of characters that match the initial [^PR]
- specifier. The directive fails with a matching failure (inappropriate input,
- according to the standard). When that directive fails, sscanf returns with a
- zero value as no assignments have taken place.
-
- As I read the C Standard, if a match to a directive fails, even if there is
- assignment suppression, the function terminates at that point. The order of
- the operations as specified by the C Standard is to match the specifier and
- convert it to the corresponding type. It then checks for assignment
- suppression.
-
- Listing 1 Message catalog file example
- .../english/example.cat
-
- Set ID = 10
- ...
- Set ID = 24
- 1 "String to output"
- 2 "Another string to output"
-
- /* End of File */
-
-
- Listing 2 Accessing the message catalog
- message_file = catopen("example.cat", 0);
- printf("%s", catgets(message_file, 24, 1,
- "I can't find it");
- catclose(message_file);
-
- /* End of File */
-
-
- Listing 3 Message catalog file example with symbols
- .../english/example.cat
-
- Set ID = ERROR_MESSAGES
- ...
- Set ID = SOME_STRINGS
- FIRST_STRING "String to output"
- SECOND_STRING "Another string to output"
-
- /* End of File */
-
-
- Listing 4 Accessing the message catalog symbolically
- #include "example.h"
- ...
- message_file = catopen("example.cat", 0);
- printf("%s", catgets(message_file, SOME_STRINGS,
- FIRST_STRING, "I can't find it");
- catclose(message_file);
-
- /* End of File */
-
-
- Listing 5 Msg will call PrintString in order to convert the printf argument
- list into a static string.
- #include <stdarg.h>
-
- char *PrintString(const char *fmt, ...)
- /* Convert printf() arguments into a static string. */
- {
- va_list args;
- static char line[999];
-
- va_start(args, fmt);
- vsprintf(line, fmt, args);
- va_end(args);
- return line;
-
-
- }
-
- void Msg(const char *fmt, ...)
- /* Send message to stdout user. */
- {
- char *line;
-
- line = PrintString(fmt);
- print("%s\n", line);
- ...
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On The Networks: Special Issue: USENET Network News Update
-
-
- Sydney S. Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, author, and
- president of Datacomp Systems, Inc., a consulting and contract programming
- firm specializing in databases, data presentation and windowing, transaction
- processing, networking, testing and test suites, and device management for
- UNIX and MS-DOS. He can be contacted care of Datacomp Systems, Inc., 3837
- Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail on the
- Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
- Internet addressing).
-
-
- This is my third anniversary in writing this column, and its time to update
- the prior January columns. In January 1990 (CUJ Vol. 8, No. 1), I wrote about
- the internet, the Internet, USENET, and Network News. In January 1991 (CUJ
- Vol. 9, No. 2), I wrote about obtaining USENET Network News. In January 1992
- (CUJ Vol. 10, No. 2), I wrote about obtaining the sources referred to in this
- column. This year, I am going to present a quick review and update to all
- three past columns. I do not intend to provide an in-depth review, so
- newcomers to USENET might want to try and seek out back issues of those
- articles.
- "On The Networks" covers articles posted to several of the source groups on
- USENET Network News: comp. sources.games, comp.sources.misc, comp.sources.
- reviewed, comp.sources.unix, comp.sources.x, and alt.sources. Each of these
- groups is like a section of a large electronic magazine called USENET Network
- News. I call it a magazine, and not a bulletin board partly because unlike a
- bulletin board, where each reader accesses a central machine to read the
- messages, USENET Network News is delivered on a subscription basis to each
- computer, and the articles are read locally.
- I try and limit my coverage to the highlights of the articles posted to each
- group. In addition, I also generally restrict my coverage to
- freely-distributable C and C++ sources. Freely-distributable means that you
- can freely (and for free) make copies of the software, use it as you see fit,
- and give it away as you desire. It does not mean the software is in the public
- domain. Most of the software is copyrighted. This means you cannot pretend you
- wrote it, or include code from it in a product you are selling. However, the
- authors have allowed you to use and distribute it for free. If you make
- changes, most authors do not let you call the changed version by the name of
- the original. This is to avoid confusion as to what is and is not part of the
- product and to reduce the authors' support headaches.
-
-
- Internet Update
-
-
- First an update. USENET, often times referred to as "the net" is a loose
- collection of cooperating computers. In the long-distant past, all USENET
- computers ran UNIX, but now they could be running anything from MS/DOS to
- VAX/VMS. It also used to be that to be considered a computer on USENET, you
- communicated via the UNIX to UNIX Communications Protocol (UUCP) to another
- computer. That also has changed. Now there are many protocols in use. So, to
- be considered a member of the USENET network, one must be able to exchange
- Electronic Mail with other computers on the USENET network.
- If your computer is part of a network, and that network has a gateway to any
- other network, you are considered on an internet, short for inter-network.
- Note this internet is spelled with a lower case i. The Internet (capital I) is
- the computer network whose addressing (name space) is loosely managed by the
- DDN Network Information Center in Chantilly, VA. It is a collection of
- networks that grew out of the Defense Department's ARPANET (Advanced Projects
- Research Agency Network).
- The Internet now includes not only the MILNET and NSFnet member networks
- (direct descendents of the ARPANET), but also several commercial TCP/IP
- networks which are members of the Commercial Internet Exchange (CIX). The
- Internet is mostly a set of networks with leased lines permanently connecting
- them to regional and backbone networks. Some outlying networks use gateways
- with dial-up links to reach their regional network, but most links are 56K,
- 1.544M, or 45M bits/second leased lines (that's megabits per second).
- Whereas only mail and news is usually available over the USENET via UUCP, the
- Internet runs the TCP/IP protocol and supports news (NNTP, Network News
- Transfer Protocol), mail (SMTP, Simple Mail Transfer Protocol), Wide Area
- Information Service (WAIS), remote access lookup (archie), remote logins to
- any computer on the network provided you have an account there (telnet), and
- remote file transfer (FTP, file transfer protocol) in addition to many other
- services. All of these services coexist and work in real time.
-
-
- Network News Update
-
-
- The sources mentioned in this column are released via several groups
- distributed as part of USENET Network News. USENET Network News is both a
- method of distributing information between a very large group of computers,
- and a somewhat organized collection of that information into news groups that
- are distributed via the news software.
- First, the software. There are now two current Network News transport software
- suites: the older, C News, and a newcomer, INN.
- The current version of the traditional Network News transport software is
- named C News, not because it is written in C but because it follows A News
- (previously called News) and B News as the third rewrite of the transport
- software. It supports transfer of the news articles (the individual messages)
- between every member of the USENET network. The C News software recently had a
- performance-enhancement release, and a "cleanup" release is expected shortly.
- C News is best suited for smaller sites that mostly have UUCP feeds, and feed
- a limited set of neighbors.
- New for the major backbone sites, especially those with Internet connections,
- is Internet Network News (INN) from Rich Salz. INN is largely responsible in
- cutting down the time it takes for an article to be propagated throughout the
- backbone networks. Where C news uses batching to save articles for
- distribution in bulk, INN uses an immediate transfer to its NNTP (TCP/IP-based
- network news protocol) neighbors. Thus an article now reaches most of the
- backbone and regional network sites in only 1-5 minutes. (Just three years ago
- it took close to a day). INN is relatively new, and takes advantage of the
- decline in the price of RAM. It boosts performance by keeping everything it
- can in memory.
- Second, there is the volume. At this time last year, the average was
- twenty-five megabytes per day. Now it is up to approximately forty megabytes
- per day. The number of newsgroups is also growing. Currently there are over
- 2,400 of them.
- Lastly, there is a new distribution method, CD-ROM. Sterling Software, 1404
- Fort Crook Road S., Bellevue, NE 68005, publishes a set of ISO-9660 format
- CD-ROMs with almost all the groups of USENET Network News. The CD-ROMs come
- out when a new one fills up, which is about every 10-14 days. They even
- include a modified news reader that will access the information as stored on
- the CD-ROM. Sterling can be reached at (800) 643-NEWS, or (402) 291-2108 from
- outside of the US. A subscription runs about $350/year + shipping. Shipping
- varies from $60/year in the US up to $200/year for some overseas areas.
-
-
- How to Use This Column
-
-
- This column normally reports on articles in the comp.sources and alt.sources
- sub hierarchies. All of the groups I currently report on but alt.sources are
- moderated. For the moderated groups, the authors of the software submit their
- packages to the moderator for posting. Each group has its own rules for
- acceptance. alt.sources is unmoderated and a free-for-all. Sources in that
- group are posted directly by the author.
- For the moderated groups, when I list a package, I provide five pieces of
- information for each package. The Volume, Issue(s), archive name, the
- contributor's name, and electronic mail address. The Volume and Issue are
- specifically named in the listing. The archive name is in italics, and the
- contributor's name is followed by an electronic mail address, in < >'s.
- To locate a package via WAIS or archie, use the archive name. This is the
- short one-word name in italics given with each listing. To find the file at an
- archive site, use the group name (from the section of the column you are
- reading--I place all listings for each group together in the column), the
- volume and the archive name. Most archive sites store the postings as
- group/volume/archive-name. The issue numbers tells you in how many parts the
- package was split when posted. This way you can be sure to get all of the
- parts.
- In addition, I report on patches to prior postings. These patches also include
- the volume, issue(s), archive name, the contributors name, and electronic-mail
- address. Patches are stored differently by different archive sites. Some store
- them along with the original volume/archive name of the master posting. Some
- store them by the volume/archive name of the patch itself. The archive name
- listed is the same for both the patch and the original posting.
- alt.sources, being unmoderated, does not have volume and issue numbers. So I
- report on the date in the Date: header of the posting and the number of parts
- in which it appeared. If the posting was assigned an archive-name by the
- contributor, I also report on that archive name. Archive sites for alt.sources
- are harder to find, but they usually store things by the archive name.
-
-
- How to Find an Archive Site
-
-
- With that much information flowing into each site every day, most sites cannot
- keep the information on their local disks for very long, usually only a couple
- of days. So, by the time you read my articles, the sources have been deleted
- from the machines in the network to make room for newer articles. So, many
- sites have agreed to archive the source groups and keep these archives for
- several years.
- The problem then is finding out which sites archive which groups, and how to
- access these archives. I again refer to the articles by Jonathan I. Kames of
- the Massachusetts Institute of Technology posted to comp.sources.wanted and
- news.answers. These articles, appear weekly and explain how to find sources.
- As a quick review, here are the steps:
- 1. Figure out in what group, Volume, and Issue(s) the posting appeared. Also
- try and determine its archive name. If you know these items, it's usually easy
- to find an archive site that keeps that group. Most archive sites keep their
- information in a hierarchy ordered first on the group, then on the volume and
- last on the archive name. These together usually make up a directory path, as
- in comp.sources.unix/volume22/elm2 .3. In that directory you will find all of
- the articles that made up the 2.3 release of the Elm Mail User Agent that was
- posted in Volume 22 of the comp.sources.unix newsgroup. If you do not know the
- archive name, but do know the volume, each volume also has an Index file that
- can be retrieved and read to determining the archive name. One common publicly
- accessible archive site for each of the moderated groups in this article is
- UUNET.
- 2. If you do not know which sites archive the groups, or even if any site is
- archiving that item, but they are not archiving the entire group, consult
- Archie. (CUJ August 1991, Vol. 9, No. 8). Archie is a mail-response program
- that tries to keep track of sites reachable via FTP (file transfer protocol, a
- TCP/IP protocol used by internet sites) that have sources available for
- distribution. Even if you cannot access the archive site directly via FTP, it
- is worth knowing that the archive site exists because there are other ways of
- retrieving sources only available via FTP.
- 3. If you know the name of the program, but do not know to what group it was
- posted, try using Archie and search based on the name. Since most sites store
- the archives by group and volume, the information returned will tell you what
- newsgroup and volume it was posted in. Then you can retrieve the item from any
- archive site for that newsgroup.
- 4. If you do not even know the name, but know you are looking for source code
- that does xxx, retrieve the indexes for each of the newsgroups and see if any
- of the entries (usually listed as the archive name and a short description of
- the function) look reasonable. If so, try those. Or, make a query to archie
- based on some keywords from the function of the software and perhaps it can
- find items that match.
- 5. Next you have to determine what access methods the archive machine allows
- to retrieve software. Most archive sites are internet-based, and support the
- FTP service. If you have access to FTP on the internet, this is the easiest
- and fastest way of retrieving the sources. If you don't, perhaps a local
- college is a member of the internet and can assist you in using FTP to
- retrieve the sources you need.
- Other sites support anonymous UUCP access. This is access via the UUCP
- protocol where you don't need to register in advance to call in. UUNET
- Communications Services supplies this using the (900) GOT-SRCS number at a
- per-minute charge for nonsubscribers. Many other archive sites provide it for
- just the cost of your long-distance telephone call. If you cannot use FTP,
- this is the next best method to use. Many anonymous UUCP archive sites list
- what they carry in the Nixpub listing of public access UNIX sites maintained
- by Phil Eschallier. The Nixpub listing is posted in comp.misc and alt.bbs
- periodically. If you don't get News and need a copy, it can be retrieved via
- electronic mail using the periodic-posting mail-based archive "server." This
- is run by MIT on the system pit-manager.mit.edu. To use the server, send an
- electronic mail message to the address mail-server@pitmanager.mit.edu with the
- subject help and it will reply with instructions on how to use the server. If
- you are not on USENET, sending electronic mail to sites on the internet is
- also possible via the commercial mail services. CompuServe, MCI-Mail,
- ATT-Mail, and Sprint-Mail all support sending messages to Internet addresses.
- Contact the support personnel at the commercial mail service you use for
- details on how to send messages to Internet addresses.
- The last way to access the sources is via electronic mail. Several sites also
- make their archives available via automated mail-response servers. Note, this
- can be a very expensive way of accessing the information, and due to the load
- it places on the networks, most archive servers heavily restrict the amount of
- information they will send each day. The most commonly used mail-response FTP
- server is ftpmail@decwrl.dec.com. Digital Equipment Corporation runs a
- mail-based archive server that will retrieve sources via FTP and then mail
- them to you. To find out how to use this service, send it a message with the
- word help in the body.
-
-
-
- CD-ROM Archives Available
-
-
- Also available, are CD-ROMs with the archives of sources posted via USENET and
- from other sources as well. Two of the larger publishers are Walnut Creek
- CD-ROM and Prime Time Freeware.
- Walnut Creek CD-ROM, 1547 Palos Verdes Mall, Suite 260, Walnut Creek, CA (800)
- 786-9907 or (510) 947-5996 publishes several CD-ROMs each year. Some contain
- the Simtel20 MS-DOS Archive, others the X and GNU archives, and still others
- MS-Windows sources and other collections of sources and binaries. Disks run
- from $25 to $60 each (varies by title) plus shipping. In addition, the offer
- those hard to find CD-caddys at reasonable prices.
- Prime Time Freeware, 370 Altair Way, Suite 150, Sunnyvale, CA 94086, (408)
- 738-4832, FAX: (408) 738-2050, <ptf@cfcl.com>, publishes twice a year a
- collection of freely distributable source code, including the complete USENET
- archives. Their disks run about $60 each set plus shipping. The latest issue,
- August 1992, has over 3MB of source code spread over two disks. They also
- offer a standing subscription plan at a discount.
- Hopefully, this special edition of my column has given you a hint as to how to
- read my column and track down the sources. Note, I have been asked many times
- if I can make floppies or tapes containing the software mentioned in my
- column. I cannot spare the time to do this. I also have to work (and teach)
- for a living. If I started doing this, I could easily spend all my time trying
- to fulfill the requests and never get any of my work done.
- However, what I have offered to do in the past, and am still willing to do, is
- provide a list of USENET sites in your area code. Send me a self-addressed,
- stamped envelope (my address is in the bio attached to this column). Those
- living in major metropolitan areas, please include two stamps on your letter.
- Note: I can only offer this service for US area codes. If you have net access,
- but need a news neighbor, I will also reply to Electronic Mail asking for
- nearby news sites.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Code Capsules
-
-
- Time and Date Processing in C
-
-
-
-
- Chuck Allison
-
-
- Chuck Allison is a software architect for the Family History Department of the
- Church of Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake
- City. He has a B.S. and M.S. in mathematics, has been programming since 1975,
- and has been teaching and developing in C since 1984. His current interest is
- object-oriented technology and education. He is a member of X3J16, the ANSI
- C++ Standards Committee. Chuck can be reached on the Internet at
- allison@decus.org, or at (801)240-4510.
-
-
- Most operating systems have some way of keeping track of the current date and
- time. ANSI C makes this information available in various formats through the
- library functions defined in time.h. The time function returns a value of type
- time_t (usually a long), which is an implementation-dependent encoding of the
- current date and time. You in turn pass this value to other functions which
- decode and format it.
- The program in Listing 1 uses the functions time, localtime and strftime to
- print the current date and time in various formats. The localtime function
- breaks the encoded time down into
- struct tm
- {
- int tm_sec; /* (0 - 61) */
- int tm_min; /* (0 - 59) */
- int tm_hour; /* (0 - 23) */
- int tm_mday; /* (1 - 31) */
- int tm_mon; /* (0 - 11) */
- int tm_year; /* past 1900 */
- int tm_wday; /* (0 - 6) */
- int tm_yday; /* (0 - 365) */
- int tm_isdst; /* daylight
- savings
- flag */
- };
- local time overwrites a static structure each time you call it, and returns
- its address (therefore only one such structure is available at a time in a
- program without making an explicit copy). The ctime function returns a pointer
- to a static string which contains the full time and date in a standard format
- (including a terminating newline). strftime formats a string according to user
- specifications (e.g., %A represents the name of the day of the week). See
- Table 1 for the complete list of format descriptors.
-
-
- Time/Date Arithmetic
-
-
- You can do time/date arithmetic by changing the values in a tm structure. The
- program in Listing 2 shows how to compute a date a given number of days in the
- future, as well as the elapsed execution time in seconds. Note the optional
- alternate syntax for the time function (the time_t parameter is passed by
- reference instead of returned as a value). The mktime function alters a tm
- structure so that the date and time values are within the proper ranges, after
- which the day-of-week (tm_wday) and day-of-year (tm_yday) fields are updated
- accordingly. mktime brings the date and time values in the tm structure into
- their proper ranges, and updates the day of week (tm-wday) and day of year
- (tm-yday) values accordingly. This occurs when a date falls outside the range
- that your implementation supports. My MS-DOS-based compiler, for example,
- cannot encode dates before January 1, 1970, but VAXC can process dates as
- early as the mid-1800s. The asctime function returns the standard string for
- the time represented in tm parameter (so ctime (&tval) is equivalent to
- asctime (localtime(&tval)). The function difftime returns the difference in
- seconds between two time_t encodings as a double.
- If you need to process dates outside your system's range or calculate the
- interval between two dates in units other than seconds, you need to roll your
- own date encoding. The application in Listing 3 through Listing 5 shows a
- technique for determining the number of years, months and days between two
- dates, using a simple month-day-year structure. It subtracts one date from
- another, much as you might have done in elementary school (i.e., it subtracts
- the days first, borrowing from the month's place if necessary, and so on).
- Note that leap years are taken into account. For brevity, the date_interval
- function assumes that the dates are valid and that the first date entered
- precedes the second. Following the lead of the functions in time.h, It returns
- a pointer to a static Date structure which holds the answer.
-
-
- File Time/Date Stamps
-
-
- Most operating systems maintain a time/date stamp for files. At the very
- least, you can find out when a file was last modified. (The common make
- facility uses this information to determine if a file needs to be recompiled,
- or if an application needs to be relinked). Since file systems vary across
- platforms, there can be no universal function to retrieve a file's time/date
- stamp, so the ANSI standard doesn't define one. However, most popular
- operating systems (including MS-DOS and VAX/VMS) provide the UNIX function
- stat, which returns pertinent file information, including the time last
- modified expressed as a time_t. The program in Listing 6 uses stat and
- difftime to see if the file time1.c is newer than (i.e., was modified more
- recently than) time2.c.
- If you need to update the time/date stamp of a file to the current time,
- simply overwrite the first byte of a file with itself. Although the contents
- haven't changed, your file system will think it has, and will update the
- time/date stamp accordingly. (Know your file system! Under VAX/VMS, you get a
- newer version of the file, while the older version is retained). This is
- sometimes called "touching" a file. The implementation of touch in Listing 7
- creates the file if it doesn't already exist. Note that the file is opened in
- "binary" mode (indicated by the character b in the open mode string--I'll
- discuss file processing in detail in a future capsule).
- Table 1 Format descriptors for strftime
- Code Sample Output
- ---------------------------------------------
- %a Wed
- %A Wednesday
- %b Oct
- %B October
- %c Wed Oct 07 13:24:27 1992
- %d 07 (day of month [01-31])
- %H 13 (hour in [00-23])
- %I 01 (hour in [01-12])
- %j 281 (day of year [001-366])
-
- %m 10 (month [01-12])
- %M 24 (minute [00-59])
- %p PM
- %S 27 (second [00-59] )
- %U 40 (Sunday week of year [00-52])
- %w 3 (day of week [0-6])
- %W 40 (Monday week of year [00-52])
- %x Wed Oct 7, 1992
- %X 13:24:27
- %y 92
- %Y 1992
- %Z EDT (daylight savings indicator)
-
- Listing 1 time1.c -- prints the current date and time in various formats
- #include <stdio.h>
- #include <time.h>
-
- #define BUFSIZE 128
-
- main()
- {
- time_t tval;
- struct tm *now;
- char buf[BUFSIZE];
- char *fancy_format =
- "Or getting really fancy:\n"
- "%A, %B %d, day %j of %Y.\n"
- "The time is %I:%M %p.";
-
- /* Get current date and time */
- tval = time(NULL);
- now = localtime(&tval);
- printf("The current date and time:\n"
- "%d/%02d/%02d %d:%02d:%02d\n\n",
- now->tm_mon+1, now->tm_mday, now->tm_year,
- now->tm_hour, now->tm_min, now->tm_sec);
- printf("Or in default system format:\n%s\n",
- ctime(&tval));
- strftime(buf,sizeof buf,fancy_format,now);
- puts(buf);
-
- return 0;
- }
-
- /* Output
- The current date and time:
- 10/06/92 12:58:00
-
- Or in default system format:
- Tue Oct 06 12:58:00 1992
-
- Or getting really fancy:
- Tuesday, October 06, day 280 of 1992.
- The time is 12:58 PM.
- */
-
- /* End of File */
-
-
-
- Listing 2 time2.c -- shows how to compute a date a given number of days in the
- future, as well as the elapsed execution time in seconds
- #include <stdio.h>
- #include <stdlib.h>
- #include <time.h>
-
- main()
- {
- time_t start, stop;
- struct tm *now;
- int ndays;
-
- /* Get current date and time */
- time(&start);
- now = localtime(&start);
-
- /* Enter an interval in days */
- fputs("How many days from now? ",stderr);
- if (scanf("%d",&ndays) !=1)
- return EXIT_FAILURE;
- now->tm_mday += ndays;
- if (mktime(now) != -1)
- printf("New date: %s",asctime(now));
- else
- puts("Sorry. Can't encode your date.");
-
- /* Calculate elapsed time */
- time(&stop);
- printf("Elapsed program time in seconds: %f\n",
- difftime(stop,start));
-
- return EXIT_SUCCESS;
- }
-
- /* Output
- How many days from now? 45
- New date: Fri Nov 20 12:40:32 1992
- Elapsed program time in seconds: 1.000000
- */
-
- /* End of File */
-
-
- Listing 3 date.h -- a simple date structure
- struct Date
- {
- int day;
- int month;
- int year;
- };
- typedef struct Date Date;
-
- Date* date_interval(const Date *, const Date *);
- /* End of File */
-
-
- Listing 4 date_int.c -- computes time interval between two dates
- /* date_int.c: Compute duration between two dates */
-
- #include "date.h"
-
-
- #define isleap(y) \
- ((y)%4 == 0 && (y)%100 != 0 (y)%400 == 0)
-
- static int Dtab [2][13] =
- {
- {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
- };
-
- Date *date_interval(const Date *d1, const Date *d2)
- {
- static Date result;
- int months, days, years, prev_month;
-
- /* Compute the interval - assume d1 precedes d2 */
- years = d2->year - d1->year;
- months = d2->month - d1->month;
- days = d2->day - d1->day;
-
- /* Do obvious corrections (days before months!)
- *
- * This is a loop in case the previous month is
- * February, and days < -28.
- */
- prev_month = d2->month - 1;
- while (days < 0)
- {
- /* Borrow from the previous month */
- if (prev_month == 0)
- prev_month = 12;
- --months;
- days += Dtab[isleap(d2->year)][prev_month--];
- }
-
- if (months < 0)
- {
- /* Borrow from the previous year */
- --years;
- months += 12;
- }
-
- /* Prepare output */
- result.month = months;
- result.day = days;
- result.year = years;
- return &result;
- }
- /* End of File */
-
-
- Listing 5 tdate.c -- illustrates the date_interval function
- /* tdate.c: Test date_interval() */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include "date.h"
-
- main()
-
- {
- Date d1, d2, *result;
- int nargs;
-
- /* Read in two dates - assume 1st precedes 2nd */
- fputs("Enter a date, MM/DD/YY> ",stderr);
- nargs = scanf("%d/%d/%d%*c", &d1.month,
- &d1.day, &d1.year);
- if (nargs != 3)
- return EXIT_FAILURE;
-
- fputs("Enter a later date, MM/DD/YY> ",stderr);
- nargs = scanf("%d/%d/%d%*c", &d2.month,
- &d2.day, &d2.year);
- if (nargs != 3)
- return EXIT_FAILURE;
-
- /* Compute interval in years, months, and days */
- result = date_interval(&d1, &d2);
- printf("years: %d, months: %d, days: %d\n",
- result->year, result->month, result->day);
- return EXIT_SUCCESS;
-
- }
- /* Sample Execution:
- Enter a date, MM/DD/YY> 10/1/51
- Enter a later date, MM/DD/YY> 10/6/92
- years: 41, months: 0, days: 5 */
- /* End of File */
-
-
- Listing 6 ftime.c -- determines if time1.c is newer than time2.c
- /* ftime.c: Compare file time stamps */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/stat.h>
- #include <time.h>
-
- main()
- {
- struct stat fs1, fs2;
-
- if (stat("time1.c",&fs1) == 0 &&
- stat("time2.c",&fs2) == 0)
- {
- double interval =
- difftime(fs2.st_mtime,fs1.st_mtime);
-
- printf("time1.c %s newer than time2.c\n",
- (interval < 0.0) ? "is" : "is not");
- return EXIT_SUCCESS;
- }
- else
- return EXIT_FAILURE;
- }
- /* Output
- time1.c is not newer than time2.c */
- /* End of File */
-
-
-
- Listing 7 touch.c -- updates time stamp by overwriting old file or creating
- the file if it doesn't exist
- /* touch.c: Update a file's time stamp */
-
- #include <stdio.h>
-
- void touch(char *fname)
- {
- FILE *f = fopen(fname,"r+b");
- if (f != NULL)
- {
- char c = getc(f);
- rewind(f);
- putc(c,f);
- }
- else
- fopen(fname,"wb");
-
- fclose(f);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- CUG New Releases
-
-
- Dynamic Link Library for Text Windows
-
-
-
-
- Steven Graham
-
-
- Steven K. Graham has worked for Hewlett-Packard, served on the Faculty at
- UMKC, and is currently a Senior Engineer with CSI.
-
-
- TextView, CUG375 (one disk) is a free Dynamic Link Library (DLL) for
- simplified manipulation of text windows under Microsoft Windows, written by
- Alan Phillips (Lancaster, United Kingdom). Alan Phillips is a systems
- programmer at the Lancaster University Computer Centre, where he writes UNIX
- communications software. Alan can be contacted at a.phillips@lancaster.ac.uk.
- Similar to WinDosIO, a previous CUG volume, TextView handles the details of
- window operations, permitting users to call functions for writing text (such
- as TVOutputText) in much the same way printf would be called in an MS-DOS
- application (with the exception of an extra parameter to identify the window
- where the text will be written). TextView can create multiple, independent
- windows that can be resized, minimized, maximized, and scrolled horizontally
- and vertically. A thoroughly-documented demonstration program illustrates the
- use of TextView windows to provide tracing and debugging information during
- application development. TextView requires the use of a compiler (such as
- Microsoft C) which can generate Windows code. The TextView volume includes a
- readable and carefully-organized 42-page manual. The TextView functions follow
- the same conventions as the Windows API, and the manual uses the same layout
- as the Microsoft Windows Programmer's Reference. TextView function names all
- begin with TV. The functions use Pascal calling conventions and must be
- declared FAR.
- Function prototypes are contained in the file textview.h. Adding this file to
- your source selects the right calling mode and performs necessary casts to far
- pointers. The TextView import library textview.lib must be included in the
- list of libraries to be linked. The stack size required for your application
- may need to be increased. Some functions in the TextView import library must
- be statically linked.
-
-
- Dual-Mode tools
-
-
- Volume 376 (four disks) adds OS/2 tools to the CUG library. Martii Ylikoski,
- of Helsinki, Finland, has provided a large number of free, dual-mode tools
- that support both OS/2 and MS-DOS. The tools are remarkably well packaged.
- Each tool includes accompanying source, makefile, documentation, and demo
- files, along with files (.bat or .cmd) to install and uninstall the tools. For
- OS/2 there is also a tools2.inf file, in the standard format for OS/2 help
- files. Full source code is included, generally with a single file per utility.
- The makefiles (<toolname>.mak) indicate the required dependencies. A library
- was used in building the tools, and is included in two forms--mtoolsp.lib for
- protected mode and mtoolsr.lib for real mode. No documentation for the
- libraries exists, other than the examples of function use provided in the
- source code for the tools. The collection of 54 utilities provides a variety
- of functions such as: find file (ff), disk usage (du), head, tail, set
- priority (setprty), touch, cat, and scan (a find-like utility that searches
- for files and executes commands once the files are found).
-
-
- Hard-To-Find Information on Floppy Disks
-
-
- Diskette manipulations are the core of CUG 377 (one disk), provided by Ian
- Ashdown, P. Eng., of West Vancouver. This volume provides a wealth of
- information about diskette device-service routine (DSR) functions. The
- documentation addresses a variety of quirks in diskette access, and provides
- considerable hard-to-find information on floppy diskettes, diskette
- controllers, and the diskette DSR functions. The volume also provides
- extensive example and test routines, with source code (in both C and C++
- versions), for reading, writing, formatting, and verifying almost any IBM
- System 34 format diskette on a PC compatible. The code includes support and
- interface functions that increase the diskette DSR's reliability and provide a
- consistent programming interface across PC platforms. The information was
- largely determined through extensive use of an in-circuit emulator and other
- debugging tools, along with careful study of various machines and various DOS
- and BIOS versions. Given the variety of ROM BIOSes available, and the
- necessity to derive the information by experimentation, the material in this
- volume cannot cover every case, but certainly provides a thorough and careful
- treatment.
-
-
- C++ Matrix Operations
-
-
- From Robert Davies, a consultant and researcher in mathematics and computing
- from New Zealand, formerly with the New Zealand Department of Scientific and
- Industrial Research (DSIR), we get NEWMAT (CUG 378, one disk), a C++ matrix
- package. This volume was written for scientists and engineers who need to
- manipulate a variety of matrices using standard matrix operations. It was
- developed by a scientist (Robert Davies has a Ph.D. from the University of
- California at Berkeley) to support real work. NEWMAT emphasizes operations
- supporting statistical calculations. Functions include least squares,
- linear-equation solve, and eigenvalues.
- Matrix types supported include: Matrix (rectangular matrix),
- UpperTriangularMatrix, LowerTriangularMatrix, DiagonalMatrix, SymmetricMatrix,
- BandMatrix, UpperBandMatrix, LowerBandMatrix, SymmetricBandMatrix, and
- RowVector and ColumnVector (derived from Matrix). Only one element type (float
- or double) is supported. Supported matrix operations include: *, +, --,
- inverse, transpose, conversion between types, submatrix, determinant, Cholesky
- decompositions, Householder triangularization, singular value decomposition,
- eigenvalues of a symmetric matrix, sorting, fast Fourier transform, printing,
- and an interface compatible with Numerical Recipes in C. NEWMAT supports
- matrices in the range of 4x4 to the machine-dependent, maximum array size
- 90x90 double elements or 125x125 float elements for machines whose limit for
- contiguous arrays is 64K. NEWMAT works for very small matrices, but is rather
- inefficient.
- NEWMAT works with Borland and Glockenspiel C++. The version current at this
- writing (NEWMAT03) doesn't work with GNU C++, but a new version (NEWMAT06) is
- expected (by November 1992) that will work with GNU C++. Robert Davies
- suggests the following as criteria for interest in NEWMAT: first, a desire for
- matrix operations expressed as operators; second, a need for various matrix
- types; third, a need for only a single element type; fourth, use of matrix
- sizes between 4x4 and 90x90; and fifth, tolerance for a large and complex
- package. There is a fairly large file documenting the package, which broadly
- addresses issues from particulars of functions and interactions with various
- compilers, through design issues in building a matrix package. If you fit the
- profile described, then NEWMAT may be the matrix tool you need.
-
-
- Archiving and Compression Program
-
-
- The final CUG volume for this month (CUG 379, one disk) is ZOO (version 2.1),
- a file archiving and compression program (standard extension .zoo), written by
- Rahul Dhesi, with assistance from J. Brian Walters, Paul Homchick, Bill
- Davidsen, Mark Alexander, Haruhiko Okumura, Randal L. Barnes, Raymond D.
- Gardner, Greg Yachuk, and Andre Van Dalen. This volume includes C source,
- executable, and documentation. Zoo is used to create and maintain collections
- of files in compressed form. It uses the Lempel-Ziv compression algorithm
- which yields space savings from 20% to 80% depending on the file data. Zoo can
- manage multiple generations of the same file and has numerous options
- accompanied by lengthy descriptions in the manuals. Zoo supports a range of
- hardware and operating systems, and includes makefiles with various options.
- Zoo is part of the GNUish MS-DOS project, an attempt to provide a GNU-like
- environment for MS-DOS, with GNU ports and MS-DOS replacements for non-ported
- GNU software.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- C Programming Guidelines and C ++ Programming Guidelines
-
-
- Dwayne Phillips
-
-
- The author works as a computer and electronics engineer with the U.S.
- Department of Defense. He has a PhD in Electrical and Computer Engineering
- from Louisiana State University. His interest include computer vision,
- artificial intelligence, software engineering, and programming languages.
-
-
- C Programming Guidelines and C++ Programming Guidelines are comprehensive
- style manuals that list specific standards for all aspects of C and C++
- programming. They are good books for any group of programmers trying to work
- together to produce consistently high-quality, portable software. C
- Programming Guidelines covers standard ANSI C while C++ Programming Guidelines
- covers general C++. C++ Programming Guidelines repeats large parts of C
- Programming Guidelines, but it covers C++ specifics and has excellent material
- concerning migrating from C to C++.
-
-
- Purpose
-
-
- These books list coding standards, that is, rules governing the details of how
- to write source code. C and C++ are flexible languages and programmers can
- write correct, working programs using a wide variety of "styles" (including
- the obfuscated style). A coding standard offers rules and guidelines that help
- different developers to produce programs in the same style. The authors give
- three primary reasons for using a coding standard: reliability,
- readability/maintainability, and portability. If programmers follow a sound
- coding standard, the result will be more reliable, readable, and portable
- software. Some programmers argue that while noble, these attributes ruin
- program speed and efficiency. Although a legitimate argument in some special
- situations, long-term performance and monetary costs support the authors'
- reasons for advocating a coding standard.
-
-
- Audience
-
-
- Most C and C++ programmers could use these books. For example, if you are
- starting a software company, these books could aid your quality control
- program. In a university research lab, these books could help standardize the
- work of the beginners. A government employee wanting to specify software
- standards to a contractor could simply hand these over. A programmer working
- alone could use these to minimize the tendency to drift into a personal
- programming style.
- The publisher offers machine-readable versions of both books. This enables you
- to place them online as ready references or fold them into your group's own
- coding standard.
-
-
- Organization
-
-
- Because both books are references, the they are organized along similar lines.
- The chapters contain one- to ten-page sections with the majority being two
- pages long. These sections resemble man pages on UNIX systems. Each section
- has subsections titled STANDARD or GUIDELINE, JUSTIFICATION, EXAMPLE,
- ALTERNATIVES, and REFERENCES. The authors use STANDARDs as rules that you must
- follow while GUIDELINEs are suggestions.
- The subsections contain short, direct paragraphs about the subject at hand
- that resemble cohesive subroutines. This style of writing is appropriate for
- such reference books. These are not the types of books you cuddle up with in
- front of the fire and read from cover to cover. Nevertheless, the authors do
- an excellent job of tying all the sections together around the three primary
- reasons for using a coding standard.
- Sometimes the authors are too efficient. They often refer to other books
- published by Plum Hall for details. I prefer reading the information in the
- book at hand over holding my place with one finger while thumbing through a
- different book.
- The programming guidelines in the books fall into one of two categories. The
- first category comprises syntax rules (use of tabs, spaces, lines per
- function, lines per file). The second category comprises style rules
- (functional cohesiveness, functions per file, program and problem structure).
- This may seem to be a lengthy statement of the subject since most books on
- programming devote only one chapter to programming style. The subject,
- however, is more complicated than most people realize and does require one
- book per language.
- One criticism of the text is a shortage of code examples. The authors do give
- code examples, but not in every section. Programmers sometimes do not
- translate words into action well. They do, however, understand code and I wish
- the these books had more source code.
-
-
- Highlights
-
-
- There are several sections in each deserving special attention.
-
-
- C Programming Guidelines
-
-
- In C Programming Guidelines, the author discusses byte order and how this
- affects program portability. When you venture outside the PC world, you find
- the different machines store numbers with a different byte order. This is
- especially true with floating-point numbers. C Programming Guidelines suggests
- converting data to a canonical form before storing to disk and converting back
- immediately after reading from disk.
- One section discusses the different methods of placing braces ({}) in C
- programs. The author gives three different methods with many examples. This
- may sound trivial, but the author claims this is a highly-charged issue with
- most programmers.
- Another section states how to use a local standard header for a project to
- improve portability. I have been the victim of this mistake. Someone once gave
- me a program that had 40 C files and each started with #include
- /usr/users/dave/all.h. When we moved the program to a new environment we had
- to replace each of these with #include all.h. This may sound trivial, but it
- can be very time-consuming.
- Plum advocates the use of code reviews to ensure correctness and portability.
- Plum describes a "first-order correctness review" that produces a list of test
- cases for the software. You use these for first-order correctness testing and
- later for regression testing.
- A final section to note covers the environment of Standard C. In this section
- Plum defines a "strictly portable" C program as producing "identical behavior
- in any Standard C environment." Strictly portable programs must be careful of
- word sizes and number representations. Plum lists several dozen rules that
- make your program strictly portable.
- C Programming Guidelines concludes with a description of all the library
- functions in Standard C. This will not replace a vendor's reference manual. It
- is, however, a good, concise description of the standard library.
-
-
- C++ Programming Guidelines
-
-
- C++ Programming Guidelines repeats several dozen sections from C Programming
- Guidelines. Nevertheless, C++ Programming Guidelines contains significant
- additions about C++ items.
- C++ Programming Guidelines dedicates considerable attention to migrating from
- C to C++, a very important topic given that most of us C programmers will be
- using C++ in five years if not sooner. We would all like the luxury of not
- having to produce anything of value for six months while we learn C++, but
- that will not happen. We must remain productive while migrating.
-
- The authors describe three levels in the migration: Typesafe C, Object-Based
- C++, and Full C++. Typesafe C is the common subset of Standard C and C++. You
- should use it when the target platform currently supports C and will support
- C++ by the time you field your system. Object-based C++ is a form of
- beginner's C++ using basic classes and other C++ extensions. It is not full
- C++ in that you do not use class derivation and virtual functions. The authors
- describe these levels amply and as a programmer and manager of programmers I
- endorse their slow but sure approach to this problem.
- C++ Programming Guidelines does not dodge the issue of portable C++ programs.
- The ANSI C standard makes portable C possible. C++ does not yet share such a
- standard, so portable C++ programs are more difficult to realize. The authors
- discuss how to work around this throughout the text and recommend using Ellis
- and Stroustrup's text as a de facto standard (see reference below).
- C++ Programming Guidelines has several sections that discuss code reuse and
- management of reusable code. One of the selling points of full C++,
- object-oriented programming is reusable code. You do not receive the full
- advantages of reusable code without following certain rules in coding and
- managing code. The authors do a commendable job of explaining this subject and
- sprinkling tips through the entire book.
- C++ Programming Guidelines has one short but vital page on the importance of
- design in object-oriented, C++ programming. In C++ you create base classes
- from which you derive many other classes. If you change a base class, then
- this change can ripple through many derived classes and you have a quandary.
- The solution is design. You must spend a much larger proportion of time and
- effort when designing your base classes. You need more reviews and more
- outsiders asking questions about your design. The result is shorter overall
- development time and higher quality software.
- The authors conclude C++ Programming Guidelines with two benchmark programs.
- These programs produce a table of execution times for basic operations in C
- and C++. They also provide you with a sense of execution times for basic
- operations so you can estimate program performance.
-
-
- Conclusion
-
-
- These are working books that spell out coding standards. A coding standard
- will help improve the quality and portability of your group's software by
- helping you to have consistent source code in all your projects. If your group
- does not have a coding standard, it needs one. If your group needs a coding
- standard, start with one of these books. You may not agree with everything in
- the books, but that's not necessary. Use the books as a foundation and cut,
- paste, add, and delete until you have a standard that will work for you. This
- will improve the quality of your software, and we all need to do that.
- Reference
- Ellis, Margaret A. and Bjarne Stroustrup. 1990. The Annotated C++ Reference
- Manual. Reading, MA: Addison-Wesley.
- Book Information
- C Programming Guidelines, 2nd Edition
- by Thomas Plum
- Price: $30.00
- ISBN: 0-911537-07-4
- C++ Programming Guideliness
- by Thomas Plum and Dan Saks
- Price: $34.95
- ISBN: 0-911537-10-4
- Publisher: Plum Hall Inc.
- To order: (913) 841-1631 (books),
- (808) 885-6663 (machine-readable text)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- Just two issues back, I was telling you about all the exciting things going on
- in the international standards arena, at least as related to the C programming
- language. (See the Editor's Forum, CUJ November 1992.) The past few weeks have
- seen a different kind of excitement welling up in the ANSI arena. I fear the
- result won't be nearly as positive, however.
- Standards, as you probably know, are produced largely by the effort of
- volunteers. Many of us who developed the C standard put in half a year of
- meeting time over a period of six years, not to mention a comparable
- investment in work between meetings. Throw in the cost of travel and lodging
- for two dozen meetings and you're looking at a serious investment in both time
- and money. (Multiply that by 50 or 60 active participants and you begin to
- appreciate the staggering cost of developing a programming language standard.)
- For the privilege of doing all this grunt work, we pay an annual fee to CBEMA,
- the secretariat to the ANSI-authorized committee X3 that oversees our efforts.
- That fee has climbed over the years to a hefty $300 per year. Not outrageous
- for those of us whose companies pick up the tab, but noticeable. As an
- independent, I shelled out $650 last year for membership in X3J11 (C) and
- X3J16 (C++). The extra $50 encourages CBEMA to distribute documents for X3J11.
- But now CBEMA has begun issuing a flurry of additional bills, retroactive to
- last year. The typical charge is an additional $300 per year for each X3
- committee we participate in -- ostensibly to cover the costs of our
- international activities. I've been asked to pay an additional $1,200 for a
- variety of reasons. I can look forward to an additional $1,850 in bills for
- 1993 before you even read these words. Needless to say, a number of us
- standards drones are more than a little upset.
- I can't judge whether these additional fees are reasonable, by some metric. I
- do know that they were decided with no public debate and they were made
- retroactive for most of a year. That is not a good way to treat volunteers,
- techie or otherwise. The short term distraction is trifling compared to the
- loss of talent this can cause in the longer term. And it makes us all leery
- about what might happen next.
- For my part, I am resigning a number of posts that I have long valued. I shall
- continue as Convenor of WG14 -- I feel a responsibility for the stewardship of
- C for at least the near future. I plan to keep up with the X3 activities in C
- and C++. But I don't expect to travel quite as much or work quite as hard in
- the standards arena from now on. I hope that not too many others feel pinched
- enough to do likewise.
- P.J. Plauger
- pjp@plauger.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- TauMetric Upgrades C++ Compilers for DEC/MIPS
-
-
- TauMetric Corporation announced upgrades to its Oregon C++ Development Systems
- for SPARC and for DECstations and DECsystems workstations. The Oregon C++
- system provides direct code generation, C++ level 2.1 compatibility, and a C++
- debugger. The Oregon C++ compiler includes modes for both ANSI C and K&R C.
- Oregon C++ generates object code directly and the system provides a revised
- version of Oregon Debugger (ODB) for C++. Major new features include: nested
- class and type scopes, operator delete [] syntax, and overloading postfix ++
- and --.
- Oregon C++ is available for VAX/VMS, SPARC, MIPS (DECstation), HP 9000/300,
- and Sun-3 Systems. Contact TauMetric Corporation, 8765 Fletcher Parkway, Suite
- 301, La Mesa, CA 91942, (619) 697-7607, or (800) 874-8501; FAX: (619)
- 697-1140.
-
-
- TFS Releases C++ Class Library and Hypertext Help System
-
-
- The Friendly Solutions (TFS) Company has released C*Drive++, a C++ class
- library, and FASTVIEW, a hypertext help system and help text compiler.
- C*Drive++ contains low-level routines for windowing, pop-up and pull-down menu
- functions, form entry, and printer control. The package also includes classes
- for screen I/O, mouse/ keyboard support, string handling, color control, and
- directories. Source code is provided. The class library is compatible with
- Borland and Microsoft compilers. FASTVIEW allows developers to create TSR help
- text. The help engine works with C*Drive++ or as a stand alone system. Source
- for the FASTVIEW engine is included.
- C*Drive++ is priced at $140, and FASTVIEW (with royalty free distribution) is
- priced at $199. Contact The Friendly Solutions Company, 6309 Chimney Wood Ct.,
- Alexandria, VA 22306, (703) 765-0654.
-
-
- Quarterdeck Provides X11 and Motif Software Development Toolkits for
- DESQview/X
-
-
- Quarterdeck Office Systems, Inc. has announced its DESQview/X X11 and Motif
- Software Development Toolkits. Also available at no charge with the toolkits
- is the GNU C/C++ compiler. With the X11 Toolkit, developers will be able to:
- port X clients from other X Window System environments to DESQview/X, create
- new X clients, perform network-independent communication between machines,
- access Adobe Type Manager to use Type 1 scalable fonts, and customize
- DESQview/X through utility programs and configuration files.
- The DESQview/X X11 Starter Toolkit, for use with the GNU C/C++ compiler only,
- includes the X11 R4 programming libraries, the DESQview/X system library with
- Berkeley Socket Interface, sample programs, make files for GNU, the DESQview/X
- Road-map documentation, and the GNU C/C++ compiler. The starter kit sells for
- $50. The complete DESQview/X X11 Toolkit, priced at $750, includes (in
- addition): make files and library support for Microsoft C, Borland C++,
- Zortech C++, Rational Instant C, Watcom C/386, and MetaWare High C compilers,
- O'Reilly X Reference and Programming Guides (Volumes 1, 2, 4, and 5), Rational
- Systems DOS/4GX DOS extender and tools, Instant C development environment,
- Oxygen, and Quarterdeck's Developer Passport Support. The toolkit components
- are available separately.
- Quarterdeck has made the Quarterdeck X libraries for DESQview/X available to
- GNU for free distribution with its C/C++ compiler. For information, contact
- Quarterdeck Office Systems, Inc., 150 Pico Boulevard, Santa Monica, CA 90405,
- (800) 354-3222 or (310) 392-9851, FAX: (310) 399-3802.
-
-
- Innovative Data Solutions Upgrades PARAGen Code Generator for PARADOX
-
-
- Innovative Data Solutions, Inc., has upgraded its PARAGen code generator for
- the PARADOX engine. PARAGen creates C, C++, standard Pascal, object-oriented
- Pascal, and Pascal for Windows (TPW) code for accessing PARADOX database
- applications. PARAGen creates OOP code with classes and member functions that
- manipulate those classes. PARAGen creates an internal record structure that
- represents the actual structure of the PARADOX table, enabling record and
- search operations to populate the structure and access values stored in the
- transfer buffer immediately. Optimization, error checking, and generated code
- style are configurable. Users can create and examine PARADOX tables from
- within PARAGen, allowing productive work without the neeed to load PARADOX on
- the machine. PARAGen's look and feel has been changed to conform to the
- CUA/SAA compliant interface standard.
- PARAGen retails for $179 and comes in a DOS specific version and a native
- Windows 3.x version. Contact Innovative Data Solutions, Inc., 1757 Eastwood
- Court, Suite 9, Schaumburg, IL 60195, (708) 882-3713.
-
-
- Non Standard Logics Implements XFaceMaker X/Motif GUI Builder for SCO Open
- Desktop
-
-
- Non Standard Logics SA (NSL) has implemented its XFaceMaker (XFM) interactive
- graphical interface builder for The Santa Cruz Operation's (SCO) Open Desktop.
- For information, contact the US office of NSL at 99 Bedford Street, Boston, MA
- 02111, (617) 482-6393; FAX: (617) 482-0357. NSL European headquarters offices
- are at 57-59 Rue Lhomond, 75005 Paris France; telephone (33-1) 43 36 77 50,
- FAX: (33-1) 43 36 59 78.
-
-
- Quarterdeck Adds DOS Protected Mode Interface (DPMI) Host to QEMM-386
-
-
- Quarterdeck Office Systems, Inc., has announced their DPMI Host, which
- supports virtual memory and provides a companion product to QEMM-386, their PC
- memory-management software. Quarterdeck's DPMI Host is compatible with
- Microsoft C/C++, Borland C++, and Intel's Code Builder Kit. The Quarterdeck
- DPMI Host requires QEMM-386, DESQview-386, or DESQview/X. The DPMI Host
- supports running multiple DPMI programs inside Quarterdeck's DESQview
- products.
- Quarterdeck DPMI Host is a full implementation of versoin 0.9 of the DPMI
- specification, including DOS extensions. Quarterdeck will make the DPMI Host
- available to registered Quarterdeck users at no cost, through the company's
- user BBS: (310) 314-3227. Online documentation is available. QEMM users can
- also download the DPMI Host from various online services. Users who want disks
- and hard copy documentation can order DPMI Host directly for $30 (or free to
- Quarterdeck Passport Support Subscribers). For information, contact
- Quarterdeck Office Systems, Inc., 150 Pico Boulevard, Santa Monica, CA 90405,
- (800) 354-3222 or (310) 392-9851; FAX: (310) 399-3802.
-
-
- Algorithmic Solutions Releases IFF Indexed File Management Library for UNIX
- System V
-
-
- Algorithmic Solutions has released IFF,the Indexed File Management Library for
- C programming environments. IFF is a C library that supports indexed files,
- with up to 16 keys, both unique and non-unique keys, seven key data types,
- user-defined key data type support, segmented keys with up to 10 components,
- file and record locking, and automatic, internal lock management that enables
- both single and multiuser access. IFF uses a single file that incorporates
- both data and indexing structure, avoiding the need for special utilities for
- copying or moving files.
- IFF prices begin at $595, and SCO UNIX, SPARC and HP-UX are supported. Contact
- Algorithmic Solutions, P.O. Box 382, Simpsonville, MD 21150-0382, (301)
- 421-0134; FAX: (301) 942-1452.
-
-
-
- Software Through Pictures Ships on SCO Open Desktop Release 2.0
-
-
- Interactive Development Environments, Inc. (IDE) has announced the shipment of
- Software through Pictures Integrated Structured Environment (ISE) Release 4.2D
- for SCO Open Desktop Release 2.0. IDE's Software through Picture is a
- multiuser, integrated CASE environment that supports development for technical
- and commercial applications.
- A single license for Software through Pictures ISE for SCO Open Desktop ranges
- from $5,000 to $21,000, depending on the configuration. Suggested hardware
- configuration includes a 33MHz 486 PC, 300MB hard drive, and 16MB RAM. For
- information, contact Interactive Development Environments, 595 Market Street,
- 10th Floor, San Francisco, CA 94105, (800) 888-IDE1, (415) 543-0900; FAX:
- (415)543-0145.
-
-
- Sequiter Software Announces Clipper to C Translator
-
-
- Sequiter Software has released CodeTranslator 2.0, which translates Clipper
- '87 and dBASE III PLUS applications into C, and has announced plans for a
- November release of CodeBase 5.0. CodeTranslator generates ANSI compliant C
- code, designed to correspond to the original dBASE/Clipper application,
- preserving specific variable and function names, as well as familiar commands
- like goto and skip. CodeBase 5.0 is a C/C++ library which has file and
- multiuser compatibility with dBASE, FoxPro, and Clipper. New CodeBase features
- include bit optimization technology (for faster queries) and CodeReporter, a
- Microsoft Windows report design tool.
- CodeTranslator retails for $245 and CodeBase retails for $395. Contact
- Sequiter Software, Inc., Suite 209, 9644 - 54 Avenue, Edmonton, Alberta,
- Canada, T6E 5V1, (403) 437-2410, FAX: (403) 436-2999.
-
-
- RTXC and PC/104 Unite for Real-time Embedded Applications
-
-
- A. T. Barrett & Associates has announced real-time kernel support for the
- PC/104 Consortium with its real-time executive, RTXC. RTXC is a real-time
- multitasking executive for use in embedded systems requiring a deterministic
- design with pre-emptive scheduling for event-driven operation. RTXC is written
- primarily in ANSI C and supports all the standard PC-compatible resources.
- RTXC provides a library of services including task, memory, and resource
- management, intertask communication and synchronization, and timer support.
- RTXC is available in three configurations, Basic Library (26 kernel services),
- Advanced Library (38 kernel services), or Extended Library (55 kernel
- services). Source code is supplied and there are no royalties for continued
- use in multiple products. For information, contact A.T. Barett & Associates,
- 11501 Chimney Rock, Suite R, Houston, TX 77035, (713) 728-9688.
-
-
- AccSys for dBASE Version 2 Provides Faster and More Flexible API
-
-
- Copia International, Ltd., has released version 2.0 of AccSys for dBASE. Their
- announcement stated that the new version runs five times faster than previous
- versions, for multiuser applications with index files. Version 2 adds high
- level functions that can read, write, update, pack, index, and re-index the
- database. The AccSys for dBASE Expression Analyzer allows programmers to read,
- write, and update "foreign databases". Version 2 adds dEparse, dErun, and
- dEeval, enabling AccSys for dBASE to parse, evaluate, and interpret dBASE key
- and index expression. Over 70 dBASE look-alike functions have also been added.
- AccSys for dBASE is priced at $395 or $995 with source. Contact Copia
- International, Ltd., 1342 Avalon Court, Wheaton, IL 60187, (708) 682-8898;
- FAX: (708) 665-9841.
-
-
- Symantec Announces MultiScope Debuggers for Borland and Microsoft C++
- Applications
-
-
- Symantec Corporation has announced version 2.0 of its MultiScope Debuggers,
- which supports Borland C++ and Microsoft C/C++ (6.0 and 7.0) for programming
- Windows and DOS applications. The MultiScope Debuggers offer a Windows-hosted
- (3.1) graphical user interface. The CommandBar provides quick access to
- frequently used debugging commands. The DOS debuggers can be Windows-hosted
- and allow debugging of DOS applications in a DOS window. A Symantec
- representative described the MultiScope Debuggers as designed specifically for
- debugging C++ code and noted the inclusion of features such as class browsing,
- automatic object mapping, object-oriented breakpoints, and name unmangling.
- MultiScope includes both Run-Time Debugging for controlling program execution
- and a Crash Analyzer System for examining the aftermath of program crashes.
- MultiScope Debuggers for Windows is priced at $379. The DOS version runs $179.
- For information, contact Symantec Corporation, 10201 Torre Avenue, Cupertino,
- CA 95014-2132, (800) 999-8846 or (408) 253-9600; FAX: (408) 253-4092; Telex:
- 9103808778.
-
-
- EnQue Upgrades UltraWin Text Windowing Library
-
-
- EnQue Software has upgraded UltraWin 2.10, its text windowing library, whose
- features include unlimited overlapping windows, background printing, PC timer
- control, mouse and graphic support, and enhanced data entry capabilities,
- along with a hypertext help engine and an EGA/VGA font editor. EnQue has also
- released InTUItion 1.10, a textual user-interface library which includes a
- tool-supporting interactive interface design. EnCom 1.00, a COMM library is a
- new introduction from EnQue, with interrupt driven transmit and receive,
- support for COMM ports one through four, CRC and message formatting
- facilities.
- UltraWin, EnCom, and InTUItion, with small model libraries are available free
- from various online services, or by sending $2 for shipping and handling to
- EnQue. Complete source with large model libraries and printed manuals are
- available for EnCom ($100), UltraWin ($100), or both UltraWin and InTUItion
- ($200). Contact EnQue Software, Route 1, Box 116C, Pleasant Hill, MO 64080,
- Voice/FAX: (816) 987-2515; EnQue BBS: (816) 353-0991.
-
-
- Library Technologies Releases Version 3.0 of C-Heap Memory Management Library
-
-
- Library Technologies has released version 3.0 of its C-Heap memory management
- library for Microsoft and Borland C/C++ compilers. C-Heap now supports the
- allocation of memory from upper memory blocks (UMBs) through malloc, either
- via MS-DOS calls or the XMS driver transparently. C-Heap can use 64K of
- expanded (EMS) memory as heap space. Version 3.0 of C-Heap adds local heaps to
- its list of memory tools. Local heaps are similar to Microsoft-based heaps,
- but are not restricted to 64K. Any block of heap memory of any size can be
- used as a local heap and allocated from as required. If the block is
- associated with a particular object, the block may be swapped out of memory to
- expanded, extended, or virtual memory as required. C-Heap is priced at $229,
- including source. Contact Library Technologies, P.O. Box 56031, Madison, WI
- 53705-9331, (800) 267-6171.
-
-
- SilverWare Inc. Adds COM Port Control to dBASE IV
-
-
- SilverWare, Inc., has released SilverComm 3.00 (SPCS), a communications
- library (written in C and assembly) that provides xBASE programmers with
- access to COM port control. xBASE programs can auto detect and invoke the
- 16550 FIFO UART, increasing data throughput and reliability at baud rates up
- to 115K. SilverComm 3.0 includes support for the IBM PS/2 Dual Async Multi COM
- Adapter and shared interrupt (IRQ) handling on Micro Channel adapters. Other
- new features include support for high number interrupts on multi COM boards,
- YMODEM Batch file transfer protocol. SilverComm also supports XMODEM, YMODEM
- and ASCII transfers, ANSI and TTY terminal emulations, and AST or STB multi
- COM boards. SilverComm 3.0 is priced at $249 (upgrages $89) and includes
- complete C and assembly source code. Contact SilverWare, Inc., 3010 LBJ
- Freeway, Suite 740, Dallas, TX 75234, (214) 247-0131, FAX: (214) 406-9999.
-
-
- Pryor and Pryor Releases Communications Libraries
-
-
- Pryor and Pryor, Inc., has released three communications products for C and
- assembly programmers. The SPI library is a communications library module for
- serial ports which supports sharing IRQs. SPI and a mouse can coexist on
- either IRQ3 or IRQ4 at rates up to 4,800 baud. The FCL library is a
- file-compression module that can be used in dynamically compressing
- file-transfer programs as well as simple disk file-compression programs. The
- SDA serial Data Analyzer program converts a PC into a tool for monitoring and
- displaying data flows over an RS232 link. The user may select display modes,
- capture and store data in real-time, playback captured data, and print
- information. Prices range from $49 (SPI, FCL) to $229 (SDA). For information,
- contact Pryor and Pryor, Inc., 602 -- 1230 Comox Street, Vancouver, B.C.,
- Canada, Voice/FAX: (604) 669-2609.
-
-
-
- MetaCard Upgrades UNIX Development Environment
-
-
- MetaCard Corporation has upgraded their hypermedia and rapid application
- development environment, MetaCard 1.2. The release is Motif-compliant and will
- be supported on nine UNIX/X11 platforms, including new support for Silicon
- Graphics IRIS, Data General AViiON, and IBM RS/6000 workstations. A C-based
- extension allows MetaCard to be used for computationally-intensive or
- hardware-dependent applications. C functions can be called directly from
- MetaCard; no relinking or other processing is required. A save-disabled
- product is available free via Internet. MetaCard is self-contained, no C
- compiler is required. Single-user licenses for MetaCard cost $495, with site
- and volume licensing available. Contact MetaCard Corporation, 4710 Shoup
- Place, Boulder, CO 80303, (303) 447-3936.
-
-
- Adaptive Solutions Ships Parallel Processor CNAPS Computer Systems
-
-
- Adaptive Solutions, Inc., has begun shipping its parallel-processing CNAPS
- computer systems, designed for pattern recognition and signal-processing
- applications. CNAPS systems can be configured with 128, 256, or 512
- processors. The CNAPS system connects to a UNIX host via Ethernet. The CodeNet
- development tools provide Motif interfaces and include the CNAPS-C compiler,
- with extensions to support massive parallelism and scaled fixedpoint
- arithmetic of the CNAPS architecture. Other tools include the CNAPS
- Programming Language (CPL) assembler and debugger, command-line and windowing
- interfaces, and data conversion utilities. Contact Adaptive Solutions, Inc.,
- 1400 NW Compton Drive, Suite 340, Beaverton, Oregon 97006, (503) 690-1236;
- FAX: (503) 690-1249.
-
-
- SMDS Updates Aide-De-Camp and Allies with Centerline
-
-
- Software Maintenance and Development Systems, Inc. (SMDS), has released
- version 8.0 of the Aide-De-Camp (ADC) software-configuration management
- system. Version 8.0 includes the Lakota scripting language. ADC is
- object-based and can track software changes at a user-defined level, including
- file, subroutine, procedure, or components of individual changes. SMDS also
- announced a joint marketing and product integration alliance with Centerline
- Software, Inc. (formerly Saber Software). The integration effort will link ADC
- and Centerline's CodeCenter and ObjectCenter UNIX programming environments, to
- provide support for developing and managing large C/C++ software inventories.
- Contact Software Maintenance & Development Systems, Inc., P.O. Box 555,
- Concord, MA 01742, (508) 369-7398; FAX: (508) 369-8272.
-
-
- Promula Development Corporation Announced Fortran for COHERENT
-
-
- Promula Development Corporation and Mark Williams Company have announced
- Promula's PROMULA FORTRAN compiler for COHERENT. PROMULA FORTRAN supports
- FORTRAN 66, FORTRAN 77, VAX FORTRAN, PRIME FORTRAN, SUN FORTRAN, and some
- FORTRAN 90 extensions, and passes the Federal Software Testing Center's
- FORTRAN Compiler Validation suite, version 2. PROMULA FORTRAN provides natural
- integration with C-based libraries on COHERENT and is compatible with the
- PROMULA FORTRAN to C Translator, which generates C source. PROMULA FORTRAN for
- COHERENT is priced at $95. Contact Promula Development Corporation, 3620 North
- High Street, Suite 301, Columbus, OH 43214, (614) 263-5454; FAX: (614)
- 263-5573.
-
-
- Knowledge Dynamics Corp's INSTALL Improves Data Compression in Version 3.2
-
-
- Knowledge Dynamics Corportion has updated INSTALL, with a new compression
- algorithm, Reduce, that compresses data to 60% for most products. Version 3.2
- has an enhanced script language with fully general assignment statements, 12
- new string-handling functions, on-the-fly modificaiton of environment
- variables, and the ability to cold or warm boot the end-user's computer. An
- option for INSTALL 3.2 is the HyperText Help System, a TSR which provides
- access to the INSTALL manual, requires 2K RAM with EMS/XMS, and is Norton
- Guides-compatible. Contact Knowledge Dynamics Corporation, P.O. Box 1558,
- Canyon Lake, TX 78130-1558, (512) 964-3994.
-
-
- Microtec Research Introduces Software Development Environment for Embedded
- Systems
-
-
- Microtec Research, Inc., has introduced XRAY MasterWorks, a C/C++ integrated
- software-development environment specifically designed for embedded-systems
- engineers. XRAY MasterWorks is a package of program building, code generation,
- debugging, and analysis tools that provides push-button control over a variety
- of development tasks. Tight integration provides features such as rebuilding a
- program and reloading it into the debugger with a single command. XRAY
- MasterWorks contains XRAY Master, XRAY Debugger, and the C/C++ cross-compiler
- package. XRAY Master includes the following components: an environment
- manager, XRAY Control Panel (configuration control), XRAY Source Explorer,
- XRAY Make, and Object Format Converter (converts code to formats usable with
- logic analyzers and PROM programmers). XRAY Debugger supports multiple windows
- and can debug code in various execution environments, including
- instruction-set simulation, in-circuit emulation, and in-circuit monitor. The
- ANSI C and C++ Cross Compiler Package generates ROMable, reentrant code, and
- includes compiler, macro assembler, liner, and librarian.
- XRAY MasterWorks is hosted on SPARCstations and initially supports embedded
- systems based on Motorola 68x0 and 683xx processors. For information, contact
- Microtec Research Inc., 2350 Mission College Boulevard, Santa Clara, CA 95054,
- (800) 950-5554; FAX: (408) 982-8266.
-
-
- Borland Ships BRIEF 3.1 for DOS and OS/2
-
-
- Borland International, Inc., has begun shipping BRIEF 3.1 for DOS and OS/2.
- Formerly sold by Solution Systems as separate products Borland will bundle the
- two versions of the programmer's editor in a single package. New features in
- BRIEF 3.1 include mouse support, EMS, Redo, and Dialog Box support. Brief has
- a C-like macro language; statement completion; integration with compilers;
- language support including Borland C++, Turbo Pascal, dBASE and Assembly; and
- multiple, resizable windows. Contact Borland International, Inc., 1800 Green
- Hills Road, P.O. Box 660001, Scotts Valley, CA 95067-0001, (408) 439-4825.
-
-
- Softbridge Announces Automated Test Facility 2.0
-
-
- Softbridge, Inc., has announced Automated Test Facility (ATF) 2.0. ATF is a
- software system designed for unattended testing of applications written under
- Windows, OS/2, and DOS, either standalone or in networked environments. ATF
- 2.0 includes interactive menu-based test script building, online hypertext
- help, revamped user interface, and enhanced test management. ATF 2.0 provides
- a more open architecture, with Dynamic Link Library (DLL) functions. The SQL
- Server database required in earlier versions is now an optional feature.
- Contact Softbridge, Inc., 125 Cambridge Park Drive, Cambridge, MA 02140, (617)
- 576-2257; FAX: (617) 864-7747.
-
-
- CHAMPS Software Announces CHAMPS/CASE
-
-
- CHAMPS Software, Inc, has announced CHAMPS/CASE, a tool providing a framework
- for Rapid Application Development. CHAMPS/CASE is designed as a VAX Rdb/VMS
- layered product and should be available first quarter 1993. CHAMPS/CASE was
- designed to expedite the transition from FMS, RMS, and Cobol-based development
- to IFDL, Rdb, and ANSI C. CHAMPS/CASE generates 100% of the C source code for
- applications and can create ANSI C, ANSI SQL, and IFDL programs. CHAMPS is
- NAS, ANSI/SQL, FIMS, and POSIX compliant and supports DEC's COHESION
- environment. Contact CHAMPS Software, Inc. 1255 North Vantage Point, Crystal
- River, FL 34429, (904) 795-2362; FAX: (904) 795-9100.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Mr. Plauger:
- I have been reading the C Users Journal since it was little more than a
- newsletter for the users group. The one thing I have noticed in C programmers
- is the use of the functions "FOO" and "FOOBAR" within a test module. Where are
- the origins of such naming conventions, and to what are they attributed?
- Although, not a technical question, it has aroused my curiosity over the
- years.
- Sincerely,
- Mr. Philip Felice
- U.S. Trust Company of New York
- 770 Broadway
- New York, NY 10003
- The terms foo, bar, and foobar all derive from the old U.S. Navy slang FUBAR,
- their answer to the Army's SNAFU. FUBAR leaked into the programming community
- through MIT. The (somewhat laundered) Navy term is an acronym for "fouled up
- beyond all repair," while the Army term is short for "situation normal, all
- fouled up." -- pjp
- Mr Plauger,
- Thank you for doing such a fine job over the years. My first subscription
- issue came Saturday. Exciting. Exciting--an article on neural nets--then on
- page 85 the article falls off the face of the earth????? How beastly
- clever--they have implemented a '50s movie cliff hanger? (OK. OK. Just
- kidding.)
- I'm sure you have really heard about this problem by now. I hope that the end
- of the article will be in next month's issue. (Well OK, deadlines being what
- they are--the month after?)
- This is one issue that I am rushing a check for the source code disk. I really
- appreciate the cost of the disk to be affordable!
- Thanks & Cheers,
- deryl
- I can't claim credit for that particular gaffe. Some days, there's enough
- embarrassment to go around. -- pjp
- Diane Thomas, Managing Editor replies:
- Profuse apologies for our FUBAR. You can find the correction to that article
- on page 102 of the October 1992 issue. If you don't care to purchase the
- October issue, please call or write and I will fax or mail the correction to
- you.
- Editor:
- I am constantly impressed with the articles and the quality of The C Users
- Journal and I want to thank you for all the work that you do. It always
- disappoints me to read some flame from a reader who is mostly demonstrating
- his lack of tact or inability to express an opinion without insulting someone.
- I hope you take all flames with a grain of salt and recognize that most people
- that are satisfied or happy don't write. I, personally, rarely get the time.
- It's also great to see that the Gods and Gurus make mistakes, too. I noticed a
- goof that you'll probably get a hundred letters on and is one of the ones I've
- read a number of other gurus do commonly. In your "Bugs" article, your example
- of violation of coding standards showed the "improved version" as:
- char fn[L_tmpnam], *s;
- if ((str = fopen ((const char *)
- tmpnam(fn), "wb+")) == NULL)
- ;
- else if ((s = (char *)
- malloc(sizeof (fn) + 1))
- == NULL)
- fclose (str); str = NULL;
- else
- str->_Tmpnam = strcpy(s, fn);
- The erroneous line is the one with the fclose on it. You will notice that
- there are two statements between the else if and the else with no curly
- braces. Tsk, tsk, tsk. :-)
- I've gotten into the habit of always using curly braces just because I made
- this mistake all the time. Also, I frequently want to put a print statement
- into the if statement somewhere and then have to put curly braces around it
- anyway.
- Thanks again for all the very interesting articles and wealth of information.
- Chris Carlson
- carlson@sgi.com
- Howdy;
- I just got the latest issue of The C Users Journal, and was zipping through
- your "Standard C" column when I noticed that one of your "fixes" contains yet
- another bug. The offending code appears on page 12. [same code as in previous
- letter follows -- pjp] I doubt this will even compile as written.
- I'm pretty sure you intended to write:
- fclose(str), str = NULL;
- Don't you just hate when that happens :-)
- Timothy G. Northrup
- ...!rutgers!brspyr1!jrsalb1!tim
- Yup. Luckily, the error occurred in transcription for publication in the
- magazine (my fault). The actual code does compile and run its test cases. --
- pjp
- Dear Mr. Plauger,
- I enjoyed reading your article "Bugs" in the September '92 issue of The C
- Users Journal. Your book, The Standard C Library is quite informative.
- I did notice one example which, as written, has undefined behavior according
- to The Standard as I understand it. On page 209 your va_fputs function returns
- without calling va_end on a write error. In your implementation of the library
- it doesn't make any difference, but as the text points out that may not always
- be the case.
- While I'm on the subject, I don't recall ever reading a discussion of an
- implementation of the Standard C library suitable for use by multi- threaded
- programs. I program on the Amiga where spinning off multiple threads is quite
- easy to do, and while the system shared libraries are written to expect to be
- called from various tasks at any time, the C linker libraries for the
- compilers I've looked at expect to have only one caller at a time. Some
- functions work just fine anyway (strlen for example) while others like strtok
- (which make use of an internal static variable in the implementations I've
- seen) are clearly not going to like having multiple asynchronous callers. So
- if you're looking for an article idea you may want to consider "Library Use by
- Multiple Concurrent Threads."
- Otherwise, it's good to have The Editor reachable via e-mail. Now if we only
- had a tutorial on the correct spelling of "Plauger." (Wow! Your own domain! Is
- that anything like the fat jokes, "He's as big as New Jersey" or "He has his
- own zip code?")
- Todd M. Lewis
- utoddl@guitar.oit.unc.edu
- That's a bug all right. It's already fixed in the second printing, but thanks
- for reporting it. My brother, Dave Plauger, has just finished making a
- thread-safe C++ library for Hewlett Packard. I'm trying to coax him into
- writing an article on what he did. -- pjp
- Dear Mr. Plauger,
- This is an answer to Mr. Grabbe's letter in the July issue of CUJ.
- The point editor for the PC is available for free using anonymous ftp.
- Address: unmvax.cs.unm.edu.
- login: anonymous
- password: id
-
- It is located in /pub/Point/ptpc.tar.Z. This is a compressed archive file that
- will be extracted on (hopefully) any UNIX system using
- zcat ptpc.tar.Z tar xvf -
- Note: there is also an X-Windows version of point at the same location (best
- UNIX editor I have ever seen besides emacs/epoch).
- As far as I know point has been posted to comp.sources.*. Right, Mr.
- Weinstein? By the way, where is your column? Continue tracking comp.sources.x!
- PS: The last issues of CUJ have become somewhat "less academic" maybe due to
- the discussion some months ago. To me this is a serious drawback for the
- quality of your magazine.
- Yours truly,
- Gerhard Wilhelms
- Lehrstuhl fuer Informatik I
- Universitaet Augsburg
- Universitaetsstr. 2
- W-8900 Augsburg
- GERMANY
- Which is a serious drawback, that the magazine is less or more academic? You'd
- think we could get 50,000 readers to agree on something. Seriously, all we can
- do is keep striving for a good balance in selecting from submissions. We
- appreciate your continuing input to help us tune that balance. -- pjp
- Editor:
- In the article titled "Bit Arrays with C++" in the July 1992 issue of CUJ,
- authors A. Giancola and L. Baker chose an approach that seemed very unorthodox
- to me. If possible I would appreciate a clarification of their design
- rationale.
- The code given in the article cannot, according to the authors themselves, be
- compiled with a licensed derivative of the AT&T C++ compiler. A second
- compiler, Borland's C++ v3.0, does compile the code but issues a warning
- message. The compilation problems stem directly from the use of a nested class
- inside a template class, a design that Giancola and Baker chose in order to
- "prevent inadvertent misuse of objects." Why do they consider this possibility
- to be of such importance? Of what value is "safe" code if it cannot be
- compiled, or compiles with warnings?
- The authors' stated goal was to create a bit array that could be addressed
- using arithmetic assignment statements of the form A(i,j) = x;. This was quite
- a difficult problem and required approximately a hundred lines of clever code
- to solve. An alternate approach would have been to access array elements
- through an assignment function. this would have required perhaps ten or twenty
- lines of straightforward code. There is no question that it also would have
- the advantage of faster execution. To call an access function, one must write
- a statement of the form A.set(i,j,x); instead of A(i,j) = x;. Is the second
- form so much cleaner that it is worth the coding effort and the sacrifice in
- execution speed?
- In the first sentence of the article, Giancola and Baker explain that they
- developed this code while working in a "Go" program. Their task was to
- represent the Go board, a 19x19 grid in which each location can be either
- white, black, or empty. This could have been done by using an array of char,
- e.g., char board[19][19], requiring 361 bytes of storage. No special code
- would be needed since access to 2-dimensional char arrays is built into the
- C/C++ language. The implementation chosen by the authors requires only 113
- bytes of storage, but at the expense of added complexity.
- Unless the program must store a large number of Go positions simultaneously,
- the modest savings in storage space achieved by the authors hardly seems worth
- the effort. Furthermore, storing and retrieving values would be relatively
- efficient for a 19x19 array of characters; packing the information into 113
- bytes makes accessing of data about an order of magnitude more complex (and
- thus slower). In these days where PCs are typically equipped with megabytes of
- memory, the authors' apparently extreme desire to conserve storage space is
- difficult to understand.
- If storage space was indeed at a premium for some reason, it is odd that
- Giancola and Baker chose an implementation in which all of the bit vector code
- is in the form of inline functions. The inline expansion of every bit vector
- function call will consume code space; why not use non-inline functions to
- reduce the program's code size?
- At a detailed level the code is not as efficent or direct as it could be. This
- fragment:
- int i;
-
- tmp_mask = 1;
- for (i=1;i<N;i++) {
- tmp_mask <<=1;
- tmp_mask =1;
- }
- could be replaced by the single line:
- tmp_mask = (1 << NUM_BITS) - 1;
- The code for class BIT_TMP declares two BYTE arrays with a fixed-dimension of
- eight. One of the arrays (MASK) is never accessed except for its zeroth
- element (the other elements appear only as temporaries in a calculation and
- could be eliminated). The second array (INV_MASK) does not need to be eight
- characters long in all cases; its length could be computed at compile-time as
- INV_MASK[8/NUM_BYTES]. These two changes could save as many as 13 bytes of
- storage per object with no penalty.
- In the constructor for class BIT_TMP a temporary double (num_sub_fields) is
- used where an int or unsigned char would work fine. This would speed execution
- with no penalty.
- The two operator= functions defined in BIT_TMP end with an assignment to a
- member variable (value). These functions are of return type void. Since
- BIT_TEMP objects only exist as temporaries, these assignments are useless; the
- object will be destroyed after the closing brace of operator+().
- While I salute the ingenuity of Giancola's and Baker's idea in this article, I
- am disturbed by the apparent impracticality of their code. They started with a
- simple, real-world problem--representing a Go board--and evolved a very
- complicated, non-portable solution involving templates and nested classes.
- Straightforward approaches were possible. Why were they rejected?
- I realize that C++ is a relatively new language and that questions of
- programming style are not easily resolved. Magazine articles provide an
- excellent means for programmers to study how another software designer
- approaches a problem. In this case it seems to me that Giancola and Baker went
- to a lot of work in order to achieve a slight improvement in notational
- elegance and to avoid a far-fetched possibility of accidental code misuse.
- This is not the kind of methodology that will solve the software crisis.
- Have I missed something?
- Sincerely,
- Paul A. Cornelius
- 1261 Fernside St.
- Redwood City, CA 94061
- Golly. Sounds to me like you favor functional notation over operator notation
- if efficiency is an issue. Also, you favor using the basic C data types
- instead of C++ class constructs if that too is more efficient. Those are the
- arguments I expect from a C programmer arguing against the excesses of C++.
- You do indeed raise some valid objections. But I like your last paragraph
- best. I chose that article because it explores some serious style issues in
- writing C++. (The C++ standards committee is still haggling over ways to make
- overloaded subscript operators safer, for example. We're not alone.) The only
- way we'll develop good style(s) in C++ is to try out a few. -- pjp
- L. Baker and A. Giancola respond:
- While the code details mentioned by Mr. Cornelius are reasonable and necessary
- for peak efficiency, his stylistic comments did seem to miss the point. We
- will address these in the order presented in his letter.
- The published code does indeed compile under both the Comeau C++ 3.0, a
- licensed AT&T Cfront, and Borland C++ 3.0. The article discussed differences
- noted in the order of function calls in these implementations, which could not
- have been observed if only Borland successfully compiled the code. The only
- reason the nested class causes any problems at all is that current
- implementations impose an additional restriction on the language not present
- in the Annotated Reference Manual (ARM by Ellis and Stroustrup). Nested class
- functions must be defined in the class declaration, which makes them inline.
- The ARM allows them to be defined at global scope (ARM 9.7 p. 187). In any
- case, the 'inline' directive is only a hint to the compiler (ARM 7.1.2), and
- may be ignored. It appears to be a bug to fail to compile because of a hint
- used for efficiency. The warning from Borland, that it failed to inline code
- we did not explicitly ask it to inline, is at worst a petty annoyance. A
- message that a hint has been ignored could be considered useful information.
- When compilers are available that fully support the ARM version of C++, this
- problem will disappear. We thought it better to illustrate the principle
- rather than what the current implementation will tolerate. We have not
- determined whether Cfront 3.0.1 fixes this bug.
- The value of operator overloading is the ability to write things like A=B to
- set A, and do so in a type-safe manner. We cited as references to our article
- other publications that either used A.set (B) or used type coersion which
- would not guarantee safety. This is not a minor point, in our opinion.
- Operator overloading loses much of its value if one cannot use the assignment
- operator to assign, or if one must sacrifice safety to do so. Similarly, we
- desired to nest classes to hide one class from direct user access. If the
- reader does not agree with this premise, then the reader would not see the
- purpose to the article.
- In a Go program, one may need to examine tens or hundreds of thousands of
- positions. Storage is critical, even if bought at the cost of some code
- complexity; i. e., reducing required storage by a factor of more than three,
- would be extremely valuable. We did not emphasize this point in the article,
- thinking it obvious, but with hindsight we should have.
- If by software crisis Cornelius means the problem of producing reliable
- software containing millions of lines of code, by large teams of programmers,
- then we would claim to have made a small contribution by providing (in one
- case) an intuitive notation for assignment (A=B) that is type safe in place of
- methods that lack at least one of these features. C++, as presently designed,
- is probably not the ultimate solution to the software crisis. As discussed in
- Ellis and Stroustrup, it controls access but not visibility. Thus, C++ is not
- truly suitable for programming in the large. For example, if two modules,
- written by two teams, used the same variable name in different classes, there
- would be conflicts even if only one of those variables were accessible (ARM
- 11, p. 240).
- Similarly, by including the statements:
- #define private public
- #define protected public
- before any include directives in a file, a user could subvert any information
- hiding! When any source for a class interface, even the private part, is
- changed and recompiled, every user of that class must be recompiled! The
- information hiding of the object- oriented programming paradigm is lost.
- Contrast this with the information hiding of Ada. If an Ada package body (the
- implementation) is recompiled, but the the package specification (external
- interface) is unchanged, none of the users of that package need be recompiled,
- merely relinked with the new body. Data private to the package is not only not
- accessible outside the package, it is invisible. These features may produce
- somewhat less-efficient code in some cases, but the gain in safety, and the
- benefits for team programming in the large, should be obvious. It would be
- desirable if C++, as it evolves, picks up some of these features.
- Sincerely,
- L. Baker and A. Giancola
- Dear Mr. Plauger:
- Last year I was an early purchaser of your book, The Standard C Library, with
- the optional diskette containing the library source code. Recently I tried to
- use the time functions and ran into some obvious bugs in the code (e.g.,
- xgetzone.c's reformat function formats the timezone abbreviation backwards). I
- called The C Users Bookstore order line to ask if an updated version of the
- diskette was available and was told that it was, but the price quoted was $50
- for an exchange with my original diskette, or $99 for the diskette alone. I
- doubt if you get many takers on the latter offer given that the book and
- diskette are currently advertised in the magazine for only $77.95.
- Needless to say, it's a lot cheaper for me to just fix the problems that I
- found and annotate my copy of the book, but I'm also curious why your pricing
- strategy for the code diskette is so prohibitive. For example, in the same C
- Users Bookstore advertisement the code diskette for Illustrated C adds only
- $10 to the price of the book. If the diskette pricing is designed as an
- alternative to strict enforcement of a licensing policy for commercial use of
- the code, then I'm lodging a protest on behalf of those of us whose use of it
- is strictly non-commercial.
- Regards,
- Brian Johnson
-
- BJ Inc.
- 109 Minna St, Ste 215
- San Francisco, CA 94105
- I agreed to a price increase for the code disk for a variety of reasons that I
- won't recite here. The main reason was to bring the single-copy price more in
- line with the quantity license terms. (You need such a license if you put the
- code on a multiuser system, if you put it on a network, or if you distribute
- copies of the source or unlinked binaries.) I knew the higher price would
- annoy some people, particularly individuals who just want to play with the
- code and don't want to have to type it in. I couldn't figure out how to please
- such diverse constituencies, so I agreed to take the heat. -- pjp
- P.J.:
- Two chapters into The Standard C Library and my disillusionment with C is
- nearly complete. Maybe now I can (belatedly) start using it as a tool instead
- of a god.
- About copyrights--I wish you had assigned the code (at least) in the book to
- either the standards committees or the public domain. I know Prentice-Hall
- needs their piece of the action, and I acknowledge that copyrights allow
- greater flexibility than patents in derived works. But if, having read your
- book, I implement a C library for, say, OS-9, how do I avoid having to put the
- derivation notice in my code? The algorithms, data structures, and identifier
- names are going to be derivative of the Standard and therefore reminiscent of
- your code.
- Joel Rees
- 565 E. Mansfield Ave.
- South Salt Lake City, UT 84106-1205
- All a copyright covers is unique expression. If you can show that the choice
- of algorithms, data structures, and/or identifier names is natural, or derives
- directly from the C Standard, similarities to my code don't matter. Crib
- anybody's code directly and you infringe, however. (I managed to replicate
- UNIX under the watchful gaze of AT&T, and after having had access to UNIX
- source, without infringing their copyright. It can be done.)
- The problem I've stumbled into is the one I outlined in my previous response.
- I started out simply to make "exemplary" code for all to see and enjoy. Before
- I knew it, I had created another marketplace among people who need a Standard
- C library. Now it seems that whatever I do, I upset somebody. -- pjp
- Editor:
- I recently purchased The C Toolbox book by William James L. Hunt (Addison
- Wesley), with its "Ready To Run Programs in Turbo C, Microsoft C, and Quick
- C." While quickly perusing the book, I noticed some serial-communication
- programs that looked interesting. I committed myself to purchase the book.
- After having purchased the book, I examined the programs only to find out that
- they referred to other functions which were not available anywhere in the
- book. They do, however, offer a source disk package which I suspect must have
- these functions in it. I feel that the book, by itself, is completely useless
- and is misrepresentative.
- I feel that other potential buyers of this book should know this.
- Bill Casey
- Woodside, NY
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Date Conversions
-
-
- David Burki
-
-
- David Burki is a Programmer/Analyst employed by PDA, Inc. His recent projects
- include application development under OS/2 Presentation Manager as well as
- MS-Windows and MS-DOS. You may contact him at PDA, Inc. 11600 College Blvd.,
- Suite 100, Overland Park, KS 66210, (913) 469-8700.
-
-
- Whether you are programming business applications, calculating the position of
- celestial objects, or just trying to figure your personal biorhythm, the need
- to perform arithmetic with calendar dates is crucial to computations involving
- time. Calendar dates are used everywhere. Interest (and penalties) are
- calculated, subscription renewal notices are mailed, and your physical,
- emotional, and intellectual biorhythmic peaks and slumps are just a few things
- based on dates. The problem is that there is no direct method to perform
- arithmetic on calendar dates. The routines presented here will allow you to
- determine the number of days between two dates, calculate the calendar date
- some number of days before or after a given date, determine the day of the
- week a certain date falls on, and determine whether a given year is a leap
- year. In addition, the internal representation of these dates provide a
- compact method for storing dates.
-
-
- The Trouble with Dates
-
-
- The problem with date computations stems from the way we represent calendar
- dates. The Gregorian calendar (see the sidebar, "A Brief History of the
- Calendar"), combines three different numbering units to create a calendar
- date: the number of days in a month, the number of months in a year, and the
- number of years since the beginning of the Christian era. Each of these
- "digits" are of a different base. Years are base 10, months are base 12, and
- days are either base 28, 29, 30 or 31. To easily do arithmetic on a calendar
- date, it must first be converted into a single number. Converting a calendar
- date into a single number yields a unique Julian Day number for that date.
- Notice I said Julian Day, not Julian date. A Julian Day number is a method
- astronomers use to identify dates. The Julian Day method was developed by
- Joseph Scaliger about 1577 A.D. as a means by which all days within recorded
- time would be assigned consecutive numbers (Harvey 1983). Contrary to popular
- belief, Scaliger named his method after his father, not Julius Caesar. A
- Julian date, on the other hand, is a calendar date based on the Julian
- calendar.
-
-
- Conversions
-
-
- At the heart of date manipulation is the ability to convert a calendar date to
- a Julian Day number and back. Converting a Gregorian date to a Julian Day
- number is accomplished by the function ToJul in Listing 1. ToJul takes the
- month, day, and year as parameters and returns the Julian Day number as a long
- integer. The constants used in the function were derived by the algorithm's
- originator and are critical to being able to successfully convert the Julian
- Day number back to a Gregorian date. Converting a Julian Day number back to a
- Gregorian date is accomplished by the function FromJul (Listing 1). Arguments
- for FromJul are the Julian Day number, and pointers to the variables which
- will hold the resultant month, day, and year. With only these two functions it
- is possible to subtract two dates and add or subtract some number of days to a
- given date. Once a Gregorian date has been converted to a Julian Day number,
- it is possible to determine the day of the week that date falls on. The
- function DayOfWeek (Listing 1) returns an integer ranging from 0 to 7
- representing Monday through Sunday, simply by computing the remainder of the
- Julian Day number divided by 7. The IsLeapYear function in Listing 1 returns a
- boolean value which indicates whether the year passed as a parameter is a leap
- year or not.
- If you have the need to know the phase of the moon on a given date, an
- approximation can be obtained using the Julian Day number. If the Julian Day
- number divided by 29.530588 yields a decimal remainder of or proximate to
- 0.83, that day is a full moon. A decimal remainder proximate to 0.33 indicates
- a new moon (Harvey 1983).
- The conversion routines presented here have been tested using a broad range of
- dates. Testing has included round tripping every valid date from 4000 B.C. to
- beyond 5000 A.D., as well as random checking of the day of the week against
- published calendars. Use caution with B.C. dates. The year immediately
- preceding 1 A.D. is 1 B.C. There is no year 0. Years prior to 1 A.D. are
- numbered beginning at -1.
- I would like to extend a special thanks to Jeff Betts, president of Creative
- Programming for providing the references to the algorithms used to create the
- Julian Day routines in the Vitamin C Library. Without his assistance, the
- research would have been much more painful.
- You may not need routines to manipulate dates every day, but each one of us
- tries to build a software arsenal which we can use to conquer the daily
- challenges. These date routines are one more weapon to be added to your cache.
-
-
- Bibliography
-
-
- Fliegl, Henry F. and Van Flanders, Thomas C. "A machine algorithm for
- processing dates." Communications of the ACM, Volume 11, Number 10, October
- 1968, page 657.
- Friedman, Howard S. 1989. The Development of the Gregorian Calendar. (This is
- a text file (kalend.zip) found in the IBM Programmers Forum on CompuServe.)
- Harvey, O. L. 1983. Calendar Conversions by Way of the Julian Day Number.
- Philadelphia, PA: American Philosophical Society.
- A Brief History of the Calendar
- The Gregorian calendar in use today is based on a calendar created by the
- Egyptian astronomer, Sosigenes, at the direction of Julius Caesar. This
- calendar was placed into effect the year we now call 45 B.C. (Friedman 1989).
- Sosigenes calculated the length of the tropical year (equinox to equinox) at
- 365.25 days, and proposed that the normal year have 365 days, and an extra day
- be inserted every fourth year to make up for the extra one quarter day each
- year. The actual length of the tropical year is 365.2421987 days calculated
- with reference to a cesium atomic clock in 1956 by the Harvard University
- astronomer, Simon Newcomb (Fliegl 1968). The effect of Sosigenes tropical year
- being shorter than the actual tropical year becomes apparent after several
- centuries. By the time of Pope Gregory XIII, in the 16th century, the
- difference between the calendar vernal equinox and the actual vernal equinox
- amounted to 10 days. Under Pope Gregory, Christopher Clavis developed the
- rules (based on calculations made by astronomer Aloysius Lilus) for the
- Gregorian calendar. This is the calendar used by most of the nations of the
- world today. Each calendar year would be 365 days in length, and an extra day
- would be inserted every fourth year (any year evenly divisible by 4) except in
- centennial years (years evenly divisible by 100) unless the centennial year
- was also evenly divisible by 400. See Table 1 if that was a bit confusing.
- Since the current calendar date was off by 10 days, Pope Gregory ordered that
- the final day of the era of the Julian calendar would be Thursday, October 4,
- 1582 A.D. The following day would be Friday, October 15, 1582 A.D., The first
- day of the era of the Gregorian calendar (Friedman 1989). This edict took
- effect almost immediately in the Papal States, but it took many years for
- other European nations to adopt the new calendar.
- Table 1 Leap year table
- year mod 4 == 0 year mod 100 == 0 year mod 400 == 0 Is leap year
- -------------------------------------------------------------------
- T T T T
- T T F F
- T F Don't care T
- F Don't care Don't care F
-
- Listing 1 Calendar conversion routines
- /*
- ; #defines
- */
- #define BOOL int
- #define TRUE 1
- #define FALSE 0
-
- /*
- ; Function prototypes
- */
- long ToJul( int, int, int );
- void FromJul( long, int *, int *, int * );
-
- BOOL IsLeapYear( int );
- int DayOfWeek( long );
-
- /*
- ;
- ; USAGE: long ToJul( int, int, int );
- ; int month; Gregorian calendar date month
- ; int day; Gregorian calendar date day
- ; int year; Gregorian calendar date year
- ;
- ; DESCRIPTION: Converts Gregorian calendar date to a
- ; julian day number.
- ;
- ; ALGORITHM: adaptation of the FORTRAN code used to
- ; implement the algorithm presented by
- ; H. Fliegl and T. Van Flanders,
- ; Communications of the ACM, Vol. 11,
- ; No. 10, October, 1968, page 657.
- ;
- ;RETURNS: long int representing the julian day number
- */
- long ToJul( int month, int day, int year )
- {
- long jul_day, // returned julian day
- lmonth = (long)month, // cast them once
- lday = (long)day, // instead of
- lyear = (long)year; // each usage
-
- jul_day = lday - 32075L + 1461L *
- (lyear + 4800 + (lmonth - 14L) / 12L) /
- 4L + 367L * (lmonth - 2L - (lmonth - 14L) /
- 12L * 12L) / 12L - 3L * ((lyear + 4900L +
- (lmonth - 14L) / 12L) / 100L) / 4L;
-
- return jul_day;
- }
-
- /*
- ;
- ; USAGE: void FromJul( long, int *, int *, int * );
- ; long jul_day; Julian Day number to convert
- ; int *month; Gregorian calendar month (return)
- ; int *day; Gregorian calendar day (return)
- ; int *year; Gregorian calendar year (return)
- ;
- ; DESCRIPTION: Converts Julian Day number to its
- ; corresponding Gregorian calendar date
- ; components.
- ;
- ; ALGORITHM: adaptation of the FORTRAN code used to
- ; implement the algorithm presented by
- ; H. Fliegl and T. Van Flanders,
- ; Communications of the ACM, Vol. 11,
- ; No. 10, October, 1968, page 657.
- ;
- ; RETURNS: Nothing, updates variables pointed to by
- ; year, month and day.
- */
- void FromJul( long jul_date,
-
- int *month,
- int *day,
- int *year )
- {
- long t1, // temporary work variables
- t2,
- yr,
- mo;
-
- t1 = jul_date + 68569L;
- t2 = 4L * t1 / 146097L;
- t1 = t1 - (146097L * t2 + 3L) / 4L;
- yr = 4000L * (t1 + 1) / 1461001L;
- t1 = t1 - 1461L * yr / 4L + 31;
- mo = 80L * t1 / 2447L;
- *day = (int)(t1 - 2447L * mo / 80L);
- t1 = mo / 11L;
- *month = (int)(mo + 2L - 12L * t1);
- *year = (int){100L * (t2 - 49L) + yr + t1);
- }
-
- /*
- ; USAGE: int IsLeapYear( int );
- ; int year; year to check
- ;
- ; DESCRIPTION: Determines whether a given year is a
- ; leap year.
- ;
- ; RETURNS:
- ; TRUE (non-zero) year is a leap year
- ; FALSE (zero) year is not a leap year
- */
- BOOL IsLeapYear( int year )
- {
- return (year % 4 == 0 &&
- (year % 100 != 0 year % 400 == 0))
- }
-
- /*
- ; USAGE: int DayOfWeek( long );
- ; long jul_day; julian day number
- ;
- ; DESCRIPTION: Determine the day of the week a given
- ; julian day number falls on.
- ;
- ; RETURNS: 0 ... 7, where 0 is a Monday, 7 is a Sunday.
- */
- int DayOfWeek( long jul_day )
- {
- return (int)(jul_day % 7L);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Real-Number Approximation for Real Programmers
-
-
- Mark Gingrich
-
-
- Mark currently works on real-time, embedded software in the medical device
- industry. Although trained in physics and computer science, he occasionally
- "moonlights" as an amateur astronomer. He can be reached at 355 Estabrook St.,
- Apt. 403, San Leandro, CA 94577.
-
-
-
-
- Introduction
-
-
- Real-world numerical calculations rarely, if ever, call for floating-point
- precision to the gazillionth digit. Indeed, do you really need to know your
- automobile's gas mileage to one-part-per-billion resolution?
- Of course, such practicalities influence the design of device software. And
- living at one computational extreme are those lean, mean
- microcontroller-embedded machines which often abstain from floating-point
- code. Yet it is frequently the case that an oddball transducer scaling factor
- or proportionality constant is required--oddball in refusing to conform to our
- pristine integer-only universe. Hence the need to link in extra bytes from the
- floating-point math library into what is, all too often, a nearly full ROM
- space.
- This article describes a simple utility that computes integer-ratio
- approximations for floating-point constants. Code requiring only a few
- instances of floating point for scaling may instead use nearly equivalent
- integer ratios. The result is speed and size benefits by avoiding
- floating-point routines altogether.
- For example, instead of scaling a quantity by, say, 0.66666666, you could
- multiply by 2, then divide by 3. But this is a trivial case. A much more
- interesting question is: What integer ratios best approximate those
- perennially popular (and irrational) constants, such as the square root of 2,
- or e, or p?
-
-
- Approximating Pi by the Slice
-
-
- Let's try p. The first few decimal digits are:
- 3.14159265358979323846...
- Chopping after the first two digits suggests the crude integer-ratio
- approximation 31/10. Reality differs by little more than one percent in this
- instance.
- But maybe that's not accurate enough. So tack on another digit. p then
- approximates to 314/100 (157/50 when reduced to lowest terms), just 0.05
- percent from the truth. Continuing in this fashion for four more iterations
- yields the ratio:
- 3141593/1000000
- with a mere 0.00001 percent error. Not bad for such a straightforward (I'll
- call it radix-10) method. Yet p is better approximated without so many digits.
- Although not immediately apparent, the ratio 355/113 is superior to
- 3141593/1000000
- There are two compelling reasons why. First, 355/113 is nearer to p. Second,
- it has a smaller numerator (355 versus 3141593, the latter not even within
- 16-bit number range). Here is a crucial computational advantage. When a ratio
- is applied as a scaling factor, a smaller numerator permits larger
- multiplicands without overflow hassles.
- So 355/113 is better. But is it just a freakish occurrence? Table 1 implies
- otherwise. It shows a sequence of radix-10 rational approximations for p
- juxtaposed with a few "hand-picked" ratios (355/113 being one). Each of the
- latter provides better accuracy with smaller integers--more accuracy per bit.
- Certainly it's not difficult to devise successively more accurate radix-10
- ratios by inspection (31/10, 314/100, 3142/1000,...), but discovering these
- preferred "hand-picked" ratios is more subtle. There is a way to do so. It
- employs a mathematical device known as a continued fraction.
-
-
- Fractions, to be Continued
-
-
- The continued-fraction form is a real number's alter ego. I'll intuitively
- illustrate the concept by creating a continued fraction for p. Take p's
- decimal-digit sequence and break it into a sum of two components--the integer
- part and the fractional part.
- 3 + 0.1415926535897932...
- Ignoring the fractional part for a moment, I could quit right here and claim
- that p is equal to 3--a first-cut estimate. Instead, I'll show a bit more
- initiative and improve the approximation. Rewrite the fractional part as
- follows:
- Click Here for Equation
- Fixating on the denominator (7.06525133059310477...), I note again that I'm
- dealing with a real number. So break it into a sum of integer and fractional
- parts:
- Click Here for Equation
- Like before, I may ignore the fractional component and stop at this point,
- leaving the result:
- Click Here for Equation
- Or, yet again, I can improve the approximation. Rewrite the fractional
- denominator term:
- Click Here for Equation
- The pattern to this algorithm now should be conspicuous. Break the bottom-most
- denominator into integer and fractional parts:
- Click Here for Equation
- Ignoring the fractional component, I see that the approximation becomes:
- Click Here for Equation
- For good measure, I'll do just one more iteration of this process:
- Click Here for Equation
- And so on, and so on. These first few approximations should look familiar.
- They are the "hand-picked" ratios of Table 1. And I could continue generating
- still better ratios seemingly forever, or stop when I achieve the desired
- accuracy.
- This construct of nested fractions:
- Click Here for Equation
-
- is called a simple continued fraction. It's "simple" because each numerator is
- unity. Given any nonnegative real number, R, the ais result by fiat of
- Algorithm A (Figure 1). Thus the ais needed to make p are:
- [3, 7, 15, 1, 292, 1, 1, 1, 2, 1,
- 3, 1, ...]
- which is not as instantly recognizable as 3.14159..., but just as valid.
- Experimenting with this algorithm and a judicious assortment of real numbers
- reveals several notable continued-fraction characteristics:
- Rational numbers are expressible with a finite number of ais (for instance,
- 7/4 = 1.75 = [1, 1, 3]).
- Irrational numbers require infinitely many ais.
- The approximation of an irrational number improves with each successive ai,
- always alternating between a-bit-too-large and a-bit-too-small values.
- To handle negative real numbers, treat the value as if it were positive; then
- negate a0 and the top-most numerator.
- Algorithm A is simple and intuitive--but flawed in actual practice. The gotcha
- is the finite floating-point representation of real numbers. After a dozen or
- so iterations, erroneous ais are produced. The algorithm also can mess up
- after only a few iterations. The comparison test at the top of the loop often
- flubs because round-off prevents the fractional result from reaching zero
- exactly. A further annoyance arises from having to collapse the continued
- fraction to a simple fraction for use as a scaling factor.
- Algorithm B (Figure 2) sidesteps these pitfalls. It is a recurrence method
- using integers only. First you must decide how precisely--how many digits--you
- want to specify the real number of interest, then express it as a radix-10
- ratio. For example, 3.1416 is 31416/10000. The algorithm starts out by
- producing a crude estimate, then grinds out additional ratios, p[i]/q[i], each
- a simple fraction in lowest terms and more accurate than the previous one,
- until the initial radix-10 ratio is achieved. (Algorithm mavens will note a
- similarity to Euclid's algorithm. It's not a coincidence. See Knuth, 1981.)
-
-
- Real-World Usage
-
-
- Listing 1 details an implementation of Algorithm B in ANSI C. This short
- utility, called fp2ratio, accepts a floating-point number as a command-line
- argument and displays the ever-improving ratio approximations in succession.
- Output for a 9-digit representation of p appears in Figure 3.
- But before applying one of these ratios as a scaling factor, a caveat. Keep in
- mind the round-off correction needed before division by the denominator
- (denom). Positive values are scaled thusly:
- Click Here for Equation
- So much for the theory; let's try a practical application. Imagine a
- relatively noiseless sinusoidal signal digitized to 10-bit precision. The
- analog-to-digital converter scaling is 1 millivolt per least-significant bit.
- You wish to display, to three decimal-digit precision, the signal's
- root-mean-square (RMS) value in millivolts. Here's one strategy. Take the
- difference between the minimum and maximum signal excursions--the peak-to-peak
- value--then convert to RMS by scaling:
- Click Here for Equation
- The peak-to-peak value is guaranteed never to exceed decimal 1,023. (It's a
- 10-bit result, remember.) So declare it an unsigned 16-bit word. As a guard
- against 16-bit overflow during scaling, the selected ratio's numerator must
- not exceed 64. Figure 4 shows fp2ratio's output for the scaling factor
- sqrt2/4. The best ratio honoring our overflow constraint is 35/99.
- Implementing the round-off correction, the scaling expression to appear in
- code is:
- Click Here for Equation
-
-
- Conclusion
-
-
- Another possible application of this approach is the "clean up" of numerical
- values due to loss of fidelity. Take, for example, the computation of a matrix
- inverse, where the matrix elements are known rationals. You crunch the
- numbers. You get your inverse--along with unavoidable round-off and truncation
- effects. Yes, the answer is very close, but it's not an exact rational result.
- A bit of post-processing, a la the methods described, may help recover the
- true rational answer.
- Continued fractions, it turns out, are applied well beyond the ratio
- approximation of real numbers. Functions, too, may be approximated, but with
- polynomials replacing the integers in the numerators and denominators. This is
- an alternative to a power-series approximation (Press, 1986).
- But as handy as they are, continued fractions haven't achieved household-name
- status. The author Petr Beckmann has lamented that they are, "part of the
- 'lost mathematics,' the mathematics now considered too advanced for high
- school and too elementary for college." (Beckmann 1971)
- Real programmers are inveterate collectors of tricks and techniques. Consider
- adding continued fractions to your algorithm toolbox.
- References
- Beckmann, Petr. 1971. A History of Pi. New York: Dorset Press.
- Knuth, Donald E. 1981. The Art of Computer Programming. Volume 2:
- Seminumerical Algorithms., 2nd ed. Reading, MA: Addison-Wesley.
- Olds, C.D. 1963. Continued Fractions. New York: Random House, New Mathematical
- Library.
- Press, William H., Flannery, Brian P., Teukolsky, Saul A., Vetterling, William
- T. 1986. Numerical Recipes. Cambridge, England: Cambridge University Press.
- Sinnott, Roger W. January, 1989. "Continued Fractions and the Sky". Sky and
- Telescope. 80-82.
- Baseball, Mom, and Rational Pi
- Divining the ratio of a circle's circumference to its diameter has been an
- intellectual challenge for nearly four millennia. There's even an implied
- specification for p in the Old Testament (1 Kings 7:23 and 2 Chronicles 4:2):
- Then he made the molten sea; it was round, ten cubits from brim to brim, and
- five cubits high, and a line of thirty cubits measured in circumference.
- Gauged empirically at first--perhaps with lengths of rope--then attacked
- analytically over the years with increasing mathematical sophistication, the
- noble goal was to find p's exact integer ratio. How close did they get? As the
- 1600s commenced, p had been revealed to well over 30 decimal places. In the
- early 1700s, it was known to over 100 decimal places--with no sign of
- repeating digits. (A repeating digit sequence is the telltale clue of
- rationality.)
- Finally, in the 1760s, Lambert proved p to be an irrational number. A little
- more than century later, it was proved also to be transcendental, a special
- breed of irrational. (Transcendental numbers cannot be solutions to algebraic
- equations with rational coefficients.)
- But despite these breakthroughs, mathematical truth soon after experienced an
- amusing episode of American democracy in action. In 1897, the state of
- Indiana's House of Representatives voted 67 to 0, proclaiming p equal to 16/5,
- exactly! The bill was proposed by a physician and amateur mathematician, a Dr.
- Edwin Goodwin, who claimed this "discovery." And being a loyal Hoosier, he
- offered Indiana free use of the result--all others would have to pay
- royalties! Fortunately, last-minute lobbying by an alert mathematics professor
- kept the bill from making it through the Indiana Senate.
- Figure 1 Given a real number, R, this algorithm computes the ais for a simple
- continued fraction.
- Algorithm A:
-
- x = R;
- i = O;
- a[ i ] = integer_part( x );
-
- while ( fractional_part( x ) != 0.0 )
- {
- x = 1.0 / fractional_part( x );
- i = i+1;
- a[ i ] = integer_part( x );
- }
- Figure 2 Given a real number, R, this algorithm produces a sequence of simpler
- -- but always improving -- ratio approximations.
- Algorithm B:
-
-
- n = radix10_numerator( R );
- d = radix10_denominator( R );
- p[ 0 ] = 0;
- p[ 1 ] = 1;
- q[ 0 ] = 1;
- q[ 1 ] = 0;
- i = 2;
-
- do
- {
- p[ i ] = ( n div d ) * p[ i - 1] + p[ i - 2 ];
- q[ i ] = ( n div d ) * q[ i - 1] + q[ i - 2 ];
- i = i + 1;
- temp = d;
- d = n modulo d;
- n = temp;
- } while ( n modulo d != 0 );
- Figure 3 A nine-digit representation of p is displayed as a sequence of
- improving ratio approximations.
- fp2ratio 3.14159265
-
- 3.14159265:
-
- 3 / 1 -4.5%
- 22 / 7 +0.040%
- 333 / 106 -0.0026%
- 355 / 113 +0.0000086%
- 102573 / 32650 -0.000000022%
- 102928 / 32763 +0.0000000078%
- 308429 / 98176 -0.0000000021%
- 411357 / 130939 +0.00000000040%
- 1542500 / 490993 -0.000000000094%
- 1953857 / 621932 +0.000000000010%
- 15219499 / 4844517 -0.00000000000033%
- 62831853 / 20000000 Exactly!
- Figure 4 sqrt2/4 is truncated to nine digits and rationally approximated.
- fp2ratio .353553391
-
- 0.353553391:
-
- 1 / 2 +41.4%
- 1 / 3 -5.7%
- 5 / 14 +1.0%
- 6 / 17 -0.17%
- 29 / 82 +0.030%
- 35 / 99 -0.0051%
- 169 / 478 +0.00088%
- 204 / 577 -0.00015%
- 985 / 2786 +0.000026%
- 1189 / 3363 -0.0000045%
- 5741 / 16238 +0.00000064%
- 6930 / 19601 -0.00000025%
- 19601 / 55440 -0.000000015%
- 104935 / 296801 -0.0000000021%
- 124536 / 352241 +0.00000000062%
- 354007 / 1001283 -0.00000000018%
- 478543 / 1353524 +0.000000000024%
- 2746722 / 7768903 -0.0000000000027%
- 3225265 / 9122427 +0.0000000000013%
- 5971987 / 16891330 -0.00000000000050%
-
- 9197252 / 26013757 +0.00000000000014%
- 24366491 / 68918844 -0.000000000000016%
- 82296725 / 232770289 +0.0000000000000012%
- 353553391 / 1000000000 Exactly!
- Table 1 Comparing the accuracy of radix-10 versus "hand-picked" rational
- approximations of p. (Overlined digits signify a repeating sequence.)
- Radix-10 "Hand Picked" Value Percent
- Ratio Ratio Error
-
- 3 / 1 3 -4.5
-
- 31 / 10 3.1 -1.3
-
- 314 / 100 3.14 -0.051
- __________LINEEND____
- 22 / 7 3.142857 +0.040
-
- 3142 / 1000 3.142 +0.013
-
- 333 / 106 3.1415094... -0.0026
-
- 31416 / 10000 3.1416 +0.00023
-
- 314159 / 100000 3.14159 0.000084
-
- 3141593 / 1000000 3.141593 +0.000011
-
- 355 / 113 3.1415929... +0.0000085
-
- 31415927 / 10000000 3.1415927 +0.0000015
- Table 2 Some notable (if not perverse) rational approximations of p.
- When By Whom Ratio Value Percent
- Error
-
- c. 2000 B.C. Babylonians 25 / 8 3.125 -0.53
-
- c. 2000 B.C. Egyptians 256 / 81 3.16049... +0.60
-
- c. 550 B.C. Old Testament,
- 1 Kings 7:23 3 / 1 3 -4.5
-
- c. 220 B.C. Archimedes of Syracuse 211875 / 67441 3.14163... +0.0013
- _____LINEEND____
- c. 160 A.D. Ptolemy of Alexandria 377 / 120 3.1416 +0.0024
-
- 263 A.D. Liu Hui (China) 157 / 50 3.14 -0.051
-
- c. 500 A.D. Aryabhata (India) 3927 / 1250 3.1416 +0.00023
- __ ______LINEEND____
- 1220 A.D. Fibonacci (Italy) 864 / 275 3.1418 +0.0072
-
- 1573 A.D. Valentinus Otho
- (Prussia) 355 / 113 3.14159... +0.0000085
-
- 1897 A.D. Indiana House of 16 / 5 3.2 +1.9
- Representatives (U.S.A.)
-
- Listing 1 fp2ratio.c -- an implementation of Algorithm B in Figure 2 in ANSI C
- /******************************************************
- *
-
- * fp2ratio.c
- *
- * Floating-point number to ratio approximation.
- *
- * Usage: fp2ratio number
- *
- * Compiler: Microsoft C 6.0 using inline
- * floating-point emulator (option /FPi).
- *
- ******************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <math.h>
-
- /* Maximum argument significant digits. */
- #define MAX_SIG_DIGITS 9
-
- #define ARRAY_SIZE 2
- #define I ( i ) % ARRAY_SIZE
- #define I MINUS_1 ( i - 1 ) % ARRAY_SIZE
- #define I_MINUS_2 ( i ) % ARRAY_SIZE
-
- typedef unsigned long Ulong;
- typedef long double Ldouble;
-
- int main( int argc, char **argv )
- {
- int i, dec_digits, sign;
- Ulong n0, n, d0, d, temp;
- Ulong p[ARRAY_SIZE] = { 0, 1 };
- Ulong q[ARRAY_SIZE] = { 1, 0 };
- double x;
- Ldouble percent_err;
-
- /* Check for one command-line argument. */
- if ( argc != 2 )
- {
- fprintf(stderr, "Usage: %s number\n", argv[0);
- exit( EXIT_FAILURE );
- }
- else /* argc == 2 */
- x = strtod( argv[1], (char **) NULL );
-
- /* Handle zero and negative arguments. */
- if ( x < 0.0 )
- {
- sign = -1;
- x = -x;
- }
- else if ( x == 0.0 )
- {
- puts( "\n0:\n" );
- puts( " 0 / 1 Exactly!" );
- exit( EXIT_SUCCESS );
- }
- else /* x > 0.0 */
- sign = 1;
-
-
- /* Check for out-of-range arguments. */
- if ( x >= pow( 10.0, (double) MAX_SIG_DIGITS ) )
- {
- fprintf( stderr, "%s: Magnitude is", argv[1] );
- fprintf( stderr, " too large.\n" );
- exit( EXIT_FAILURE );
- }
- else if ( x <=
- pow( 10.0, (double) -MAX_SIG_DIGITS) / 2.0 )
- {
- fprintf( stderr, "%s: Magnitude is", argv[1] );
- fprintf( stderr, "too small.\n" );
- exit( EXIT_FAILURE );
- }
-
- /* Determine the argument's radix-10 ratio. */
- d0 = (Ulong) pow( 10.0, (double) MAX_SIG_DIGITS -
- ((x < 1.0) ? 0.0 : floor( 1.0 + log10( x ))));
- n0 = (Ulong) ( ( x * (double) d0 ) + 0.5 );
- printf( "\n%.*g:\n\n", MAX_SIG_DIGITS,
- (double) n0 / (double) d0 );
-
- /* Iteratively determine integer ratios. */
- for ( i = 2, d = d0, n = n0 ; ;
- i++, temp = d, d = n % d, n = temp )
- {
- p[I] = n / d * p[I_MINUS_1] + p[I_MINUS_2];
- q[I] = n / d * q[I_MINUS_1] + q[I_MINUS_2];
-
- /* Print ratios with non-zero numerators. */
- if ( p[I] != 0 )
- {
- printf("%11ld / %-10lu", sign * p[I], q[I]);
- if ( n % d == 0 )
- {
- printf(" Exactly!\n" );
- break;
- }
-
- /*
- * Compute the ratio's percent error
- * (taking care to avoid significance
- * loss when subtracting nearly equal
- * values):
- *
- * percent error =
- *
- *
- * 100 * ( p[i] * d0 - q[i] * n0 )
- * -----------------------------------
- * q[I] * n0
- *
- * Display in %f format with at least two
- * significant digits.
- */
- percent_err = (Ldouble)p[I] * (Ldouble)d0;
- percent_err -= (Ldouble)q[I] * (Ldouble)n0;
- percent_err *= 100.0L;
- percent_err /= (Ldouble)q[I] * (Ldouble)n0;
-
-
- dec_digits =
- ( fabs( (double) percent_err ) >= 10.0 ) ?
- 1 : 1 + ( int ) ( fabs( floor(
- log10( fabs((double) percent_err )))));
- printf( "%+*.*Lf%%\n", dec_digits + 6,
- dec_digits, percent_err );
- }
- }
-
- exit( EXIT_SUCCESS );
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sorting Networks
-
-
- Frederick Hegeman
-
-
- Frederick Hegeman is an amateur programmer and computer language hobyist. He
- can be reached at P.O. Box 2368, Rapid City, SD 57709, telephone (605)
- 343-7014.
-
-
- A sorting network sorts n items by performing a predetermined set of
- comparisons. A software implementation for a sequential computer might take
- the form
- swap(1,2); swap(4,5); swap(3,5);
- swap(3,4); swap(1,4); swap(1,3);
- swap(2,5); swap(2,4); swap(2,3);
- where swap(a,b) = if(b < a)
- exchange a and b
- This is a nine-comparison network for sorting five items. You might try it
- with pennies, nickels, dimes, quarters, and cheeseburgers. The best-, worst-,
- and typical-case number of comparisons are the same. Only the number of
- exchanges varies. Nine comparisons are, in fact, the fewest necessary to sort
- five items by exchanges when any combination of items is possible. When n is
- known to be small, these simple predetermined sequences out-perform the best
- algorithmic sorts.
- In theory, the minimum number of comparisons necessary to sort n items is the
- ceiling of log2(n!). As n grows larger, 1og2(n!) approaches nlog2(n). The
- reason why O(nlog2(n)) sorting algorithms, such as Quicksort, are so efficient
- as n grows larger should be obvious. Sorting networks generated by the
- Bose-Nelson algorithm (See Listing 1.) are O(n1.585), which diverges from
- nlog2(n) rapidly. However, the Bose-Nelson network for 16 elements is 65
- comparisons, which is pretty nearly nlog2(n), and sorting 1000 random
- 16-element arrays using a Quicksort that pivoted on the last element in the
- array requires 85.43 comparisons on average, which is just over n1.585. This
- is probably the reverse of what you might have expected. Sorting is so often
- discussed in terms of increasing n that it is easy to fall into the trap of
- expecting "efficient" algorithms to behave nicely over the whole range of
- their inputs. The "gotcha" in Quicksort is the extra comparisons needed to
- properly partition the array. They are insignificant when sorting a large
- array but constitute a significant part of the total when the array is small.
- As n becomes very small, the behavior of sorting networks comes closer to the
- ideal.
- Bose-Nelson generates minimum comparison networks only for n £ 8. However,
- minimum comparison networks are available for 9£ n£16 as well. Those generated
- by the code in Listing 2 are based on illustrations in The Art of Computer
- Programming, Vol. 3 (Knuth 1973). Please note that both listings generate
- sorts according to element numbers--one greater than the corresponding array
- index.
- Sorting small arrays is a problem all its own, difficult to understand in the
- usual terms. If you need to sort a small array in a time-critical section of
- code, you can count on only two things holding true: your algorithmic sort may
- perform much worse than expected, and no algorithmic sort can match the
- minimum comparison networks on all combinations of elements. When that code is
- part of something like an operating system, the networks have at least two
- other attractive features: many comparisons can be done in parallel, if
- appropriate; and they are inherently re-entrant, so that sorting may be
- interleaved with other tasks.
-
-
- Bibliography
-
-
- Knuth, Donald E. 1973. The Art of Computer Programming, Vol. 3. Reading, MA:
- Addison-Wesley. Pp. 220-229.
- Bose, R. C. and Nelson, R. J. 1962. "A Sorting Problem". JACM, Vol. 9. Pp.
- 282-296.
- Davis, Wilbon. September, 1992. "Time Complexity". The C Users Journal, Vol.
- 10, No. 9. Pp. 29-38.
-
- Listing 1 Bose-Nelson algorithm for generating sorting networks
- /* Calling bose(n) generates a network
- * to sort n items. See R. C. Bose & R. J. Nelson,
- * "A Sorting Problem", JACM Vol. 9, Pp. 282-296. */
- bose(n)
- int n;
- {
- Pstar(1, n); /* sort the sequence {X1,...,Xn} */
- }
-
- P(i, j)
- int i, j;
- {
- printf("swap(%d, %d);\n", i, j);
- }
-
- Pstar(i, m)
- int i; /* value of first element in sequence */
- int m; /* length of sequence */
- {
- int a;
-
- if(m > 1)
- {
- /* Partition into 2 shorter sequences,
- * generate a sorting method for each,
- * and merge the two sub-networks. */
- a = m/2;
- Pstar(i, a);
-
- Pstar((i + a), (m - a));
- Pbracket(i, a, (i + a), (m - a));
- }
- }
-
- Pbracket(i, x, j, y)
- int i; /* value of first element in sequence 1 */
- int x; /* length of sequence 1 */
- int j; /* value of first element in sequence 2 */
- int y; /* length of sequence 2 */
- {
- int a, b;
-
- if(x == 1 && y == 1)
- P(i, j); /* 1 comparison sorts 2 items */
- else if(x == 1 && y == 2)
- {
- /* 2 comparisons inserts an item into an
- * already sorted sequence of length 2. */
- P(i, (j + 1));
- P(i, j);
- }
- else if(x == 2 && y == 1)
- {
- /* As above, but inserting j */
- P(i, j);
- P((i + 1), j);
- }
- else
- {
- /* Recurse on shorter sequences, attempting
- * to make the length of one subsequence odd
- * and the length of the other even. If we
- * can do this, we eventually merge the two. */
- a = x/2;
- b = (x & 1) ? (y/2) : ((y + 1)/2);
- Pbracket(i, a, j, b);
- Pbracket((i + a), (x - a), (j + b), (y - b));
- Pbracket((i + a), (x - a), j, b);
- }
- }
- /* End of File */
-
-
- Listing 2 Minimum comparison network pairs
- /* 10 items - 29 comparisons; 9 - 25 */
- int net10[29][2] = {
- {2,9},{1,5},{6,10},{3,7},{4,8},{1,4},{7,10},{3,6},
- {1,2},{4,7},{9,10},{5,8},{1,3},{5,9),{2,6),{8,10},
- {2,3},{4,5},{6,7},{8,9),{2,4},{7,9},{3,5},{6,8},
- {3,4},{7,8},{4,6},{5,7},{5,6}};
-
- /* 12 items - 39 comparisons; 11 - 35 */
- int net12[39][2] = {
- {1,2},{3,4},{5,6},{7,8},{9,10},{11,12},{2,4},{6,8},
- {10,12},{1,3},{5,7},{9,11},{2,3},{6,7},{10,11},
- {2,6},{7,11},{6,10},{3,7},{2,6},{7,11},{1,5},{8,12},
- {4,8},{5,9},{1,5},{8,12},{2,5},{8,11},{4,9},{3,4},
- {9,10},{3,5},{8,10},{4,6},{7,9},{4,5},{6,7},{8,9}};
-
-
- /* 16 items - 60 comparisons; 15 - 56; 14 - 51; 13 - 46 */
- int net16[60][2] = {
- {1,2},{3,4},{5,6},{7,8},{9,10},{11,12},{13,14},
- {15,16},{1,3},{5,7},{9,11},{13,15},{2,4},{6,8},
- {10,12},{14,16},{1,5},{9,13},{2,6},{10,14},{3,7},
- {11,15},{4,8},{12,16},{1,9},{2,10},{3,11},{4,12},
- {5,13},{6,14},{7,15},{8,16},{6,11},{7,10},{4,13},
- {8,12},{14,15},{2,3},{5,9},{2,5},{8,14},{3,9},
- {12,15},{3,5},{6,7},{10,11},{12,14},{4,9},{8,13},
- {7,9},{4,6},{8,10},{11,13},{4,5},{6,7},{8,9},{10,11},
- {12,13},{7,8},{9,10}};
-
- /* Extracts a network for n items from an array of
- * comparison pairs for m items when n <= m. Expects
- * the 2nd member of each pair to be the larger. For
- * example, to extract a minimum comparison network
- * for 9 items call
- * extract(9, sizeof(net10)/(2*sizeof(int)), net10); */
- extract(n, m, network)
- int n, m;
- int network[][2];
- {
- int i;
-
- for(i = 0; i < m; i += 1)
- {
- if(network[i][1] <= n)
- printf("swap(%d, %d);\n",
- network[i][0] , network[i][1]);
- }
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- An Efficient Method for Optimizing Binary Trees
-
-
- David W. Schwartz
-
-
- David W. Schwartz is an undergraduate student in Computer Science and
- Economics at Oklahoma State University. He works in Systems Design for
- Dimensional Concepts Inc., a company specializing in hospital Medical Records
- software. He can be reached by mail at Oklahoma State University, c/o Dr. M.
- Samadzadeh, 219 Math Sciences, Stillwater, OK 74078.
-
-
- A binary tree is a data structure used to store dynamic, ordered data. The
- distribution of data within a binary tree greatly influences the efficiency of
- operations on the tree: the more unbalanced the tree, the less efficient the
- data access. Through normal use (i.e., additions and/or deletions), a binary
- tree will generally become unbalanced. Therefore, it is desirable to optimize
- the binary tree periodically to maximize execution time efficiency.
- Many techniques have been devised to prevent trees from becoming unbalanced,
- including AVL trees, red-black trees, and 2-3 trees. These methods generally
- complicate the simple insert, delete, and access routines available with
- standard binary trees. They also generally incorporate the need for additional
- memory in each node.
- It is possible, however, to optimize a binary tree after construction using an
- algorithm as short and simple as the ones used to create and access it. First,
- the tree is formed into a linked list through a standard in-order traversal.
- The tree can then be reformed into its optimally-balanced configuration with a
- simple recursive function.
- This solution allows all access routines to be concise and simple, uses no
- extra data space, and saves time by not balancing a tree when speed is needed
- most, i.e., when the tree is in use.
-
-
- Origin
-
-
- In his article "Optimizing Binary Trees," (Terry 1991) The author suggests
- rebalancing a tree only periodically, by first flattening the tree into a
- linked list (the least speed-efficient case), then "folding" the tree in half,
- forming two subtrees, which can then also be folded recursively. This process
- yields the optimum arrangement for the tree.
- In Terry's algorithm, the only exception to the folding process appears when a
- subtree contains exactly five nodes. Under the normal process of folding, the
- subtree in Figure 1 would result. Note that if the values in the five nodes
- are close together, it becomes impossible to add new nodes except at the
- extreme edges of the tree, thus extending the tree to three levels. If the
- tree is arranged as in Figure 2, any value less than 3 can be added without
- extending the tree. To avoid such sparsely filled trees, Terry always
- positions the short side of the subtree toward the outside of the parent tree.
- His routines are also generic, i.e., they work on any standard binary tree,
- without knowing what data it contains. His routines require only that each
- node have the pointers to its branches at a predictable location in the node.
- The easiest way to meet this requirement is to place the pointers at the
- beginning of the node; any tree whose node structure begins as follows can be
- optimized:
- struct treenode {
- struct treenode *left;
- struct treenode *right;
- ...
- };
-
-
- A Better Way
-
-
- Although Terry's routine yields the optimal node arrangement for any given
- tree, it includes many counterproductive movements. If a tree contains N
- nodes, each time the routine folds the tree, it must alter 0(N) of the nodes.
- The tree must be folded approximately log2(N) times, yielding a total of 0(N *
- log2(N)) alterations to complete the process. It is possible to achieve the
- same result with code that is smaller, faster, and makes only 2N or 0(N)
- alterations in the tree. (For an empirical timing comparison of the two
- methods, see the sidebar entitled "Timing Comparison".)
- First, the tree must be formed into a linked list through a standard in-order
- traversal. Then the tree is reformed into its optimally-balanced configuration
- with a simple recursive routine (Listing 1). The tree passed to this routine
- is assumed to have two pointers as the first elements in the node. Data in the
- node is inconsequential to this discussion. The tree is also assumed to use
- NULL pointers as terminators on the leaf nodes. Thus, a program can use these
- routines by simply passing a void pointer to optree and replacing the old root
- with the return value. A program need only execute the following line of code
- to optimize any binary tree.
- root = (struct treenode *) optree((void *)root);
-
-
- The Code
-
-
- Of the code necessary for the optimization (see Listing 1), the only routine
- available to other source files is optree (from "optimize tree"). It serves as
- the entry point for the module and takes only a single parameter, a pointer to
- the root of the tree to be optimized. The optree function initializes the
- pointers for the linked list and then calls the routine to form the tree into
- a linked list, list_build.
- The list_build function is a modified in-order traversal of the tree. It
- traverses the tree attaching each node to the end of a list and counting how
- many nodes are placed in the list. This count later determines how many nodes
- are in the tree and how the tree should be subdivided optimally. When
- list_build completes, optree begins the optimization process by resetting the
- left variable, to indicate that the tree currently leans to the right, and
- then calling the recursive optimization routine, form_tree.
- The form_tree function works much like a recursive node-counting routine
- turned upside down. A node-counting routine would call itself to find the
- number of nodes in the left subtree, add one for the current node, and call
- itself to find the number of nodes in the right subtree. Conversely, form_tree
- receives as a parameter the number of nodes that should be included in the
- newly-constructed tree. To construct the appropriate tree, form_tree
- determines how many nodes belong in the left subtree, calls itself to remove
- that number of nodes from the linked list, removes a node from the list for
- itself, and then calls itself again to remove the rest of the nodes (the right
- subtree) from the list. The recursion terminates when form_tree is asked to
- construct a tree with fewer than one node (namely, a NULL tree).
- The only complexity in the algorithm involves calculating how many nodes
- belong in each subtree. If the number of nodes to be divided between the two
- subtrees is odd, the extra node should be put in the subtree that is closer to
- the center of the current node's parent. If the current node is a left child
- of node X, the extra node should go into the right subtree of the current
- node. This arrangement forces the shortest paths to the outside of the tree,
- where data is most likely to be added.
- The special five-node case (in which the subtree is unevenly divided, as in
- Figure 2) is also considered in calculating the number of nodes that belong in
- each subtree. Note that when folding a five-node tree, the desired effect can
- be achieved by simply moving the fold one node too far from the center of the
- parent tree. That is, instead of dividing the nodes so that there is one more
- node in one subtree than in the other, simply divide it so that there are two
- more nodes in one subtree than in the other. Thus handling the five-node case
- requires only the following line of code.
- if(num == 5) middle++;
-
-
- External Variables
-
-
- There are five global variables in Listing 1. The decision to make these
- variables global was not reached arbitrarily. The globals are all static and
- thus protected from other modules. The list_base variable is global so that
- one node with a right pointer can be placed on the list before recursion
- begins. This eliminates the need to check for a NULL header each time the
- function is entered, thus increasing efficiency and decreasing code size. The
- other four globals are used by multiple levels of recursion, although only one
- copy of each is necessary. If these variables had not been global, some
- routines would have required several extra parameters. Excess parameters
- consume scarce stack space and add expensive pointer references. Consequently,
- the drawbacks usually associated with the use of global variables are
- overshadowed by the compactness, efficiency, and clarity they provide here.
-
-
- Sorted Data
-
-
- One disadvantage to binary trees is that when adding sorted data they
- degenerate into linked lists and exhibit worst case behavior. The proposed
- optimization algorithm addresses this limitation in two ways. First, the
- optimization algorithm can conveniently "fix" any binary tree that has
- suffered the effects of sorted data previously added. Second, with only a
- small modification the algorithm can add sorted data to a tree as quickly and
- conveniently as the data's sorted nature warrants! Since a large volume of
- sorted data would normally only be added in batch processing, it could be done
- easily as part of the optimization process.
-
- During optimization, the tree is initially formed into a linked list. The
- sorted data is already in some form of a list (or it cannot be considered
- sorted). The two lists merge easily and quickly into a single list. This list
- becomes a balanced tree when given to the optimization function. Thus, adding
- a large volume of sorted data results in optimal performance, not worst-case
- performance.
-
-
- Possible Problems
-
-
- Perhaps the most obvious problem with the routines presented here lies in the
- tendency of list_build to cause a stack overflow when flattening a large,
- badly degenerated tree. A non-recursive in-order traversal algorithm, which
- can be found in many computer science textbooks, will solve this problem.
- Listing 1 uses a recursive approach in the interest of simplicity and brevity.
- The form_tree function should not need to be rewritten to avoid recursion.
- Nearly all implementations of C allow for enough levels of recursion to
- optimize millions of nodes. A well-optimized binary tree of height 32 can
- contain over four billion nodes, many times the number of nodes normally
- expected in a binary tree.
-
-
- Conclusion
-
-
- Any implementation of a binary tree can benefit from having the data in the
- tree redistributed evenly. The routines presented here provide a means to
- achieve the most speed-efficient distribution of the data, without the
- complexities involved in balancing the tree as it is growing and shrinking.
- The standard routines for manipulation of binary trees are renowned for being
- small, easy to understand, and fairly efficient. Every attempt was made to
- keep the optimization routines within that tradition.
- The optimization routines presented here are a significant improvement over
- the routines presented by Terry (1991) which inspired their creation. When
- compared to the original routines, these routines:
- are smaller--they compile to smaller executable code and take fewer pages to
- list;
- are easier to understand--only one function is necessary to rebuild a tree
- from a list;
- require less stack space--fewer parameters are used for the recursive
- functions; and
- execute faster.
-
-
- Bibliography
-
-
- Terry, Bruce. June 1991. "Optimizing Binary Trees." The C Users Journal.
- 65-74.
-
-
- Timing Comparison
-
-
- In these tests, a small program, which adds the same random numbers to each of
- two binary trees, optimizes each copy of the tree 30 times. (Since the tree is
- initially flattened and then rebuilt, rebuilding a balanced tree should take
- just as long as rebuilding a degenerate one.) Thirty repetitions was deemed to
- be sufficient to cancel any round-off or timer errors. Table 1 gives the time
- needed for each routine to complete its optimizations. Figure 3 is a graphical
- representation of the data in the table.
- Figure 1 Normal result of a five-node tree
- Figure 2 Preferred result of a five-node tree
- Figure 3 Execution time comparison
- Table 1 Execution times for original and proposed methods
-
- Listing 1 Code for optimizing a binary tree
- #include <stdio.h>
-
- /* TREE STRUCT: GENERIC BINARY TREE NODE */
- typedef struct treenode {
- struct treenode *left;
- struct treenode *right;
- /* Data is unimportant ...*/
- } TREENODE;
-
- /* STATIC VARIABLES */
- static int list_num;
- static TREENODE list_base;
- static TREENODE *list_tail;
- static TREENODE *root;
- static int left;
-
- /*list_num IS SIMPLY A COUNTER OF HOW MANY ITEMS HAVE
- BEEN ADDED TO THE LIST.
- list_base IS USED AS A DUMMY NODE AT THE HEAD OF THE
- LIST TO PREVENT TESTING FOR A NULL ROOT
- EVERY TIME A NODE IS ADDED TO THE LIST.
- list_tail IS USED AS A POINTER TO THE LAST NODE IN
-
- THE LINKED LIST.
- root IS USED AS A POINTER TO THE NODE AT THE
- HEAD OF THE LIST.
- left IS A BOOLEAN TO KEEP TRACK OF WHICH WAY WE
- ARE GOING IN THE TREE SO THAT WE DIVIDE
- UNEVEN LISTS CORRECTLY. ALTHOUGH IT WOULD
- BE MORE CLEAR TO USE A PARAMETER, THE
- ROUTINE IS FINISHED WITH left BEFORE IT
- RECURSES (IT IS NO LONGER USING IT) AND
- MAKING IT STATIC REDUCES THE STACK SIZE
- REQUIRED FOR THE RECURSION. */
-
- /* Function prototypes */
- void *optree(void *);
- static void list_build(TREENODE *);
- static TREENODE *form_tree (int);
-
- /* OPTIMIZE A TREE */
- void *optree(void *opt_root)
- {
- if (opt_root == NULL) return(NULL);
-
- list_tail = &list_base;
- list_num = 0;
-
- list_build(opt_root);
-
- root = list_base.right;
-
- left = 0;
- return((void *) form_tree(list_num));
- }
-
- /* FORM AN OPTIMIZED TREE FROM LIST */
- TREENODE *form_tree(int num)
- {
- int middle;
- TREENODE *ptr;
-
- middle = (num >> 1); /* (num / 2) */
-
- /* SPECIAL 5-NODE CASE */
- if(num == 5) middle++;
-
- /* LEAN BRANCH TO CENTER OF TREE */
- if(left) middle = num - middle - 1;
-
- /* REMOVE LEFT SUBTREE FROM LIST */
- left = 1;
- ptr = (middle > 0) ? form_tree(middle) : NULL;
- root->left = ptr;
-
- /* REMOVE THIS NODE FROM LIST */
- ptr = root;
- root = root->right;
-
- /* REMOVE RIGHT SUBTREE FROM LIST */
- left = 0;
- middle =num - middle - 1;
-
- ptr->right = (middle > 0) ? form_tree(middle) : NULL;
- return ptr;
- }
-
- /* FLATTEN TREE INTO LINKED LIST */
- void list_build(TREENODE *node)
- {
- if(node->left) list_build(node->left);
-
- list_tail->right = node;
- list_tail = node;
- lis_num++;
-
- if(node->right) list_build(node->right);
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Library of Financial Functions
-
-
- William Smith
-
-
- William Smith is the engineering manager at Montana Software, a software
- development company specializing in custom applications for MS-DOS and
- Windows. You may contact him by mail at P.O. Box 663, Bozeman, MT 59771-0663.
-
-
- The need in science and engineering for high-speed data processing was the
- original driving force behind the invention and the later improvement of
- computer hardware. It did not take long, however, before business became the
- largest user of computers and the driving force behind the improvement and
- diversification of computer software.
- Presently, the largest volume categories of software applications are
- business-oriented. Business uses of computers and software cover such diverse
- applications as financial forecasting, economic modeling, accounting, tracking
- inflation, record keeping, materials planning, inventory, depreciation,
- document processing, taxes, and bookkeeping, to name just a few.
- As a whole, business software is concerned with money. A central theme to
- business-oriented calculations and modeling is the time value of money. Loans,
- investments, rate-of-return calculations, depreciation, taxes, and
- cost/benefit analysis all require calculations based upon interest rates over
- periods of time. Nearly everyone has a loan, a bank account, or credit card.
- Interest rates and the resulting time value of money directly affects us all.
- Computations that involve interest rates are tedious by hand. They require
- using interest rate tables to relieve some of the number pushing. Computer
- automation can easily handle these types of calculations.
- More than a couple of times I have had to incorporate the equivalent of
- interest-rate tables into a software project. The relationships between the
- three quantities present value, future values, and equivalent uniform series
- form six basic formulas. These formulas are a function of interest rates and
- time expressed as number of periods.
- The theory of compounding, or how often you calculate the interest, further
- complicates matters. Calculations with continuous compounding requires an
- additional set of six formulas.
- In total, there are twelve interest rate formulas that I have utilized when
- writing business applications. These formulas eventually became a small
- library of C functions and macros that I have used many times. They are
- simple, small, and constitute a useful addition to your library and arsenal of
- programming tools.
- There are additional interest rate formulas for increasing series of values
- known as gradients. I have never had to use these formulas, so they have not
- appeared in my financial function library yet.
-
-
- Numeric Money Types
-
-
- When writing a business application in C, one of the first stumbling blocks
- you will come up against is which numeric type works best to represent money.
- Representing money values in C is a challenge. Floating-point and integer
- types have some contrasting pros and cons. Using a floating-point value to
- represent money has the problem of requiring a conversion to fixed point. This
- requires rounding off and can eventually lead to round-off error. Using an
- integer value has the disadvantage of not covering a large enough range of
- values under some compiler/operating system combinations.
- C-library vendors have addressed these problems by offering business-math
- libraries. There are many business-math libraries available that each vendor
- claims overcomes the problems of round-off error and range. I have never used
- any so I cannot make any statements as to whether they overcome some of the
- problems that developers typically encounter when working with money.
- For the interest-rate functions, I choose to use the standard floating-point
- type double. I choose this because the functions work with interest rates and
- periods. Neither of these is a money type. Interest rate is a true
- floating-point value. The number of periods is an integer value. The value
- returned by the functions is also a floating-point value. You use this value
- on or with money values, but it is not a money value. This gets the
- interest-rate functions out of the dilemma of having to commit to a best way
- to represent a money type. It is up to the user of the functions to determine
- how best to handle rounding and representation of money values.
-
-
- Interest-Rate Formulas
-
-
- The basic requirements of interest calculations involve working with the
- concepts of present values, future values, and equivalent uniform series of
- values. The relationships between these three quantities depend upon interest
- rates and time. For the purpose of the formulas, time is broken up into
- periods. The interest rate values must apply to the same period.
- There are six possible relationships between the three values of present value
- (P), future value (F), and equivalent uniform series of values (A). Table 1
- contains some symbols and definitions that the interest rate formulas use.
- The relationships between A, F, and P are represented by ratios between the
- values. Each of the six formulas calculate one of the possible six ratios. The
- relationship or ratio depends only on the interest rate and number of periods.
- Table 2 lists the six relationships for discrete compounding. The table
- includes the formula as a function of interest rate (i) and number of periods
- (n). The formulas and functions require the interest rate as a floating-point
- number, not a percentage. The table also lists the symbolic representation of
- the formula as a ratio between A, F, or P. The first column in the table is
- the name of the C-function rendition of the formula.
- The formulas in Table 2 assume discrete compounding. They expect the interest
- rate to apply to the same period as the compounding frequency. As long as this
- criterion holds, these formulas will work for any interest rate and number of
- periods. If the compounding frequency is different from the interest period,
- the number of periods and interest per period must be converted into values
- adjusted for compounding periods and interest. This is done by dividing the
- interest rate by the compounding frequency per period and multiplying the
- number of periods by the compounding frequency.
- For example, if the interest rate is 0.05 (5.0%) per period, the period length
- is one year, the number of periods is 10, and the compounding is monthly, you
- must adjust both the interest rate and the number of periods to use the
- formulas in Table 2. The interest rate (i) becomes
- 0.05 / 12 = 0.004167
- and the number of periods (n) are
- 10 * 12 = 120.
- Continuous compounding requires entirely different formulas. You can think of
- continuous compounding as increasing the compounding frequency to its absolute
- limit. The formulas still use an interest rate and the number of periods.
- Since the interest-rate period is no longer equal to the compounding period,
- the interest rate is no longer the effective interest rate, but a nominal
- interest rate (r). The nominal interest rate does not include the effect of
- compounding.
- Table 3 lists the six interest-rate formulas and functions for continuous
- compounding. The symbolic representation of the formulas is the same as in
- Table 2. The Symbols column is a ratio between A,F, or P. I have changed the C
- function names by adding _c to the end of the name. This indicates that the
- function is for continuous compounding. The formulas depend upon the nominal
- interest rate per period and the number of periods. In addition, the formulas
- use the constant e (natural log base).
-
-
- Functions and Macros
-
-
- Listing 1, FINANCES.C, contains the twelve functions listed in Table 2 and
- Table 3. These functions are interdependent. They make calls to one another. I
- have optimized these functions for size. The functions are as terse as
- possible. They rely upon the similarities between one another to keep them
- short and compact. This may take the least amount of overall code space, but
- is not the fastest rendition possible. For those who need speed, Listing 2,
- FINANCES.H contains macro equivalents to the functions in Listing 1. I have
- optimized the macros for speed. The macros avoid calling any of the functions
- in Listing 1. Listing 2 also contains prototypes for all the functions in
- Listing 1.
- If you want to create a hybrid of these two versions you can change the way
- each function is coded. Just include a call to a macro inside a function
- wrapper. For example you can rewrite the function a_to_f as
- double a_to_f( double i, int n )
- {
- return ( A_TO_F( i, n ) );
- }
- This would speed up the functions by avoiding interdependency and calls to
- other functions in the library.
- Here is an example of using the functions to calculate the monthly payment
- required to pay back a $100,000.00 loan in 30 years with a 8% annual interest
- rate. This is a typical mortgage these days.
- Payment = 100000.0 * p_to_a( 0.08 / 12.0, 30 * 12 );
- (Monthly payment is $733.77)
-
-
-
- Conclusions
-
-
- Calculations involving interest rates are fundamental to business and
- financial software. This library of functions presents a set of tools. They
- are building blocks you can use to add time value of money features to your
- applications. I have used them in applications ranging from a simple Real
- Estate loan-calculation program to a sophisticated financial-modeling
- application.
- Do not underestimate their utility. The Real Estate loan calculation program
- was extremely simple. It consisted of nothing more than a very easy to use
- interface on top of these functions. I created this program as a vertical
- application for some local realtors who were computer-illiterate and
- computer-intimidated. For a very small amount of work it garnered a handsome
- rate of return.
- Table 1 Symbols and definitions used by the interest rate formulas
- Symbol Description
- -----------------------------------------
- A uniform series amount or annuity
- F future value or worth
- P present value or worth
- e natural logarithm base (2.718)
- n number of periods
- i effective interest rate per period
- r nominal interest rate per period
- Table 2 Interest rate functions and formulas for discrete compounding
- Function Description Symbol Formula
- Name
- ----------------------------------------------------------
- p_to_f present value to future value F/P (1 + i)n
- --------
- 1
-
- f_to_p future value to present value P/F 1
- --------
- (1 + i)n
-
- f_to_a future value to annuity A/F i
- ----------
- (1 + i)n-1
-
- p_to_a present value to annuity A/P i(1 + i)n
- ------------
- (1 + i)n - 1
-
- a_to_f annuity to future value F/A (1 + i)n - 1
- ------------
- i
-
- a_to_p annuity to present value P/A (1 + i)n - 1
- ------------
- i(1 + i)n
- Table 3 Interest rate functions and formulas for continuous compounding
- Function Description Symbol Formula
- Name
- ------------------------------------------------------------
- p_to_f_c present value to future value F/P ern
- ---
- 1
-
- f_to_p_c future value to present value P/F 1
- ---
- ern
-
- f_to_a_c future value to annuity A/F (er - 1)
- ---------
- (ern - 1)
-
-
- p_to_a_c present value to annuity A/P (er - 1)ern
- -----------
- (ern - 1)
-
- a_to_f_c annuity to future value F/A (ern - 1)
- ---------
- (er - 1)
-
- a_to_p_c annuity to present value P/A (ern - 1)
- -----------
- (er - 1)ern
-
- Listing 1 FINANCES.C -- functions for calculating interest rate formulas
- /******************************************************
- File Name: FINANCES.C
- Description: Library of functions for
- calculating interest rate
- formulas
- Global Function List: a_to_f
- a_to_f_c
- a_to_p
- a_to_p_c
- f_to_a
- f_to_a_c
- f_to_p
- f_to_p_c
- p_to_a
- p_to_a_c
- p_to_f
- p_to_f_c
- Portability: Standard C
- ******************************************************/
- /* Standard C */
- #include <math.h>
- /* Own */
- #include <finances.h>
-
- /*****************************************************
- Name: a_to_f
- Description: annuity to future value
- Parameters: i - effective interest rate per period
- n - number of periods
- Return: F/A - annuity to future value factor
- *****************************************************/
- double a_to_f( double i, int n )
- {
- return ( ( p_to_f( i, n ) - 1.0 ) / i );
- }
-
- /*****************************************************
- Name: a_to_f_c
- Description: Annuity to future value
- continuous compounding
- Parameters: r - nominal interest rate per period
- n - number of periods
- Return: F/A - Annuity to future value factor
- *****************************************************/
- double a_to_f_c( double r, int n )
- {
-
- return ( ( p_to_f_c( r, n ) - 1.0 ) /
- ( pow( LN BASE, r ) - 1.0 ) );
- }
-
- /*****************************************************
- Name: a_to_p
- Description: annuity to present value
- Parameters: i - effective interest rate per period
- n - number of periods
- Return: P/A - annuity to present value factor
- *****************************************************/
- double a_to_p( double i, int n )
- {
- return ( a_to_f( i, n ) / p_to_f( i, n ) );
- }
-
- /*****************************************************
- Name: a_to_p_c
- Description: annuity to present value
- continuous compounding
- Parameters: r - nominal interest rate per period
- n - number of periods
- Return: P/A - annuity to present value factor
- *****************************************************/
- double a_to_p c( double r, int n )
- {
- return ( a_to_f_c( r, n ) / p_to_f_c( r, n ) );
- }
-
- /*****************************************************
- Name: f_to_a
- Description: future value to annuity
- Parameters: i - effective interest rate per period
- n - number of periods
- Return: A/F - future value to annuity factor
- *****************************************************/
- double f_to_a( double i, int n )
- {
- return ( 1.0 / a_to_f( i, n ) );
- }
-
- /*****************************************************
- Name: f_to_a c
- Description: future value to annuity
- continuous compounding
- Parameters: r - nominal interest rate per period
- n - number of periods
- Return: A/F - future value to annuity factor
- *****************************************************/
- double f_to_a c( double r, int n )
- {
- return ( 1.0 / a_to_f_c( r, n ) );
- }
- /*****************************************************
- Name: f_to_p
- Description: future value to present value
- Parameters: i - effective interest rate per period
- n - number of periods
- Return: P/F - future to present value factor
-
- *****************************************************/
- double f_to_p( double i, int n )
- {
- return ( 1.0 / p_to_f( i, n ) );
- }
- /*****************************************************
- Name: f_to_p_c
- Description: future value to present value
- continuous compounding
- Parameters: r - nominal interest rate per period
- n - number of periods
- Return: P/F - future to present value factor
- *****************************************************/
- double f_to_p_c( double r, int n )
- {
- return ( 1.0 / p_to_f_c( r, n ) );
- }
- /*****************************************************
- Name: p_to_a
- Description: present value to annuity
- Parameters: i - effective interest rate per period
- n - number of periods
- Return: A/P - present value to annuity factor
- *****************************************************/
- double p_to_a( double i, int n )
- {
- return ( p_to_f( i, n ) / a_to_f( i, n ) );
- }
-
- /****************************************************
- Name: p_to_a_c
- Description: present value to annuity
- continuous compounding
- Parameters: r - nominal interest rate per period
- n - number of periods
- Return: A/P - present value to annuity factor
- ****************************************************/
- double p_to_a_c( double r, int n )
- {
- return ( p_to_f_c( r, n ) / a_to_f_c( r, n ) );
- }
-
- /****************************************************
- Name: p_to_f
- Description: present value to future value
- Parameters: i - effective interest rate per period
- n - number of periods
- Return: F/P - present to future value factor
- ****************************************************/
- double p_to_f( double i, int n )
- {
- return ( pow( 1 + i, n ) );
- }
-
- /****************************************************
- Name: p_to_f_c
- Description: present value to future value
- continuous compounding
- Parameters: r - nominal interest rate per period
-
- n - number of periods
- Return: F/P - present to future value factor
- ****************************************************/
- double p_to_f_c( double r, int n )
- {
- return ( pow( LN_BASE, r * n ) );
- }
-
- /* End of File */
-
-
- Listing 2 FINANCES.H
- /****************************************************
- File Name: FINANCES.H
- Description: Include file for FINANCES.C
- Portability: Standard C
- ****************************************************/
-
- #if !defined ( FINANCES_DEFINED )
-
- /* Prototypes for function in FINANCES.C */
- double a_to_f( double i, int n );
- double a_to_f_c( double r, int n );
- double a_to_p( double i, int n );
- double a_to_p_c( double r, int n );
- double f_to_a( double i, int n );
- double f_to_a_c( double r, int n );
- double f_to_p( double i, int n );
- double f_to_p_c( double r, int n );
- double p_to_a( double i, int n );
- double p_to_a_c( double r, int n );
- double p_to_f( double i, int n );
- double p_to_f_c( double r, int n );
-
- /* Natural log base */
- #define LN_BASE 2.718281828459
-
- /* Macro versions of functions in FINANCES.C */
- #define A_TO_F( i, n ) \
- ( ( pow( 1.0 + i, n ) - 1.0 ) / i )
-
- #define A_TO_F_C( r, n ) \
- ( ( pow( LN_BASE, r * n ) - 1.0 ) / \
- ( pow( LN_BASE, r ) - 1.0 ) )
-
- #define A_TO_P( i, n ) \
- ( ( pow( 1.0 + i, n ) - 1.0 ) / \
- ( i * pow( 1.0 + i, n) ) )
-
- #define A_TO_P_C( r, n ) \
- ( ( 1.0 - pow( LN_BASE, -( r * n ) ) ) / \
- ( pow( LN_BASE, r ) - 1.0 ) )
-
- #define F_TO_A( i, n ) \
- ( i / ( pow( 1.0 + i, n ) - 1.0) )
-
- #define F_TO_A_C( r, n ) \
- ( ( pow( LN_BASE, r ) - 1.0 ) / \
- ( pow( LN_BASE, r * n ) - 1.0 ) )
-
-
- #define F_TO_P( i, n ) \
- ( 1.0 / pow( 1.0 + i, n ) )
-
- #define F_TO_P_C( r, n ) \
- ( 1.0 / pow( LN_BASE, r * n ) )
-
- #define P_TO_A( i, n ) \
- ( i * pow ( 1.0 + i, n ) / \
- ( pow( 1.0 + i, n ) - 1.0 ) )
-
- #define P_TO_A_C( r, n ) \
- ( ( pow( LN_BASE, r ) - 1.0 ) / \
- ( 1.0 - pow( LN_BASE, -( r * n ) ) ) )
-
- #define P_TO_F( i, n ) \
- (pow( 1.0 + i, n) )
-
- #define P_TO_F_C( r, n ) \
- ( pow( LN_BASE, r * n ) )
-
- #endif
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Making C++ Safe for Threads
-
-
- Dave Plauger
-
-
- Dave has over 20 years experience with the design and development of operating
- systems. His main areas of expertise include real time and multi processing.
- Dave currently serves as Technical Editor of POSIX 1003.14 (Multiprocessing
- Profile) and has been a member of various POSIX working groups since the days
- of /usr/group. Dave may be reached at Mercury Computer Systems, Lowell MA. The
- phone number is (508) 458-3100; email djp@mc.com.
-
-
-
-
- Introduction
-
-
- Multithreading is a powerful technique that can speed up application programs
- on both multiprocessors and uniprocessors. On multi processors there is a
- potential speedup due to parallel computation. On uni processors there is a
- potential speedup due to the overlap of I/O with computation.
- The speedup is potential because, in the real world, there are always
- trade-offs. In order to have computational parallelism you need
- synchronization, which adds overhead. In order to overlap I/O with computation
- you again need synchronization in the form of I/O completion events.
- Multi threading helps by hiding (but not eliminating) the complexity and the
- overhead associated with both types of parallelism. In the many cases where
- parallelism does help, programming with threads is a convenient way to use it.
-
-
- Issues of Thread-Safety
-
-
- The main problem that arises when adding threads to programs is synchronizing
- updates to shared variables. The problem arises because updates to data
- structures often require several machine steps. These individual operations
- may be interleaved at random among multiple processors. And that can cause
- corrupted data structures, storage overwrites, and even more unpleasant
- problems. Even on a uni processor threads may be time-shared, which means they
- effectively run in parallel.
- High-level languages generally let you ignore the underlying machine, but
- parallel programming drags you back into worrying about what is really
- happening. On the other hand, you do have enough information in the
- declarations to help spot the potential problems. Variables that are declared
- at file scope and static data within functions clearly need to be protected
- from uncoordinated updates by multiple threads.
- Less obviously, control data structures used by library packages need careful
- attention as well. Examples of these are the FILE structures used in the
- Standard C library and the dirent structures used in UNIX and POSIX. These
- data structures hold state information between calls. Problems may arise if
- they are used in more than one thread at the same time, or even alternately.
- The same issues arise with member data in C++ classes. In the C library,
- something has to be done for errno, stdin, stdout, and stderr (at least), to
- make them thread-safe. In C++, the analogous structures are cin, cout, cerr,
- and clog.
-
-
- Two Solutions
-
-
- There are two general approaches to dealing with a global shared variable in a
- multi-threaded program:
- provide locking in order to synchronize updates
- eliminate the problem by making the variable thread private
- There are lots of mechanisms to enforce locking. Some examples are semaphores,
- monitors, and message queues. The POSIX threads package (IEEE P1003.4a, Draft
- 6) provides the function pthread_mutex_lock and pthread_mutex_unlock.
- Locks should be associated with the data, not the code. Locking code that
- happens to access shared variables may overly limit parallelism. For example,
- in an operating system one has to lock the global run queue or task data
- during updates, but one should avoid having a scheduler lock. The latter
- approach results in a system that may be thread-safe, but it will be slower
- than the original, single-threaded version. The trade-off one makes is to
- implement enough fine-grained locking to maximize parallelism without adding
- too much overhead.
- Besides adding to the overhead of even a single-threaded program, locking
- introduces the problem of deadlock. Deadlock can be avoided by recognizing the
- hierarchical structure of the data, and obtaining locks in a strict order.
- However, this requires global knowledge of all data, which may be impossible
- in a modular system, such as a third-party library.
- Thread-private data may be supported explicitly by the compiler, if you're
- lucky. But there are routines in each thread package that provide for
- thread-private data, because it is indispensable. In POSIX threads, the
- routines are pthread_keycreate, pthread_getspecific, and pthread_setspecific.
- These routines implement an associative memory, in which a shared key is
- associated with a thread-specific pointer.
- The easiest form of thread-private storage to use is the stack. Each thread
- has its own stack, therefore all automatic variables are thread-safe. You can
- also place a data structure in thread-private storage by using malloc (in C)
- or new (in C++) to allocate the storage, then storing the pointer in
- thread-specific data. When a new thread is created, its specific data pointers
- are set to null. Knowing this, the program can tell at runtime when to
- allocate and initialize another instance of the data structure. Therefore, not
- every thread needs a copy of every data structure. pthread_keycreate registers
- a destructor function to be called for each key when the thread exits. This
- function may free the storage and otherwise clean things up.
- It's best to make variables thread-private whenever possible. The access cost
- of thread-specific data is much less compared to lock/unlock overhead, and the
- associated code can be written using the simpler, uni processor model.
-
-
- Other Methods
-
-
- One can dedicate a thread to particular functions, which makes data and code
- thread-private by convention. For example, one could contrive the program so
- that all calls to X-Window routines occur in a particular thread. The initial
- thread--the one which runs main--is the safest one to dedicate to older
- libraries. For backward compatibility, many threads packages associate the
- initial thread with global resources, such as errno.
- Sometimes global shared variables can be used in a multi-threaded program
- without additional protection. For example, a Boolean variable used as a flag
- can be set or cleared without synchronization. The code sequence:
- lock;
- flag=1;
- unlock
- is silly. But, if two or more variables need to change "simultaneously," then
- locking may be needed to protect the program state, disallowing certain
- intermediate states.
- Statistics counters employ another kind of update which may go unprotected. An
- expression like ++count is a multi-step update of a global variable, and is a
- candidate for locking. But if all you ever care about are zero/non-zero values
- of the count, no harm will result even if multiple processors collide on an
- update. (The value will be at least 1.) This is worth mentioning because test
- coverage tools often insert such counters. It is seldom worth the trouble to
- make these tools thread-safe.
-
-
- A Question of Semantics
-
-
-
- Choosing whether to add locking or to make data thread-private is more than a
- matter of mechanics. The bigger question concerns the thread model to be used.
- Are threads to be independent or cooperating? The answer to this question
- determines the implementation strategy.
- If the threads are to be cooperating, meaning they produce or consume the same
- stream of data, then the data must have internal locking and external
- synchronization. In this model, the POSIX opendir/readdir facility, for
- example, would operate with a shared data structure. An application would want
- to have threads alternately read or write directory entries.
- Each independent thread, on the other hand, would produce or consume its own
- stream of data. In this case, the buffering would be thread-private, and there
- would be no need for internal locking. Either approach is viable, it just
- depends upon the application. But what is the library writer supposed to do?
- The answer may be to supply both.
-
-
- A Thread-Safe C++ Library
-
-
- If the underlying C implementation is thread-safe, then C++ is very close to
- it. However, there are a few special considerations. First, the I/O streams
- cin, cout, cerr, and clog are static objects provided by the library. Nothing
- prevents two threads from simultaneously writing to the same output stream,
- and thus corrupting its internal state.
- Second, C++ uses an internal list to keep track of sizes of arrays of objects.
- This is due to the fact that C++ lets you say
- delete [] array-ref
- which implies that the compiler/library has to determine the number of
- elements. (For various reasons, most implementations can't store the size in
- the same chunk of memory as the array itself.) Since an array may be allocated
- on one thread and freed on another, this internal list has to be global.
- Therefore it has to be protected during updates.
- An important issue is whether I/O streams be thread-private or shared. Given
- the nature of the interface, which is to perform I/O by the character, word,
- or line, it doesn't do much good to provide locking within the interface
- functions. For example, one thread may print hello and another print world,
- but the screen output will be jumbled anyway unless the threads coordinate
- their outputs in some way outside of the I/O library. Adding locking within
- each I/O primitive call (inside the interface) will make the I/O library
- thread-safe. Indeed, this was the approach taken for POSIX threads. The
- problem is that it introduces a lot of overhead in the interest of safety.
- Alternatively, you could establish the rule that I/O streams are
- thread-private. Cooperating threads will need to synchronize outside of the
- interface. (Maybe they would have to anyway.) Independent threads would read
- or write streams as efficiently as in the single-threaded case.
- Given this rule, the library only has to concern itself with the streams it
- provides initially, namely: cin, cout, cerr, and clog. In C++, it's fairly
- easy to provide a "wrapper class" that can operate on a thread-private version
- of the original class.
-
-
- Wrapper-Class Implementation
-
-
- The wrapper class defines a small object that holds the thread-specific data
- key, initial file descriptor, and other stream-initialization parameters. The
- declared object has a static initializer which takes care of allocating the
- key. Each of the pre-defined streams is both declared and replaced with a
- macro, as follows:
- extern ostream_withassign cout;// stream declaration
- extern t_ostream_withassign __tcout;// stream wrapper
- #define cout (__tcout.ref())
- Any textual reference to cout is replaced with a call to the wrapper class,
- which returns a reference to the thread-private copy of the stream. The
- thread-private stream is not allocated and initialized until the first call to
- the ref member function is made. This introduces the limitation that &cout may
- not be used in an initializer list--because the address is no longer a
- constant value, but must be computed at runtime. The initial thread points at
- the pre-defined streams. Old binaries will therefore access the initial
- thread's I/O streams. This permits backward compatibility with single-threaded
- code.
- The wrapper is a very small class that defines a static object (such as
- __tcout) which is shared by all threads. The static initializer does three
- things:
- It allocates a key to access thread-private storage.
- It stores the stream-initialization parameters it will need later.
- It points the wrapper at the corresponding global stream.
- Static initializers are called from the initial thread before main executes.
- A pointer to the wrapper class is stored in thread-private storage. This
- pointer is later presented as an argument to the destructor function called by
- thread-exit processing. Therefore, the thread-private wrapper object and its
- stream object are deleted when the thread exits.
- The ref member function simply uses the key stored in the static object and
- tries to look up the thread-private pointer to the wrapper. The very first
- time a new thread calls any I/O function, ref will find that its
- thread-private pointer is null. When the pointer is null, ref allocates a new
- wrapper object and a new stream, then stores the pointer to its wrapper object
- in thread-private storage. Subsequently, ref returns a reference to the stream
- object assocated with the wrapper object, both of which are private to the
- thread.
- With this implementation, only threads that use stream I/O get copies of
- stream objects. Threads can be created at any time, since I/O stream
- allocation occurs on demand. The main cost added to implement thread-private
- streams is one lookup in thread-private storage for every I/O call. This is
- much cheaper than adding lock/unlock to every I/O call.
-
-
- A C++ Wish List
-
-
- It would be nicer if you could have redefined cout as an object of the wrapper
- class instead of having to use a macro. In other words, you would want to say
- extern t_ostream_withassign cout
- Then you could rely on the user-defined type conversion facility to yield a
- reference to the desired class. That is, you would simply define
- operator ostream_withassign&() {return ref();}
- in the wrapper class.
- I tried this and it mostly works, but the following limitations were found:
- Invocations of member functions didn't work. For example, cout.flush() had to
- be transformed to ((ostream_withassign&)(cout)).flush(). Here is a case where
- it would be nice to be able to overload the dot operator. Then you could say:
- ostream_withassign& operator.()
- {return ref();}
- The result is that the wrapper class could substitute a reference to the
- desired class that defines the member functions.
- The compiler complains when the expression in a return statement does not
- match the function's declaration. For example:
- ostream_withassign& f()
- {return cout;}
- would work only if the compiler applied the user-defined type conversion.
- There is a similar warning when the type of an initializer does not match the
- type of the variable being initialized. For example,
- ostream_withassign &os = cout
- would work only if the compiler applied the user-defined type conversion. If
- the user-defined type conversion were applied uniformly and it were possible
- to overload the dot operator, then it would be easier to implement a very
- useful form of indirection for any object.
-
-
- Arrays of Objects
-
-
-
- As I stated above, the other change needed for the C++ library was to add
- locking around an internal list. This change is not visible to the library's
- user. The implementation of the locking mechanics used is perhaps worthy of
- discussion.
- The list is protected with simple, mutual exclusion, using pthread_mutex_lock
- and pthread_mutex_unlock in the underlying implementation. The list-handling
- file has a definition at file scope like
- static_thread_lock_def list_lock;
- where thread_lock_def defines a static initializer that allocates and
- initializes a pthread_mutex_t.
- The body of the code where mutual exclusion is needed has the syntax:
- { thread_lock L(list_lock);
- // do something to the list
- } //end of critical region
- The braces are used to control the scope of L, which is a reference to the
- lock. The class thread_lock acquires the pthread_mutex in its initializer, and
- releases it in its destructor. Thus, the scope of L determines the bounds of
- the critical region.
- This could be considered just another stupid C++ trick except that, should
- there be a return or an exception within the region, the destructor for L will
- be called, which will release the lock. Thus, locking is integrated with
- returns and exceptions because of this method.
- There is a quiet gotcha in this implementation. Should you forget to supply a
- variable name (in this case it's L), and write thread_lock(list_lock), C++
- will lock then immediately unlock list_lock, leaving the critical region
- unprotected.
- The reason there is no warning has something to do with the great debate over
- the lifetime of temporaries. For example, in a simple expression like X + Y
- the compiler may have to make temporary copies of intermediate results, which
- one would expect to get deleted at the end of the expression, not at the end
- of the current scope. See your local language lawyer for details.
-
-
- Conclusion
-
-
- I successfully converted the Hewlett-Packard C++ library to a thread-safe
- form. Along the way, I observed:
- C++ is very close to being thread-safe, except for some potential uses of I/O
- streams and arrays of objects.
- Making global data thread-private is more efficient than adding locking.
- I/O streams should be thread-private, because the shared use of a stream
- requires external coordination anyway.
- Uniform application of user-defined type conversion coupled with the ability
- to overload the dot operator would be useful extensions. This would aid in the
- creation of wrapper classes for indirect access to any object.
- Locking based upon C++ scoping rules integrates locking with exceptions.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Image Processing, Part 9: Histogram-Based Image Segmentation
-
-
- Dwayne Phillips
-
-
- The author works as a computer and electronics engineer with the U.S.
- Department of Defense. He has a PhD in Electrical and Computer Engineering
- from Louisiana State University. His interests include computer vision,
- artificial intelligence, software engineering; and programming languages.
-
-
-
-
- Introduction to the Series of Articles
-
-
- This is the ninth in a series of articles on images and image processing.
- Previous articles discussed reading, writing, displaying, and printing images
- (TIFF format), histograms, edge detection, spatial frequency filtering, and
- sundry image operations. This article will discuss simple image segmentation
- based on histograms and image thresholding.
- Image segmentation is the process of dividing an image into regions or
- objects. It is the first step in the field of image analysis. Image processing
- displays images and alters them to make them look "better," while image
- analysis tries to discover what is in the image.
- The basic idea of image segmentation is to group individual pixels (dots in
- the image) together into regions if they are similar. Similar can mean they
- are the same intensity (shade of gray), form a texture, line up in a row,
- create a shape, etc. There are many techniques available for image
- segmentation, and they vary in complexity, power, and area of application.
-
-
- Histogram-Based Image Segmentation
-
-
- Histogram-based image segmentation is one of the simplest and most often used
- segmentation techniques. It uses the histogram to select the gray levels for
- grouping pixels into regions. In a simple image there are two entities: the
- background and the object. The background is generally one gray level and
- occupies most of the image. Therefore, its gray level is a large peak in the
- histogram. The object or subject of the image is another gray level, and its
- gray level is another, smaller peak in the histogram.
- Figure 1 shows an image example and Figure 2 shows its histogram. The tall
- peak at gray level 2 indicates it is the primary gray level for the background
- of the image. The secondary peak in the histogram at gray level 8 indicates it
- is the primary gray level for the object in the image. Figure 3 shows the
- image of Figure 1 with all the pixels except the 8s blanked out. The object is
- a happy face.
- This illustrates histogram-based image segmentation. The histogram will show
- us the gray levels of the background and the object. The largest peak
- represents the background and the next largest peak the object. We choose a
- threshold point in the valley between the two peaks and threshold the image.
- Thresholding takes any pixel whose value is on the object side of the point
- and sets it to one; it sets all others to zero. The histogram peaks and the
- valley between them are the keys.
- The idea of histogram-based segmentation is simple, but there can be problems.
- Where should you set the threshold point for the image of Figure 1? If you
- choose the point mid-way between the two peaks (threshold point=5), you
- produce the image of Figure 4. This is not the happy face object desired. If
- you choose the valley floor values of 4 or 5 as the threshold point, you also
- have a poor result. The best threshold point would be 7, but how could you
- know that without using trial and error?
- This example is difficult because there are only ten gray levels and the
- object (happy face) is small. In practice, the techniques discussed below will
- perform adequately, but there will be problems. Automatic techniques will
- fail, and you may have to resort to manual methods.
-
-
- Preprocessing: Histogram Equalization and Histogram Smoothing
-
-
- Histogram-based segmentation depends on the histogram of the image. Therefore,
- you must prepare the image and its histogram before analyzing it. The first
- step is histogram equalization (Phillips, August 1991). Histogram equalization
- attempts to alter an image so its histogram is flat and spreads out over the
- entire range of gray levels. The result is an image with better contrast.
- Photograph 1 shows an aerial image of several house trailers with its
- histogram. The contrast is poor and it would be very difficult to find objects
- based on its histogram. Photograph 2 shows the result of performing histogram
- equalization. The contrast is much better and the histogram is spread out over
- the entire range of gray levels. Photograph 3 shows the result of performing
- high-pass filtering (Phillips, October 1992) on the image of photograph 2. It
- is easy to see the house trailers, sidewalks, trees, bushes, gravel roads, and
- parking lots.
- The next preprocessing step is histogram smoothing. When examining a
- histogram, you look at peaks and valleys. Too many tall, thin, peaks and deep
- valleys will cause problems. Smoothing the histogram removes these spikes and
- fills in empty canyons while retaining the same basic shape of the histogram.
- Figure 5 shows the result of smoothing the histogram given in Figure 2. You
- can still see the peaks corresponding to the object and background, but the
- peaks are shorter and the valleys are filled. Photograph 4 shows another
- example of histogram smoothing. The histogram on the left is the original with
- several spikes and canyons. The histogram on the right has been smoothed. I
- will analyze this in the segmentation.
- Smoothing a histogram is an easy operation. It replaces each point with the
- average of it and its two neighbors. Listing 1 shows the smooth_histogram
- function that performs this operation.
-
-
- Thresholding and Region Growing
-
-
- There are two more topics common to all the methods of image segmentation:
- image thresholding and region growing. Image thresholding sets the pixels in
- the image to one or zero. Listing 2 shows the routine threshold_image_array
- that accomplishes this task.
- The difficult task is region growing. Figure 6 shows the result of
- thresholding Figure 1 correctly. The "object" in Figure 6 is a happy face. It
- comprises three different regions (two eyes and the smile). Region growing
- takes this image, groups the pixels in each separate region, and gives them
- unique labels. Figure 7 shows the result of region growing performed on Figure
- 6. Region growing grouped and labeled one eye as region one, the other eye as
- region two, and the smile as region three.
- Figure 8 shows the algorithm for region growing. It begins with an image array
- g comprising zeros and pixels set to a value. The algorithm loops through the
- image array looking for a g(i,j) == value. When it finds such a pixel, it
- calls the label_and_check_neighbor routine. label_and_check_neighbor sets the
- pixel to g_label (the region label) and examines the pixel's eight neighbors.
- If any of the neighbors equal value, they are pushed onto a stack. When
- control returns to the main algorithm, each pixel on the stack is popped and
- sent to label_and_check_neighbor. All the points on the stack equaled value,
- so you set them and check their neighbors. After setting all the pixels in the
- first region, you increment g_label and move on looking for the next region.
- Listing 3 shows the functions that implement region growing with grow being
- the primary routine. grow runs through the region-growing algorithm and calls
- label_and_check_neighbor shown next in Listing 3.
- The grow and label_and_check_neighbor functions follow the region-growing
- algorithm step for step. The only unusual item is the stack. There are several
- ways to implement a stack. I used a simple array and a file. I did this
- because a region could be as large as ROWSxCOLS (10,000 in the C Image
- Processing System), and the stack must be large enough to hold that many
- points. I push points onto the stack array by putting them in the array and
- moving the top of stack pointer. When the stack array becomes full, I write it
- to the stack file. If the array fills again, I push the array onto the top of
- the stack file. I pop points off the stack array by reading them and
- decrementing the top of stack pointer. When the stack array is empty, I pop an
- array off the stack file. The final four functions of Listing 3
- (push_data_onto_stack_file, pop_data_off_of_stack_file, append_stack_files,
- and copy_stack_files) implement the push and pop functions for the stack array
- and file.
-
-
- Histogram-Based Segmentation Techniques
-
-
- There are four segmentation techniques: the manual technique, histogram peak
- technique, histogram valley technique, and an adaptive technique.
-
-
- The Manual Technique
-
-
-
- In the manual technique the user inspects an image and its histogram manually.
- Trial and error come into play and the result is as good as you want it to be.
- I used Photograph 4 as the input for all the segmentation examples. Photograph
- 5, Photograph 6, and Photograph 7 show the result of segmentation using three
- different thresholds. The result in Photograph 5 used a high of 255 and a low
- of 125. The segmentation included the white gravel roads as well as the house
- trailers and sidewalks. The result in Photograph 6 used a high of 255 and a
- low of 175. The gravel roads begin to disappear, but the house trailers and
- sidewalks remain. Photograph 7 shows the result using a high of 255 and a low
- of 225. This segmentation only finds the four dominant house trailers. Which
- answer is correct? That depends on what you wanted to find.
- Note, all image segmentations will appear rough. You can perform additional
- processing to make the result more pleasing to the eye, but that is not the
- purpose of segmentation. The purpose is to break the image into pieces so
- later computer processing can interpret their meaning. The output is for
- computer not human consumption. Also note how difficult it is for the
- computer, even with manual aid, to find objects that are trivial for humans to
- see. Anyone could trace over the input image and outline the objects better
- than the segmentation process.
- Listing 4 shows the code that implements manual segmentation. The function
- manual_threshold_segmentation has the same form as the other application
- functions in this series. It creates the output image file if it does not
- exist, reads in the input data, thresholds it (Listing 2), grows regions if
- requested (Listing 3), and writes the output.
- manual_threshold_segmentation has the usual inputs (image file names, image
- arrays, etc.) as well as the high and low threshold values, and the value and
- segment parameters. The value parameter specifies the value at which to set a
- pixel if it falls between the high and low thresholds. You usually set value
- equal to 1 since those pixels outside the high-low range are set to zero. The
- segment parameter specifies whether or not to grow regions after thresholding.
- Sometimes you only want to threshold an image and not grow regions. The two
- operations are identical except for the last step. If segment == 1, you call
- the region-growing routines.
- Manual segmentation is good for fine tuning and getting a feel for the
- operation. Its trial-and-error nature, however, makes it time consuming and
- impractical for many applications. You need techniques that examine the
- histogram and select threshold values automatically.
-
-
- Histogram Peak Technique
-
-
- The first such technique uses the peaks of the histogram. This technique finds
- the two peaks in the histogram corresponding to the background and object of
- the image. It sets the threshold half way between the two peaks. Look back at
- the smoothed histogram in Figure 5. The background peak is at 2 and the object
- peak is at 7. The midpoint is 4, so the low threshold value is 4 and the high
- is 9.
- The peaks technique is straightforward except for two items. In the histogram
- in Figure 5, you'll note the peak at 7 is the fourth highest peak. The peaks
- at 1 and 3 are higher, but they are part of the background mountain of the
- histogram and do not correspond to the object. When you search the histogram
- for peaks, you must use a peak spacing to ensure the highest peaks are
- separated. If you did not, then you would choose 2 as the background peak and
- 1 as the object peak. Figure 9 shows the disastrous effect of this.
- The second item to watch carefully is determining which peak corresponds to
- the background and which corresponds to the object. Suppose an image had the
- histogram shown in Figure 10. Which peak corresponds to the background? The
- peak for gray level 8 is the highest, but it corresponds to the object not the
- background. The reason is the mountain surrounding the peak at gray level 2
- has a much greater area than that next to gray level 8. Therefore, gray levels
- 0 through 6 occupy the vast majority of the image, and they are the
- background.
- Listing 5 shows the source code to implement the peaks technique.
- peak_threshold_segmentation is the primary function. It checks for the
- existence of the output image, reads the input image, and calculates and
- smoothes the histogram. Next, it calls new functions to find the histogram
- peaks and determine the high and low threshold values. Finally, it thresholds
- the image, performs region growing if desired, and writes the result image to
- the output file.
- The functions find_peaks and insert_into_peaks in Listing 5 analyze the
- histogram to find the peaks for the object and background. These functions
- build a list of histogram peaks. There are several ways to do this. I used an
- array of values. find_peaks loops through the histogram and calls
- insert_into_peaks, which puts the histogram values in the proper place in the
- array. find_peaks ends by looking at the spacing between the largest peaks to
- ensure we do not have a disaster such as shown in Figure 9.
- The function peaks_high_low takes the two peaks from find_peaks and calculates
- the high- and low-threshold values for segmentation. peaks_high-low examines
- the mountains next to the peaks as illustrated in Figure 10. It then finds the
- mid-point between the peaks and sets the high and low threshold values.
- Photograph 8 shows the result of applying the peaks technique to the image of
- Photograph 4. The peaks technique found the two peaks at 255 and 77. The
- mid-point is 166, so the high threshold is 255 and the low threshold is 166.
- This is a reasonably good segmentation of Photograph 4.
-
-
- Histogram Valley Technique
-
-
- The second automatic technique uses the peaks of the histogram, but
- concentrates on the valley between them. Instead of setting the mid-point
- arbitrarily half way between the two peaks, the valley technique searches
- between the two peaks to find the lowest valley.
- Look back at the histogram of Figure 10. The peaks are at gray levels 2 and 8
- and the peaks technique would set the midpoint at 5. In contrast, the valley
- technique searches from 2 through 8 to find the lowest valley. In this case,
- the "valleypoint" is at gray level 7.
- Listing 6 shows the code that implements the valley technique. The primary
- function is valley_threshold_segmentation. It checks for the output file,
- reads the input image, calculates and smoothes the histogram, and finds the
- peaks as peak_threshold_segmentation did. It finds the valley-point via the
- functions valley_high_low, find_valley_point, and insert_into_deltas.
- find_valley_point starts at one peak and goes to the next inserting the
- histogram values into a deltas array via the insert_into_deltas function. This
- uses an array to create a list of deltas in the same manner as
- insert_into_peaks did in Listing 5. Once you have the valleypoint,
- valley_high_low checks the mountains around the peaks to ensure you associate
- the peaks with the background and object correctly.
- Photograph 9 shows the result of applying the valley technique to the image in
- Photograph 4. It found the peaks at 77 and 255 and went from 77 up to 255
- looking for the lowest valley. It pinpointed the lowest valley at gray level
- 241.
-
-
- Adaptive Histogram Technique
-
-
- The final technique uses the peaks of the histogram in a first pass and adapts
- itself to the objects found in the image in a second pass (Castleman 1979). In
- the first pass, the adaptive technique calculates the histogram for the entire
- image. It smoothes the histogram and uses the peaks technique to find the high
- and low threshold values.
- In the second pass, the technique works on each ROWSxCOLS area of the image
- individually. In each area, it segments using the high and low values found
- during the first pass. Then, it calculates the mean value for all the pixels
- segmented into background and object. It uses these means as new peaks and
- calculates new high and low threshold values for that ROWSxCOLS area. Now, it
- segments that area again using the new values.
- Listing 7 shows the code that implements the adaptive technique with
- adaptive_threshold_segmentation being the primary function. It is very similar
- to the peak_threshold_segmentation function of Listing 5 in that it uses all
- that code for its first pass. The second pass starts by calling
- threshold_and_find_means. This function thresholds the image array into
- background and object and calculates the mean pixel value for each. The second
- pass continues by using peaks_high_low to find new threshold values based on
- the background and object means. Finally, you threshold the image using these
- new threshold values.
- Photograph 10 shows the result of applying the adaptive technique to the image
- of Photograph 4. The first pass found the high- and low-threshold values to be
- 255 and 166. On the left side of the photograph, the second pass thresholded
- the image array and found the background mean to be 94 and the object mean to
- be 205. The new threshold values were 255 and 149. On the right side of the
- photograph, the background and object means were 84 and 200 and the new
- threshold values were 255 and 142.
-
-
- Integrating Histogram-Based Segmentation into the C Image Processing System
-
-
- Listing 8 shows the new code for the main routine of CIPS. I've added the
- options of thresholding and segmentation using the four techniques discussed
- above in cases 16 and 17. Listed next are the changes to the CIPS main menu
- and the two functions that interact with the user to obtain the processing
- options.
- Listing 9 shows a stand-alone application program for thresholding and
- segmenting entire image files. It is command- line driven and calls the
- functions shown in the earlier listings.
-
-
- Conclusions
-
-
- This installment in the series introduced image segmentation. This is the
- first step in locating and labeling the contents of an image. The techniques
- discussed work on simple images with good contrast and gray level separation
- between the object and background. You will need other techniques to attack
- more complex images.
- References
- Castleman, Kenneth R. 1979. Digital Image Processing. Prentice-Hall.
- Phillips, Dwayne. August 1991. "Image Processing, Part 4: Histograms and
- Histogram Equalization," The C Users Journal.
- Phillips, Dwayne. October 1992. "Image Processing, Part 7: Spatial Frequency
- Filtering," The C Users Journal.
- Figure 1 An image example
- 22222232221222212222
- 32222321250132123132
- 22588897777788888232
- 12988877707668882122
-
- 22888892326669893213
- 21278221222666665222
- 22002222220226660225
- 21221231223266622321
- 32238852223266821222
- 21288888342288882232
- 22328888899888522121
- 22123988888889223422
- 23222278888882022122
- 22232323883212123234
- 25221212222222222222
- 22122222320222202102
- 20222322412212223221
- 22221212222222342222
- 21222222221222222142
- Figure 2 A histogram of the image in Figure 1
- Figure 3 The image in Figure 1 with all the pixels except the 8's blanked out
- --------------------
- --------------------
- ---888------88888---
- ---888-------888----
- --8888--------8-----
- ----8---------------
- --------------------
- --------------------
- ----88--------8-----
- ---88888----8888----
- ----88888--888------
- ------8888888-------
- -------888888-------
- --------88----------
- --------------------
- --------------------
- --------------------
- --------------------
- --------------------
- Figure 4 Figure 1 with athreshold point of 5
- 00000000000000000000
- 00000000000000000000
- 00011111111111111000
- 00111111101111110000
- 00111110001111110000
- 00011000000111110000
- 00000000000001110000
- 00000000000011100000
- 00001100000011100000
- 00011111000011110000
- 00001111111111000000
- 00000111111111000000
- 00000011111110000000
- 00000000110000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- Figure 5 The result of smoothing the histogram given in Figure 2
- Figure 6 The result of correctly thresholding Figure 1
- 00000000000000000000
-
- 00000000000000000000
- 00011100000011111000
- 00011100000001110000
- 00111100000000100000
- 00001000000000000000
- 00000000000000000000
- 00000000000000000000
- 00001100000000100000
- 00011111000011110000
- 00001111100111000000
- 00000011111110000000
- 00000001111110000000
- 00000000110000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- Figure 7 The result of region growing performed on Figure 6
- 00000000000000000000
- 00000000000000000000
- 00011100000022222000
- 00011100000002220000
- 00111100000000200000
- 00001000000000000000
- 00000000000000000000
- 00000000000000000000
- 00003300000000300000
- 00033333000033330000
- 00003333300333000000
- 00000033333330000000
- 00000003333330000000
- 00000000330000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- 00000000000000000000
- Figure 8 Pseudocode for region growing
- 1. Given an image g with m rows and n columns
- g(i,j) for i=1,m j=1,n
- g(i,j) = value for object
- = 0 for background
- 2. set g_label=2 this is the label value
- 3. for (i=0; i<m; i++)
- scan ith row
- for (j=0; j<n; j++)
- check jth element
- stack_empty = true
- if g(i,j) == value
- label_and_check_neighbor(g(i,j),g_label)
- while stack_empty = false do
- pop element (i,j) off the stack
- label_and_check_neighbor(g(i,j),g_label)
- end while
- g_label = g_label + 1
- end of checking jth element
- end of scanning ith row
- 4. The End
-
- ---------------------------------------
- procedure label_and_check_neighbor(g(r,e), g_label)
- g(r,e) = g_label
- for (R=r-1; r<=r+1; R++)
- for (E=e-1; e<=e+1; e++)
- if g(R,E) == value then
- push (R,E) onto the stack
- stack_empty = false
- end if
- end loop over E
- end loop over R
- end procedure label_and_check_neighbor
- Figure 9 Result of incorrectpeak separation when applying the histogram peak
- technique to Figure 1
- ----------*----*----
- -------*--**--*--*--
- --------------------
- *--------*-------*--
- ------------------*-
- -*-----*------------
- --**------*-----*---
- -*--*--*-----------*
- ----------------*---
- -*------------------
- -----------------*-*
- --*-----------------
- --------------*--*--
- ------------*-*-----
- ----*-*-------------
- --*-------*----*-**-
- -*-------*--*------*
- ----*-*-------------
- -*--------*------*--
- Figure 10 A histogram in which the highest peak does not correspond to the
- background
- Photograph 1 Aerial image with poor contrast and histogram
- Photograph 2 Result of histogram on Photograph 1
- Photograph 3 Result of high-pass filtering on Photograph 2
- Photograph 4 Image portion with histogram and smoothed histogram
- Photograph 5 Threshold of Photograph 4 withhigh=225 and low=125
- Photograph 6 Threshold of Photograph 4 withhigh=255 and low=175
- Photograph 7 Threshold of Photograph 4 withhigh=255 and low=225
- Photograph 8 Threshold of Photograph 4 using peaks technique (high=255,
- low=166)
- Photograph 9 Threshold of Photograph 4 using valleytechnique (high=255,
- low=241)
- Photograph 10 Threshold of Photograph 4 using adaptive technique (high=255,
- low=149 for left side; high=255, low=142 for right side)
-
- Listing 1 Function for smoothing a histogram
- /*********************************************
- *
- * smooth_histogram(...
- *
- * This function smoothes the input histogram
- * and returns it. It uses a simple averaging
- * scheme where each point in the histogram
- * is replaced by the average of itself and
- * the two points on either side of it.
- *
- *********************************************/
-
- smooth_histogram(histogram)
- unsigned long histogram[];
-
- {
- int i;
- unsigned long new_hist[GRAY_LEVELS+1];
-
- zero_histogram(new_hist);
-
- new_hist[0] = (histogram[0] + histogram[1])/2;
- new_hist[GRAY_LEVELS] =
- (histogram[GRAY_LEVELS] +
- histogram[GRAY_LEVELS-1])/2;
-
- for(i=1; i<GRAY_LEVELS; i++){
- new_hist[i](histogram[i-1] +
- histogram[i] +
- histogram[i+1])/3;
- }
-
- for(i=0; i<=GRAY_LEVELS; i++)
- histogram[i] = new_hist[i];
-
- } /* ends smooth_histogram */
- /* End of File */
-
-
- Listing 2 Function for image thresholding
- /*********************************************
- *
- * threshold_image_array(...
- *
- * This function thresholds an input image array
- * and produces a binary output image array.
- * If the pixel in the input array is between
- * the hi and low values, then it is set to value.
- * Otherwise, it is set to 0.
- *
- *********************************************/
-
- threshold_image_array(in_image, out_image, hi, low, value)
- short hi, low, in image[ROWS][COLS],
- out_image[ROWS][COLS], value;
- {
- int counter = 0, i, j;
- for(i=0; i<ROWS; i++){
- for(j=0; j<COLS; j++){
- if(in_image[i][j] >= low &&
- in_image[i][j] <= hi){
- out_image[i][j] = value;
- counter++;
- }
- else
- out_image[i][j] = 0;
- } /* ends loop over j */
- ) /* ends loop over i */
- printf("\n\tTIA> set %d points", counter);
- } /* ends threshold_image_array */
- /* End of File */
-
-
- Listing 3 Functions that implement region growing
-
- /*********************************************
- * grow(...
- * This function is an object detector.
- * Its input is an binary image array
- * containing 0's and value's.
- * It searches through the image and connects
- * the adjacent values.
- **********************************************/
-
- grow(binary, value)
- short binary[ROWS][COLS],
- value;
- {
- char name[80];
-
- int first_call,
- i,
- j,
- object_found,
- pointer,
- pop_i,
- pop_j,
- stack_empty,
- stack_file_in_use;
-
- short g_label, stack[STACK_SIZE][2];
-
- /***********************************
- * Now begin the process of growing
- * regions.
- *************************************/
-
- g_label = 2;
- object_found = 0;
- first_call = 1;
-
- for(i=0; i<ROWS; i++){
- for(j=0; j<COLS; j++){
- stack file in use = 0;
- stack_empty = 1;
- pointer = -1;
-
- /*********************************
- * Search for the first pixel of
- * a region.
- **********************************/
-
- if(binary[i][j] == value){
- label_and_check_neighbor(binary, stack, g_label,
- &stack_empty, &pointer, i, j,
- value, &stack_file_in_use,
- &first_call);
- object_found = 1;
- } /* ends if binary[i]j] == value */
-
- /******************************
- * If the stack is not empty,
- * pop the coordinates of
- * the pixel off the stack
-
- * and check its 8 neighbors.
- *******************************/
-
- while(stack_empty == 0){
- pop_i = stack[pointer][0]; /* POP */
- pop_j = stack[pointer][1]; /* OPERATION */
- --pointer;
- if(pointer <= 0){
- if(stack_file_in_use){
- pop_data_off_of_stack_file(
- stack,
- &pointer,
- &stack_file_in_use);
- } /* ends if stack_file_in_use */
- else{
- pointer = 0;
- stack_empty = 1;
- } /* ends else stack file is
- not in use */
- } /* ends if point <= 0 */
-
- label_and_check_neighbor(binary,
- stack, g_label,
- &stack_empty,
- &pointer, pop_i,
- pop_j, value,
- &stack_file_in_use,
- &first_call);
- } /* ends while stack_empty == 0 */
-
- if(object_found == 1){
- object_found = 0;
- ++g_label;
- } /* ends if object_found == 1 */
-
- } /* ends loop over j */
- } /* ends loop over i */
-
- printf("\nGROW> found %d objects", g_label);
-
- } /* ends grow */
-
- /********************************************
- * label_and_check_neighbors(...
- * This function labels a pixel with an object
- * label and then checks the pixel's 8
- * neighbors. If any of the neigbors are
- * set, then they are also labeled.
- **********************************************/
-
- label_and_check_neighbor(binary_image, stack,
- g_label, stack_empty,
- pointer, r, e, value,
- stack_file_in_use,
- first_call)
- int e,
- *first_call,
- *pointer,
- r,
-
- *stack_empty,
- *stack_file_in_use;
-
- short binary_image [ROWS] [COLS],
- g_label,
- stack[STACK_SIZE][2],
- value;
- {
- int already_labeled = 0,
- i, j;
-
- if (binary_image[r][e] == g_label)
- already_labeled = 1;
-
- binary_image[r][e] = g_label;
-
- /*************************************
- * Look at the 8 neighors of the
- * point r,e.
- * Ensure the points you are checking
- * are in the image, i.e. not less
- * than zero and not greater than
- * ROWS-1 or COLS-1.
- **************************************/
-
- for(i=(r-1); i<=(r+1); i++){
- for(j=(e-1); j<=(e+1); j++){
-
- if((i>=0) && (i<=ROWS-1) && (j>=0) && (j<=COLS-1)) {
-
- if(binary_image[i][j] == value){
- *pointer = *pointer + 1;
- stack[*pointer][0] = i; /* PUSH */
- stack[*pointer][1] = j; /* OPERATION */
- *stack_empty = 0;
-
- if(*pointer >= (STACK_SIZE -
- STACK_FILE_LENGTH)){
- push_data_onto_stack_file(stack,
- pointer, first_call);
- *stack_file_in_use = 1;
- } /* ends if *pointer >=
- STACK_SIZE - STACK_FILE_LENGTH*/
-
- } /* end of if binary_image == value */
- } /* end if i and j are on the image */
- } /* ends loop over i rows */
- } /* ends loop over j columns */
- } /* ends label_and_check_neighbors */
-
- /***************************************
- * push_data_onto_stack_file(...
- * This function takes the stack array
- * and pushes it onto the stack file.
- *****************************************/
-
- push_data_onto_stack_file(stack, pointer, first_call)
- int *first_call, *pointer;
- short stack[STACK_SIZE][2];
-
- {
- char backup_file_name[MAX_NAME_LENGTH];
- FILE *backup_file_pointer, *stack_file_pointer;
- int diff, i;
- short holder[STACK_FILE_LENGTH][2];
-
- printf("\nSFO> Start of push_data_onto_stack ");
-
- diff = STACK_SIZE - STACK_FILE_LENGTH;
-
- /*****************************************
- * Copy the elements to be stored to the
- * stack file into holder
- ******************************************/
-
- for(i=0; i<STACK_FILE_LENGTH; i++){
- holder[i][0] = stack[i][0];
- holder[i][1] = stack[i][1];
- }
- /*****************************************
- * Move the elements of the stack down
- *****************************************/
-
- for(i=0; i<diff; i++){
- stack[i][0] = stack[i + STACK_FILE_LENGTH][0];
- stack[i][1] = stack[i + STACK_FILE_LENGTH][1];
- }
-
- /*****************************************
- * Fill the top of the stack with zeros
- *****************************************/
-
- for(i=diff; i<STACK_SIZE; i++){
- stack[i][0] = 0;
- stack[i][1] = 0;
- }
-
- *pointer = *pointer - STACK_FILE_LENGTH;
-
- /*************************************************
- * Store the holder array into the stack file.
- * Open the stack file for writing in binary
- * mode. If the file does not exist it will be
- * created. If the file does exist it will be
- * over written.
- * PUSH - IF first_time == 1 then write to stack
- * ELSE write to stack.bak
- * append stack onto stack.bak
- * copy stack.bak to stack
- * this has the effect of writing
- * to the beginning of the stack.
- ************************************************/
-
- if(*first_call == 1){
-
- *first_call = *first_call + 1;
- if((stack_file_pointer = fopen(STACK_FILE,"wb"))
- == NULL)
- printf("\nSFO> Could not open stack file");
-
- else{
- /*printf("\n\nSFO> Writing to stack file");*/
- fwrite(holder, sizeof(holder), 1, stack_file_pointer);
- fclose(stack_file_pointer);
- } /* ends else could not open stack_file */
-
- } /* ends if *first_call == 1 */
- else{ /* else stack file has been used already */
- strcpy(backup_file_name, STACK_FILE);
- append_string(".bak\0", backup_file_name);
- if((backup_file_pointer =
- fopen(backup_file_name, "wb")) == NULL)
- printf("\nSFO> Could not open backup file");
- else{
- /*printf("\n\nSFO> Writing to backup file");*/
- fwrite(holder, sizeof(holder), 1, backup_file_pointer);
- fclose(backup_file_pointer);
- } /* ends else could not open backup_file */
-
- append_stack_files(backup_file_name, STACK_FILE, holder);
- copy_stack_files(backup_file_name, STACK_FILE, holder);
-
- } /* ends else first_call != 1 */
-
- printf("--- End of push_data_onto_stack");
-
- } /* ends push_data_onto_stack_file */
-
- /***************************************
- * pop_data_off_of_stack_file(...
- * This function pops the stack array
- * off of the stack file.
- ****************************************/
-
- pop_data_off_of_stack_file(stack, pointer, stack_file_in_use)
- int *pointer, *stack_file_in_use;
- short stack[STACK_SIZE][2];
- {
- char backup_file_name[MAX_NAME_LENGTH];
- FILE *backup_file_pointer, *stack_file_pointer;
- int i;
- long write_counter;
- short holder[STACK_FILE_LENGTH][2],
- holder2[STACK_FILE_LENGTH][2];
-
- /********************************************
- * POP - Read 1 time from stack
- * Copy the remainder of stack to
- * stack.bak
- * Copy stack.bak to stack
- * This has the effect of popping off
- * of the stack.
- * Read the holder array from the stack file.
- * Open the stack file for reading in binary
- * mode.
- * If it requires more than one write to
- * copy the remainder of stack to
- * stack.bak then there is still data in the
- * stack file so set stack_file_in_use = 1.
-
- * Else set it to 0.
- ***********************************************/
-
- printf("\nSFO> Start of pop_data_off_of_stack ");
- write_counter = 0;
-
- strcpy(backup_file_name, STACK_FILE);
- append_string(".bak\0", backup_file_name);
-
- if( (stack_file_pointer = fopen(STACK_FILE, "rb")) == NULL)
- printf("\nSFO> Could not open stack file");
- else{
- /*printf("\n\nSFO> Reading from stack file");*/
- fread(holder, sizeof(holder),
- 1, stack_file_pointer);
- backup_file pointer =
- fopen(backup_file_name, "wb");
- while( fread(holder2, sizeof(holder2),
- 1, stack_file pointer) ){
- fwrite(holder2, sizeof(holder2),
- 1, backup_file_pointer);
- ++write_counter;
- } /* ends while reading */
- if(write_counter > 0)
- *stack_file_in_use = 1;
- else
- *stack_file_in_use = 0;
-
- fclose(backup_file_pointer);
- fclose(stack_file_pointer);
- } /* ends else could not open stack file */
-
- copy_stack_files(backup_file_name,
- STACK_FILE, holder2);
-
- for(i=0; i<STACK_FILE_LENGTH; i++){
- stack[i][0] = holder[i][0];
- stack[i][1] = holder[i][1];
- }
- *pointer = *pointer + STACK_FILE_LENGTH - 1;
-
- printf("--- End of pop_data_off_of_stack");
- } /* ends pop_data_off_of_stack_file */
-
- /*********************************************
- * append_stack_files(...
- * Append the second file onto the end
- * of the first.
- ***********************************************/
-
- append_stack_files(first_file, second_file, holder)
- char first_file[], second file[];
- short holder[STACK_FILE_LENGTH][2];
- {
- FILE *first, *second;
- int i;
-
- if((first = fopen(first_file, "r+b")) == NULL)
- printf("\n\nSFO> Cannot open file %s",
-
- first_file);
-
- if((second = fopen(second_file, "rb")) == NULL)
- printf("\n\nSFO> Cannot open file %s", second_file);
-
- /***************************************
- * Seek to the end of the first file and
- * to the beginning of the second file.
- *****************************************/
-
- fseek(first, OL, 2);
- fseek(second, OL, 0);
-
- while(fread(holder, sizeof(holder), 1, second) ){
- fwrite(holder, sizeof(holder), 1, first);
- } /* ends while reading */
-
- fclose(first);
- fclose(second);
-
- } /* ends append_stack_files */
-
- /******************************************
- * copy_stack_files(...
- * Copy the first file to the second.
- ********************************************/
-
- copy_stack_files(first_file, second_file, holder)
- char first_file[], second_file[];
- short holder[STACK_FILE_LENGTH][2];
- {
- FILE *first, *second;
- int i;
-
- if( (first = fopen[first_file, "rb")) == NULL)
- printf("\n\nSFO> Cannot open file %s", first_file);
-
- if( (second = fopen(second_file, "wb")) == NULL)
- printf("\n\nSFO> Cannot open file %s", second_file);
-
- /****************************************
- * Seek to the beginning of the first file.
- *****************************************/
-
- fseek(first, 0L, 0);
-
- while( fread(holder, sizeof(holder), 1, first) ){
- fwrite(holder, sizeof(holder), 1, second);
- } /* ends while reading */
-
- fclose(first);
- fclose(second);
-
- } /* ends copy_stack_files */
-
- /* End of File */
-
-
- Listing 4 Code implementing manual segmentation
-
- /**************************************************
- * manual_threshold_segmentation(...
- * This function segments an image using thresholding
- * given the hi and low values of the threshold
- * by the calling routine. It reads in an image
- * and writes the result to the output image.
- * If the segment parameter is 0, you only
- * threshold the array - you do not segment.
- ***************************************************/
-
- manual_threshold_segmentation(in_name, out_name,
- the_image, out_image,
- il, ie, ll, le,
- hi, low, value, segment)
- char in_name[], out_name[];
- int il, ie, ll, le, segment;
- short hi, low, the_image[ROWS][COLS],
- out_image[ROWS][COLS], value;
- {
- int length, width;
- struct tiff_header_struct image_header;
-
- if(does_not_exist(out_name)){
- printf("\n\nMTS> output file does not exist %s", out_name);
- read_tiff_header(in_name, &image_header);
- round_off_image_size(&image_header, &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate_tiff_file(out_name, &image_header, out_image);
- } /* ends if does_not_exist */
-
- read_tiff_image(in_name, the_image, il, ie, ll, le);
- threshold_image_array(the_image, out_image, hi, low, value);
- if(segment == 1)
- grow(out_image, value);
- write_array_into_tiff_image(out_name, out_image, il, ie, ll, le);
- } /* ends manual_threshold_segmentation */
-
- /* End of File */
-
-
- Listing 5 Code implementing the histogram peaks technique
- /*********************************************
- * peak_threshold_segmentation(...
- * This function segments an image using
- * thresholding. It uses the histogram peaks
- * to find the hi and low values of the
- * threshold.
- * If the segment parameter is 0, you only
- * threshold the array - you do not segment.
- ***********************************************/
-
- peak_threshold_segmentation(in_name, out_name
- the_image, out_image,
- il, ie, ll, le,
- value, segment)
- char in name[], out_name[];
- int il, ie, ll, le, segment;
- short the_image[ROWS][COLS],
-
- out_image[ROWS] [COLS], value;
- {
- int length, peak1, peak2, width;
- short hi, low;
- struct tiff_header_struct image_header;
- unsigned long histogram[GRAY_LEVELS+1];
-
- if(does_not_exist(out_name)){
- printf("\n\nPTS> output file does not exist %s", out_name);
- read_tiff_header(in_name, &image_header);
- round_off_image_size(&image_header, &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate_tiff_file(out_name, &image_header, out_image);
- } /* ends if does not exist */
-
- read_tiff_image(in_name, the_image, il, ie, ll, le);
- zero_histogram(histogram);
- calculate_histogram(the_image, histogram);
- smooth_histogram(histogram);
- find_peaks(histogram, &peak1, &peak2);
- peaks_high_low(histogram, peak1, peak2, &hi, &low);
- threshold_image_array(the_image, out_image, hi, low, value);
- if(segment == 1)
- grow(out_image, value);
- write_array_into_tiff_image(out_name, out_image, il, ie, ll, le);
-
- } /* ends peak_threshold_segmentation */
-
- /*******************************************
- * find_peaks(...
- * This function looks through the histogram
- * array and finds the two highest peaks.
- * The peaks must be separated, cannot be
- * next to each other, by a spacing defined
- * in cips.h.
- * The peaks array holds the peak value
- * in the first place and its location in
- * the second place.
- *******************************************/
-
- find_peaks(histogram, peak1, peak2)
- unsigned long histogram[];
- int *peak1, *peak2;
- {
- int distance[PEAKS], peaks[PEAKS][2];
- int i, j=0, max:0, max_place:0;
-
- for(i=0; i<PEAKS; i++){
- distance[i] = 0;
- peaks[i][0] = -1;
- peaks[i] [1] = -1;
- }
-
- for(i=0; i<=GRAY_LEVELS; i++){
- max = histogram[i];
- max_place = i;
- insert_into_peaks(peaks, max, max_place);
- } /* ends loop over i */
-
-
- for(i=1; i<PEAKS; i++){
- distance[i] = peaks[0][1] - peaks[i][1];
- if(distance[i] < 0)
- distance[i] = distance[i]*(-1);
- }
-
- *peak1 = peaks[0] [1];
- for(i=PEAKS-1; i>0; 1--)
- if(distance[i] > PEAK_SPACE) *peak2 = peaks[i][1];
-
- } /* ends find_peaks */
-
- /*******************************************
- * insert_into_peaks(...
- * This function takes a value and its
- * place in the histogram and inserts them
- * into a peaks array. This helps us rank
- * the the peaks in the histogram.
- * The objective is to build a list of
- * histogram peaks and thier locations.
- * The peaks array holds the peak value
- * in the first place and its location in
- * the second place.
- *******************************************/
-
- insert_into_peaks(peaks, max, max_place)
- int max, max_place, peaks[PEAKS][2];
- }
- int i, j;
-
- /* first case */
- if(max > peaks[0][0]){
- for(i=PEAKS-1; i>0; i--){
- peaks[i][0] = peaks[i-1][0];
- peaks[il[1] = peaks[i-1[1];
- }
- peaks[0] [0] = max;
- peaks[0][1] = max_place;
- } /* ends if */
-
- /* middle cases */
- for(j=0; j<PEAKS-3; j++){
- if(max < peaks[j][0] && max > peaks[j+1][0]){
- for(i:PEAKS-1; i>j+1; i--){
- peaks[i][0] = peaks[i-1][0];
- peaks[i][1] = peaks[i-1][1];
- }
- peaks[j+1][0] = max;
- peaks[j+1][1] = max_place;
- } /* ends if */
- } /* ends loop over j */
- /* last case */
- if(max < peaks[PEAKS-2][0] && max > peaks[PEAKS-1] [0]){
- peaks[PEAKS-1][0] = max;
- peaks[PEAKS-1][0] = max_place;
- } /* ends if */
-
- } /* ends insert into_peaks */
-
-
- /***********************************************
- * peaks_high_low(...
- * This function uses the histogram array
- * and the peaks to find the best high and
- * low threshold values for the threshold
- * function. You want the hi and low values
- * so that you will threshold the image around
- * the smaller of the two "humps" in the
- * histogram. This is because the smaller
- * hump represents the objects while the
- * larger hump represents the background.
- ***********************************************/
-
- peaks_high_low(histogram, peak1, peak2, hi, low)
- int peak1, peak2;
- short *hi, *low;
- unsigned long histogram[];
- {
- int i, mid_point;
- unsigned long sum1 = 0, sum2 = 0;
-
- if(peak1 > peak2)
- mid_point = ((peak1 - peak2)/2) + peak2;
- if(peak1 < peak2)
- mid_point = ((peak2 - peak1)/2) + peak1;
-
- for(i=0; i<mid_point; i++)
- sum1 = sum1 + histogram[i];
-
- for(i=mid_point; i<=GRAY_LEVELS; i++)
- sum2 = sum2 + histogram[i];
- if(sum1 >= sum2){
- *low = midpoint;
- *hi = GRAY_LEVELS;
- }
- else{
- *low = 0;
- *hi = mid_point;
- }
-
- } /* ends peaks_high_low */
- /* End of File */
-
-
- Listing 6 Code implementing the histogram valley technique
- /*********************************************
- * valley_threshold_segmentation(...
- * This function segments an image using
- * thresholdlng. It uses the histogram valleys
- * to find the hi and low values of the
- * threshold.
- * If the segment parameter is 0, you only
- * threshold the array - you do not segment.
- ***********************************************/
-
- valley_threshold_segmentation(in_name, out_name,
- the_image, out_image,
- il, ie, ll, le,
-
- value, segment)
- char in_name[], out_name[];
- int il, ie, ll, ie, segment;
- short the_image[ROWS] [COLS],
- out_image[ROWS][COLS], value;
- {
- int length, peak1, peak2, width;
- short hi, low;
- struct tiff_header_struct image_header;
- unsigned long-histogram[GRAY_LEVELS+1];
-
- if(does_not_exist(out_name)){
- printf{"\n\nVTS> output file does not exist %s", out_name);
- read_tiff_header(in_name, &image_header);
- round_off_image_size(&image_header, &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate tiff_file(out_name, &image_header, out_image);
- } /* ends if does_not_exist */
-
- read_tiff_image(in_name, the_image, il, ie, ll, ie);
- zero_histogram(histogram);
- calculate_histogram(the_image, histogram);
- smooth_histogram(histogram);
- find_peaks(histogram, &peak1, &peak2);
- valley_high_low(histogram, peak1, peak2, &hi, &low);
- threshold_image_array(the_image, out_image, hi, low, value);
- if(segment == 1)
- grow(out_image, value);
- write_array_into_tiff_image(out_name, out_image, il, ie, ll, le);
-
- } /* ends valley_threshold_segmentation */
-
- /******************************************
- * valley_high_low(...
- * This function uses the histogram array
- * and the valleys to find the best high and
- * low threshold values for the threshold
- * function. You want the hi and low values
- * so that you will threshold the image around
- * the smaller of the two "humps" in the
- * histogram. This is because the smaller
- * hump represents the objects while the
- * larger hump represents the background.
- *******************************************/
-
- valley_high_low(histogram, peak1, peak2, hi, low)
- int peak1, peak2;
- short *hi, *low;
- unsigned long histogram[];
- {
- int i, valley_point;
- unsigned long sum1 = 0, sum2 = 0;
-
- find_valley_point(histogram, peak1, peak2, &valley_point);
- /*printf("\nVHL> valley point is %d", valley_point);*/
-
- for(i=0; i<valley_point; i++)
- sum1 = sum1 + histogram[i];
-
- for(i=valley_point; i<=GRAY LEVELS; i++)
- sum2 = sum2 + histogram"[i];
-
- if(sum1 >= sum2){
- *low = valley point;
- *hi = GRAY_LEVELS;
- }
- else{
- *low = 0;
- *hi = valley_point;
- }
-
- } /* ends valley_high low */
-
- /******************************************
- * find_valley_point(...
- * This function finds the low point of
- * the valley between two peaks in a
- * histogram. It starts at the lowest
- * peak and works its way up to the
- * highest peak. Along the way, it looks
- * at each point in the histogram and inserts
- * them into a list of points. When done,
- * it has the location of the smallest histogram
- * point - that is the valley point.
- * The deltas array holds the delta value
- * in the first place and its location in
- * the second place.
- *******************************************/
-
- find_valley_point(histogram, peak1, peak2, valley_point)
- int peak1, peak2, *valley_point;
- unsigned long histogram[];
- {
- int deltas[PEAKS][2], delta_hist, i;
- for(i=0; i<PEAKS; i++){
- deltas[i][0] = 10000;
- deltas[i][1] = -1;
-
- if(peak1 < peak2){
- for(i=peak1+1; i<peak2; i++){
- delta_hist = (int)(histogram[i]);
- insert_into_deltas(deltas, delta_hist, i);
- } /* ends loop over i */
- } /* ends if peak1 < peak2 */
-
- if(peak2 < peak1){
- for(i=peak2+1; i<peak1; i++){
- delta_hist = (int)(histogram[i]);
- insert_into_deltas(deltas, delta_hist, i);
- } /* ends loop over i */
- } /* ends if peak2 < peak1 */
-
- *valley_point = deltas[0][1];
-
- } /* ends find valley_point */
-
- /****************************************
- * insert_into_deltas(...
-
- * This function inserts histogram deltas
- * into a deltas array. The smallest delta
- * will be at the top of the array.
- * The objective is to build a list of
- * histogram area deltas and thier locations.
- * The deltas array holds the delta value
- * in the first place and its location in
- * the second place.
- *******************************************/
- insert_into_deltas(deltas, value, place)
- int value, place, deltas[PEAKS] [2];
- {
- int i, j;
- /* first case */
- if(value < deltas[0] [0]){
- for(i=PEAKS-1; i>0; i--){
- deltas[i][0] = deltas[i-1][[0];
- deltas[i] [1] = deltas[i-1] [1];
- }
- deltas[0] [0] = value;
- deltas[0][1] = place;
- } /* ends if */
-
- /* middle cases */
- for(j=0; j<PEAKS-3; j++){
- if(value > deltas[j][0] &&
- value < deltas [j+1] [0] ) {
- for(i=PEAKS-1; i>j+1; i--){
- deltas[i][0] = deltas[i-1][0];
- deltas[i][1] = deltas[i-1][1];
- }
- deltas[j+1][0] = value;
- deltas[j+1][1] = place;
- } /* ends if */
- } /* ends loop over j */
-
- /* last case */
- if(value > deltas[PEAKS-2][0] &&
- value < deltas[PEAKS-i][o]){
- deltas[PEAKS-1][0] = value;
- deltas[PEAKS-1][1] = place;
- } /* ends if */
-
- } /* ends insert_into_deltas */
- /* End of File */
-
-
- Listing 7 Code implementing the adaptive technique
- /************************************************
- *
- * adaptive_threshold_segmentation(...
- *
- * This function segments an image using
- * thresholding. It uses two passes
- * to find the hi and low values of the
- * threshold. The first pass uses the peaks
- * of the histogram to find the hi and low
- * threshold values. It thresholds the image
- * using these hi lows and calculates the means
-
- * of the object and background. Then we use
- * these means as new peaks to calculate new
- * hi and low values. Finally, we threshold
- * the image again using these second hi low
- * hi low values.
- *
- * If the segment parameter is 0, you only
- * threshold the array - you do not segment.
- *
- **************************************************/
-
- adaptive_threshold_segmentation(in_name, out_name,
- the_image, out_image,
- il, ie, ll, le,
- value, segment)
- char in_name[], out_name[];
- int il, ie, ll, le, segment;
- short the_image[ROWS] [COLS], out_image[ROWS] [COLS], value;
- {
- int length, peak1, peak2, width;
- short background, hi, low, object;
- struct tiff_header_struct image_header;
- unsigned long-histogram[GRAY_LEVELS+1];
-
- if(does_not exist(out_name)){
- printf("\n\nATS> output file does not exist %s", out_name);
- read_tiff_header(in_name, &image_header);
- round_off_image_size(&image_header, &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate_tiff_file(out_name, &image_header, out_image);
- } /* ends if does not exist */
-
- read_tiff_image(in_name, the_image, il, ie, ll, le);
- zero_histogram(histogram);
- calculate_histogram(the_image, histogram);
- smooth_histogram(histogram);
- find_peaks(histogram, &peak1, &peak2);
- peaks_high_low(histogram, peak1, peak2, &hi, &low);
- threshold_and_find_means(the_image, out_image,
- hi, low, value,
- &object, &background);
- peaks_high_low(histogram, object, background,
- &hi, &low);
- threshold_image_array(the_image, out_image, hi, low, value);
- if(segment == 1)
- grow(out_image, value);
- write_array_into_tiff_image(out_name, out_image,
- il, ie, ll, le);
-
- } /* ends adaptive_threshold_segmentation */
-
- /************************************************
- *
- * threshold_and_find_means(...
- *
- * This function thresholds an input image array
- * and produces a binary output image array.
- * If the pixel in the input array is between
-
- * the hi and low values, then it is set to value.
- * Otherwise, it is set to 0.
- *
- ************************************************/
-
- threshold_and_find_means(in_image, out_image, hi,
- low, value, object_mean,
- background_mean)
- short *background_mean, hi, low,
- in_image[ROWS][COLS], *object_mean,
- out_image[ROWS][COLS], value;
- {
- int counter = 0,
- i,
- j;
- unsigned long object = 0,
- background = 0;
-
- for(i=0; i<ROWS; i++){
- for(j=0; j<COLS; j++){
- if(in_image[i][j] >= low && in_image[i][j] <= hi){
- out_image[i][j] = value;
- counter++;
- object = object + in_image[i][j];
- }
- else{
- out_image[i][j] : 0;
- background = background + in_image[i][j];
- }
- } /* ends loop over j */
- } /* ends loop over i */
- object = object/counter;
- background = background/((ROWS*COLS)-counter);
- *object_mean = (short)(object);
- *background mean = (short)(background);
- printf("\n\tTAFM> set %d points", counter);
- printf("\n\tTAFM> object=%d background=%d",
- *object_mean, *background_mean);
- } /* ends threshold_and_find_means */
-
- /* End of File */
-
-
- Listing 8 main routine for the C Image Processing System revised to include
- histogram image segmentation
- case 16: /* image thresholding */
- printf("\nCIPS> Enter input image name\n");
- get_image_name(name);
- printf("\nCIPS> Enter output image name\n");
- get_image_name(name2);
- get_parameters(&il, &ie, &ll, &le);
- get_threshold_options(ts_method, &hi, &low, &value);
- if (ts_method[0] == 'm' ts_method[0] == 'M')
- manual_threshold_segmentation(name,
- name2, the_image, out_image,
- il, ie, ll, le,
- hi, low, value, 0);
- if(ts_method[0] == 'p' ts_method[0] == 'P')
- peak_threshold_segmentation(name,
- name2, the_image, out_image,
-
- il, ie, ll, le, value, 0);
- if(ts_method[0] == 'v' ts_method[0] == 'V')
- valley_threshold_segmentation(name,
- name2, the_image, out_image,
- il, ie, ll, le, value, 0);
- if(ts_method[0] == 'a' ts_method[0] == 'a')
- adaptive_threshold_segmentation(name,
- name2, the_image, out_image,
- il, ie, ll, le, value, 0);
- break;
-
- case 17: /* image segmentation */
- printf("\nCIPS> Enter input image name\n");
- get_image_name(name);
- printf("\nCIPS> Enter output image name\n");
- get_image_name(name2);
- get_parameters(&il, &ie, &ll, &le);
- get_segmentation_options(ts_method, &hi,
- &low, &value);
- if(ts_method[0] == 'm' (ts_method[0] == 'M')
- manual_threshold_segmentation(name,
- name2, the_image, out_image,
- il, ie, ll, le,
- hi, low, value, 1);
- if(ts_method[0] == 'p' ts_method[0] == 'P')
- peak_threshold_segmentation(name,
- name2, the_image, out_image,
- il, ie, ll, le, value, 1);
- if(ts_method[0] == 'v' ts_method[0] == 'V')
- valley_threshold_segmentation(name,
- name2, the_image, out_image,
- il, ie, ll, le, value, 1);
- if(ts_method[0] == 'a' ts_method[0] == 'a')
- adaptive_threshold_segmentation(name,
- name2, the_image. out_image,
- il, ie, ll, le, value, 1);
- break;
-
- .
- .
- .
-
- /****************************************************
- * show_menu(..
- * This function displays the CIPS main menu.
- *****************************************************/
- show_menu()
- {
-
- printf("\n\nWelcome to CIPS");
- printf("\nThe C Image Processing System");
- printf("\nThese are you choices:");
- printf("\n\t1. Display image header");
- printf("\n\t2. Show image numbers");
- printf("\n\t3. Print image numbers");
- printf("\n\t4. Display image (VGA & EGA only)");
- printf("\n\t5. Display or print image using halftoning");
- printf("\n\t6. Print graphics image using dithering");
- printf("\n\t7. Print or display histogram numbers");
-
- printf("\n\t8. Perform edge detection");
- printf("\n\t9. Perform edge enhancement");
- printf("\n\t10. Perform image filtering");
- printf("\n\t11. Perform image addition and subtraction");
- printf("\n\t12. Perform image cutting and pasting");
- printf("\n\t13. Perform image rotation and flipping");
- printf("\n\t14. Perform image scaling");
- printf("\n\t15. Create a blank image");
- printf("\n\t16. Perform image thresholding");
- printf("\n\t17. Perform image segmentation");
- printf("\n\t20. Exit system");
- printf("\n\nEnter choice _\b");
-
- } /* ends show_menu */
- .
- .
- .
-
- /******************************************
- * get_segmentation_options(...
- * This function interacts with the user
- * to obtain the options for image
- * segmentation.
- *******************************************/
-
- get_segmentation_options(method, hi, low, value)
- char method[];
- short *hi, *low, *value;
- {
- int i, not_finished = 1, response;
-
- while(not_finished){
- printf("\n\nThe image segmentation options are:\n");
- printf("\n\t1. Method is %s", method);
- printf("\n\t (options are manual peaks");
- printf( " valleys adapative)");
- printf("\n\t2. Value is %d", *value);
- printf("\n\t3. Hi is %d", *hi);
- printf("\n\t4. Low is %d", *low);
- printf("\n\t Hi and Low needed only for");
- printf( "manual method");
- printf("\n\nEnter choice (0 = no change):_\b");
-
- get_integer(&response);
-
- if(response == 0)
- not_finished == 0;
-
- if(response == 1){
- printf("\nEnter method (options are:");
- printf(" manual peaks valleys adaptive)\n\t");
- read_string(method);
- }
-
- if(response == 2){
- printf("\nEnter value: __\b\b\b");
- get_short(value);
- }
-
-
- if(response == 3){
- printf("\nEnter hi: __\b\b\b");
- get_short(hi);
- }
- if(response == 4){
- printf("\nEnter low: __\b\b\b");
- get_short(low);
- }
-
- } /* ends while not_finished */
- } /* ends get_segmentation_options */
-
- /*********************************************
- * get_threshold_options{...
- * This function interacts with the user
- * to obtain the options for image
- * threshold.
- **********************************************/
-
- get_threshold_options(method, hi, low, value)
- char method[];
- short *hi, *low, *value;
- {
- int i, not_finished = 1, response;
- while(not_finished){
- printf("\n\nThe image threshold options are:\n");
- printf("\n\tl. Method is %s", method);
- printf("\n\t (options are manual peaks");
- printf( "valleys adapative)");
- printf("\n\t2. Value is %d", *value);
- printf("\n\t3. Hi is %d", *hi);
- printf("\n\t4. Low is %d", *low);
- printf("\n\t Hi and Low needed only for");
- printf( " manual method");
- printf("\n\nEnter choice (0 = no change):_\b");
-
- get_integer(&response);
-
- if(response == 0)
- not_finished = 0;
-
- if(response == 1){
- printf("\nEnter method (options are:");
- printf(" manual peaks valleys adaptive)\n\t");
- read_string(method);
- }
-
- if(response == 2){
- printf("\nEnter value: __\b\b\b");
- get_short(value);
- }
-
- if(response == 3){
- printf("\nEnter hi: __\b\b\b");
- get_short(hi);
- }
- if(response == 4){
- printf("\nEnter low: __\b\b\b");
- get_short(low);
-
- }
-
- } /* ends while not_finished */
- } /* ends get_threshold_options */
- /* End of File */
-
-
- Listing 9 Application for thresholding and segmenting an image file
- /***************************************
- *
- * file d:\cips\mainseg.c
- *
- * Functions: This file contains
- * main
- *
- * Purpose:
- * This file contains the main calling
- * routine in an edge detection program.
- *
- * External Calls:
- * gin.c - get_image_name
- * numcvrt.c - get_integer
- * int_convert
- * tiff.c - read_tiff_header
- * enhance_edges
- * hist.c - zero_histogram
- * smooth_histogram
- * show_histogram
- * calculate_histogram
- * segment.c - threshold_image_array
- * grow
- * find_peaks
- * peaks_high_low
- * valley_high_low
- * threshold_and_find_means
- *
- * Modifications:
- * 27 September 1992 - created
- *
- ****************************************/
-
- #include "cips.h"
-
- short the_image[ROWS][COLS];
- short out_image[ROWS][COLS];
- unsigned long histogram[GRAY_LEVELS+1];
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- char name[80], name2[80], response[80];
- int count, i, ie, i1, j, k, le, length, ll,
- peakl, peak2, size,
- t, type, v, width;
- short background, hi, low, object, value;
- struct tiff_header_struct image_header;
-
- _setvideomode(_TEXTC80); /* MSC 6.0 statements */
-
- _setbkcolor(1);
- _settextcolor(7);
- _clearscreen(_GCLEARSCREEN);
-
- if(argc < 7){
- printf("\n\nmainseg in-file out-file hi low value operation");
- printf("\n\t\toperation = threshold grow peaks valleys adaptive");
- printf("\n");
- exit(0);
- }
-
- strcpy(name, argv[1]);
- strcpy(name2, argv[2]);
- hi = atoi(argv[3]);
- low = atoi(argv[4]);
- value = atoi(argv[5]);
-
- il = 1;
- ie = 1;
- ll = ROWS+1;
- le = COLS+1;
-
- read_tiff_header(name, &image_header);
-
- length = (90 + image_header.image_length)/ROWS;
- width = (90 +image_header.image_width)/COLS;
- count = 1;
- printf("\nlength=%d width=%d", length, width);
-
- if(does_not_exist(name2)){
- read_tiff_header(name, &image_header);
- round_off_image_size(&image_header, &length, &width);
- image_header.image_length = length*ROWS;
- image_header;image_width = width*COLS;
- create_allocate_tiff_file(name2, &image_header,
- out_image);
- } /* ends if does_not_exist */
- zero_histogram(histogram);
-
- /**********************************
- *
- * Manual Threshold operation
- *
- ***********************************/
-
- if(argv[6][0] == 't'){
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
- printf("\nrunning %d of %d", count, length*width);
- count++;
- read_tiff_image(name, the_image,
- il+i*ROWS, ie+j*COLS,
- ll+i*ROWS, le+j*COLS);
- printf("\nMS> Calling threshold");
- threshold_image_array(the_image, out_image,
- hi, low, value);
- write_array_into_tiff_image(name2, out_image,
- il +i*ROWS,
- ie+j*COLS,
-
- 11+i*ROWS,
- le+j*COLS);
- } /* ends loop over i */
- } /* ends loop over j */
- } /* ends if t */
-
- /**********************************
- *
- * Grow region operation
- *
- **********************************/
-
- if(argv[6][0] == 'g'){
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
- printf("\nrunning %d of %d", count, length*width);
- count++;
- read_tiff_image(name, the_image,
- il+i*ROWS, ie+j*COLS,
- 11+i*ROWS, le+j*COLS);
- printf("\nMS> Calling grow");
- grow(the_image, value);
- write_array_into_tiff_image(name2, the_image,
- il+i*ROWS,
- ie+j*COLS,
- 11+i*ROWS,
- le+j*COLS);
- } /* ends loop over i */
- } /* ends loop over j */
- } /* ends if g */
-
- /**********************************
- *
- * Peak threshold operation
- *
- ***********************************/
-
- if(argv[6][0] == 'P'){
-
- /* calculate histogram for the
- entire image file */
-
- zero_histogram(histogram);
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
- printf("\nrunning %d of %d", count, length*width);
- count++;
- read_tiff_image(name, the_image,
- il+i*ROWS, ie+j*COLS,
- ll+i*ROWS, le+j*COLS);
- printf("\nMS> Calling hist functions");
- calculate_histogram(the_image, histogram);
- } /* ends loop over i */
- } /* ends loop over j */
-
- smooth_histogram(histogram);
- show_histogram(histogram);
- find_peaks(histogram, &peak1, &peak2);
- printf("\npeakl=%d peak2=%d", peak1, peak2);
-
- peaks_high_low(histogram, peak1, peak2,
- &hi, &low);
- printf("\nhi=%d low=%d", hi, low);
-
- /* now read the image file again
- and threshold and grow objects. */
- count = 1;
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
- printf("\nrunning %d of %d", count, length*width);
- count++;
- read_tiff_image(name, the_image,
- il+i*ROWS, ie+j*COLS,
- ll+i*ROWS, le+j*COLS);
- threshold_image_array(the_image, out_image,
- hi, low, value);
- write_array_into_tiff_image(name2, out_image,
- il+i*ROWS,
- ie+j*COLS,
- ll+i*ROWS,
- le+j*COLS);
- } /* ends loop over i */
- } /* ends loop over j */
- } /* ends if p */
-
- /********************************
- *
- * Valley threshold operation
- *
- ********************************/
-
- if(argv[6][0] = = 'v'){
-
- /* calculate histogram for the
- entire image file */
-
- zero_histogram(histogram);
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
- printf("\nrunning %d of %d", count, length*width);
- count++;
- read_tiff_image(name, the_image,
- il+i*ROWS, ie+j*COLS,
- ll+i*ROWS, le+j*COLS);
- printf("\nMS> Calling hist functions");
- calculate_histogram(the_image, histogram);
- } /* ends loop over i */
- } /* ends loop over j */
-
- smooth_histogram(histogram);
- show_histogram(histogram);
- find_peaks(histogram, &peak1, &peak2);
- printf("\npeakl=%d peak2=%d", peak1, peak2);
- valley_high_low(histogram, peak1, peak2,
- &hi, &low);
- printf("\nhi=%d low=%d", hi, low);
-
- /* now read the image file again
- and threshold and grow objects. */
-
- count = 1;
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
- printf("\nrunning %d of %d", count, length*width);
- count++;
- read_tiff_image(name, the_image,
- il+i*ROWS, ie+j*COLS,
- ll+i*ROWS, le+j*COLS);
- threshold_image_array(the_image, out_image,
- hi, low, value);
- write_array_into_tiff_image(name2, out_image,
- il+i*ROWS,
- ie+j*COLS,
- ll+i*ROWS,
- le+j*COLS);
- } /* ends loop over i */
- } /* ends loop over j */
- } /* ends if v */
-
- /********************************
- *
- * Adaptive threshold operation
- *
- ********************************/
-
- if(argv[6][0] == 'a'){
-
- /* calculate histogram for the
- entire image file */
-
- zero_histogram(histogram);
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
- printf("\nrunning %d of %d", count, length*width);
- count++;
- read_tiff_image(name, the_image,
- il+i*ROWS, ie+j*COLS,
- ll+i*ROWS, le+j*COLS);
- printf("\nMS> Calling hist functions");
- calculate_histogram(the_image, histogram);
- } /* ends loop over i */
- } /* ends loop over j */
-
- /* find the peaks for the entire
- image file. */
-
- smooth_histogram(histogram);
- show_histogram(histogram);
- find_peaks(histogram, &peakl, &peak2);
- printf("\npeakl=%d peak2=%d", peak1, peak2);
- peaks_high_low(histogram, peak1, peak2,
- &hi, &low);
- printf("\nhi=%d low=%d", hi, low);
-
- /* Second Pass */
-
- count = 1;
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
-
- printf("\nrunning %d of %d", count, length*width);
- count++;
- read_tiff_image(name, the_image,
- il+i*ROWS, ie+j*COLS,
- ll+i*ROWS, le+j*COLS);
- threshold_and find_means(the_image,
- out_image, hi, low,
- value, &object,
- &background);
- peaks_high_low(histogram, object, background,
- &hi, &low);
- printf("\nafter means calculated - hi=%d low=%d", hi,
- low);
- threshold_image_array(the_image, out_image,
- hi, low, value);
- write_array_into_tiff_image(name2, out_image,
- il+i*ROWS,
- ie+j*COLS,
- ll+i*ROWS,
- le+j*COLS);
- } /* ends loop over i */
- } /* ends loop over j */
- } /* ends if a */
-
- } /* ends main */
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pointer Power in C and C++, Part 1
-
-
- Christopher Skelly
-
-
- Christopher Skelly has been a teacher of C and C++ for the past ten years,
- first for Plum Hall Inc., and then for his own company, Insight Resource Inc.
- Insight Resource also developed the bestselling help utility, KO-PILOT for
- WordPerfect, which Brit Hume called "the best add-in ever written." Chris has
- served on both the C and C++ ANSI committees, and was the Technical Chairman
- for this year's "C Plus C++" and "C++ in Action" conferences, presented by
- Boston University. He writes regularly for the C User's Journal and the C++
- Journal, and can be reached at Insight Resource Inc., 914-631-5032, or at
- 71005.771@compuserve.com.
-
-
- Pointers have always been the trickiest part of C to fully master. The syntax
- of declarations and expressions using pointers is decidedly different. Arrays
- have an often fuzzily understood relationship to pointers in C. Perhaps a
- minority of C programmers really understand that [] has no intrinsic
- connection with "arrayness," just as * has no fixed connection with
- "pointerness." Pointer arithmetic is tricky, especially with
- multiply-dimensioned arrays, or higher-level pointers like char **p. Pointers
- to functions provide a sublime power in C programs, but one easily misused, as
- any number of physically-damaged devices might testify to. Early BASIC and
- Pascal made it hard to shoot yourself in the foot. Early C made it easy, and
- later C and C++ can only help you if you let them. The situation I encountered
- teaching C in the early 1980s was well represented by one student who
- observed, "I was doing fine with C, until we got to pointers."
- This two-part article sets out to help you master C's pointer power. Pointers
- turn out to be a marvelous aspect of the C family of languages. They let you
- solve many complex problems efficiently and elegantly, often using runtime
- analysis and decision-making stratagems. Despite their complex aspects,
- pointers in C turn out to be governed by relatively simple underlying ideas,
- which can be used to resolve even the most complex pointer problems. This
- article is based on the somewhat unconventional idea that pointers in C and
- C++, for all their power, are really quite simple, once one understands a
- small set of central principles.
- This set of principles is designed to be extremely practical and simple to
- use. Virtually everything in this article applies to both C and C++.
- Understanding this model of pointer behavior is critical. You can memorize
- rules or the meaning of certain particular expressions. You can even learn to
- work with simple pointers by rote. But unless you understand the underlying
- principles, you will get stuck every time by whatever the next level of
- complexity happens to be. C and C++ are wonderful at always having a next
- level of complexity available, should you have need of it!
- The program in Listing 1, inspired by Alan Feuer's great C Puzzle Book,
- illustrates the kind of complex pointer expression you will want to be able to
- handle, if not with great ease, at least with the certainty that you are
- approaching the problem correctly. At the end of this two-part series, I will
- solve this puzzle, using the techniques described here.
- Expressions like ++*--*++pppp[0] +5 arise with great infrequency in the real
- world! Nevertheless, the ability to decipher such an expression is a
- fundamental part of fluency in C. The most important advantage of fluency is
- the freedom to think more about the real problem at hand, and less about the
- programming language.
- The following analysis starts simply enough, but it quickly jumps to more
- complicated levels based on the fundamental premise. I call the fundamental
- premise, the Campleat Pointer, perhaps because the great Angling classic
- really does represent the point I am trying to make, or perhaps, as my wife
- suggests, because I would rather be trout fishing than practically anything
- else.
-
-
- The Compleat Pointer
-
-
- First, you must start off with a fundamental question. What exactly is a
- pointer?
- In English, a "pointer" is an indicator. It directs you toward something. An
- arrow indicating a location, or a hot tip about a certain opportunity, a
- pointer always directs your attention to something besides itself. The same is
- true in C and C++. Calling something a pointer is a poetic way of saying that
- you can use this thing to find something else. The something else is the thing
- "pointed at." Imagine a line pointing from the first thing to the second. In
- reality, of course, there is no line, dotted or otherwise, connecting the two
- objects. The lines drawn in diagrams are quite imaginary.
-
-
- Key Fact #1 -- A pointer is a variable containing an address.
-
-
- The real connection a pointer makes is that the content of the pointer is the
- address of the object pointed at. All the power and subtlety of pointers comes
- out of this fundamental connection. Pointers are variables, which store the
- addresses of other programming objects. Remember that an object in Standard C
- is simply a region of storage. I'll use the term class object to refer to
- instances of classes in C++ or other object-oriented systems.
-
-
- Key Fact #2 -- A pointer always "knows" the type of thing it addresses. It can
- be properly used only to access something of the correct type.
-
-
- Computer memory is organized into one or more ranges of addresses. Almost
- every object in a program has a unique memory address. The address tells where
- in the computer's memory the object is located. Since a pointer holds the
- address of an object, the pointer can be used as a tool for accessing the
- object pointed at. This is the heart of the pointer concept.
- Yet all addresses are not the same, at least not according to a pointer. Each
- pointer has a built-in sense of the type of object stored at the address which
- the pointer contains. This is the second crucial observation.
- Put more formally, a pointer always points to an object of some particular
- type. The type may be one of the built-in types, such as char, short, int,
- long, float, or double, or any one of the possible derived types, including
- arrays, functions, structs, unions, and even other pointers. C++ adds classes
- to Standard C, allowing C++ pointers to point at class objects, or even at
- class-object members, though the latter are implemented rather differently
- than typical C pointers. Even the special case pointer to void points at a
- specific type, and such a pointer has its own set of resources and
- limitations.
- Every pointer value is thus really a package, a collection of two specific
- pieces of information: an address and a type pointed at. One might think of
- this combination of information about where something is located, as well as
- what is located there, as an "access cookie." The word "cookie" means a
- package or collection of ingredients mixed and cooked together properly. Since
- pointers are almost always used to access objects, the term access cookie
- reminds you that it always takes both ingredients, the address and the type,
- to properly access an object.
-
-
- Key Fact #3 -- Pointer values are address/type pairs, just like pointer
- variables. However, pointer values are not storable Ivalues.
-
-
- Next, you should understand the important distinction between pointer
- variables and pointer values. Pointer-like values often exist which are not
- contained in any specific variable. Simple pointer expressions like p + 1
- evaluate to these pointer-like values, without ever being stored in any
- specific variable.
- In other words, if p is the name of a pointer variable, then p + 1 is a
- pointer value, but not a pointer variable. Why? Because the expression p + 1
- gives us an address, and it has an associated type, exactly as if it were a
- pointer variable, yet there is no variable that is actually storing the value
- p + 1. Pointer variables always store pointer values, but pointer values are
- not always stored in pointer variables. Both C and C++ call objects with
- storable addresses, lvalues, though they disagree in some surprising ways as
- to exactly what an lvalue is.
- In most cases, the same rules apply to pointer variables and pointer values.
- Both variables and values can have the indirection operator (*) applied to
- them. This is called dereferencing the pointer. But some operators, like the
- address-of operator (&) can only be applied to pointer variables, not to
- pointer values. Strictly speaking, I ought to reserve the word pointer for
- pointer variables, and always refer to pointer values explicitly as
- address/type values or with some other term. For practical purposes however, I
- will sometimes use the word pointer for both variables and values. Whenever
- the distinction is critical, however, I'll use the specifically correct term.
- To review, the absolutely essential pointer principles are:
- A pointer is a package containing an address and a type to be found at that
- address.
- Simple but specific rules govern the behavior of both parts of the pointer
- package. Some things affect the address part, some things the type part. You
- have to keep track of what both parts mean when you work with pointers.
- Pointer variables are variables which store pointer values. Pointer values can
- also be generated by expressions.
-
-
- Essential Programming Concepts
-
-
- There are two essential programming concepts that will be of real benefit to
- those studying pointers in C. The first I call The Three Attributes, and the
- second The Ladder of Indirection. If you understand the Three Attributes you
- can understand the Ladder of Indirection, and mastering the Ladder is the
- heart of playing Pointer Dominos. Knowing how to play Pointer Dominos is the
- key to mastering pointers in C.
-
-
-
- Key Fact #4 -- Every pointer has three fundamental attributes. These
- attributes are the location, the contents, and the indirect value of the
- pointer.
-
-
- You know that a pointer is an address storer, that is, it contains an address.
- You also know the difference between pointer variables and pointer values. The
- Three Attributes are the attributes of a pointer variable.
- The contents of a pointer is the first of three critical values which can be
- derived from that pointer. These three values are so critical to the proper
- understanding of pointers that I've named them the Three Attributes.
- Technically, each of these attributes is the value of an expression using the
- pointer. I choose to focus on three of these particular expressions, since
- these three yield the most critical information involved with the pointer.
- The expression which will always return the contents of a pointer is simply
- the name of the pointer. If you have a pointer p, that address which is the
- current contents of the pointer is represented by the symbol p in your
- program.
- A pointer variable has a second attribute, a second value intimately
- associated with that pointer. This second attribute is the location of the
- pointer. The location of the pointer is the place in memory where the pointer
- itself is stored. This location, like the contents, is an address. But the
- pointer's location is generally a very different address from the address
- stored in the pointer as the pointer's contents.
- The expression which gives us the location of a pointer is composed of the
- pointer's name, preceded by the & or address-of operator, as in
- &p /* &p is the LOCATION of the pointer */
- The first two attributes, location and contents, are attributes of every
- variable. A simple int variable, x, has a location given by &x and a contents
- or current value given by x. But a pointer has a third attribute, an indirect
- value, the critical value that makes a pointer special to begin with.
- The indirect value may also be the trickiest of the three attributes to work
- with and to fully understand. To find the indirect value of a pointer, you
- take the contents of the pointer as an address, from which you retrieve a
- value. The indirect value is thus the value at the contents of the pointer.
- The expression for the indirect value also has a type, and the type is always
- the same as the type part of the pointer itself. If p is a pointer to char,
- then the indirect value of p is a char. If p points at a double, then the
- indirect value of p is a double.
- In a program, the expression which evaluates to the third attribute, or
- indirect value, is *p, as in
- *p /* *p is the INDIRECT VALUE of p */
- There are several good ways to think about the meaning of the term indirect
- value. In a sense, the contents of the pointer is the pointer's direct value.
- When you access a variable directly, you expect to receive the value of that
- variable's contents. But with a pointer, you can use this contents as an
- address to go look for something else. In effect, you get to the thing you are
- looking for indirectly, using the pointer as an intermediate stepping stone.
- This is where the term indirect value comes from.
- These three attributes can be organized into a small but powerful set of
- values concerning a pointer. If you keep these three values clearly
- distinguished one from another in working with a pointer, you will be most
- likely to use the pointer correctly in your programs.
- To review, the three attributes are:
- location -- where the pointer is itself stored (&p).
- contents -- what is stored in the pointer (p).
- indirect value -- what is stored AT the contents of pointer (*p).
-
-
- Key Fact #5 -- The three attributes of a pointer represent three distinct
- address levels. These address levels can also be called levels of indirection.
-
-
- You may have noticed that although I discussed the contents of the pointer as
- the very first attribute, I am now showing the location as the top or first
- attribute. The reason why will become clear in just a moment.
- Let's think about these three attributes of every pointer. What do they
- reveal? First of all, they show that there are levels of addressing, at least
- three levels represented by the three attributes. Each attribute is at a
- different level in the addressing scheme.
- Start with p itself. p is a variable containing an address. I call p a level 1
- expression. Level 1 means that p holds the address of something else. Look at
- what happens when you tack the ampersand onto p in front. Now you get the
- address of p, &p. &p is the address of something whose contents is also an
- address, that is, an address of an address. This I call a level 2 expression.
- Starting with the value &p, you can do the process of going to an address and
- finding a value twice.
- *p is also at a different level than p. To get to *p from p you go to an
- address. You use up one level of addressing and go "down" to the next lower
- level. So if p is level 1, *p is level 0. *p is just like other variables
- which don't hold addresses, such as the int variable i.
- To review, we have three different levels of indirection represented by the
- three attributes:
- level 2 -- location of p (&p)
- level 1 -- contents of p (p)
- level 0 -- indirect value of p (*p)
- It's not hard to imagine the levels connected together as steps on a ladder,
- and that's precisely the second essential programming concept about pointers,
- the Ladder of Indirection.
-
-
- The Ladder of Indirection
-
-
- The Ladder of Indirection is really a model of how expressions change level in
- pointer space. In the model, pointer space is a series of discrete planes,
- starting at ground level 0, and connected by a "ladder," or means of ascending
- and descending.
-
-
- Key Fact #6 -- Pointer space is organized into a series of planes or levels.
-
-
- Every pointer expression can be assigned to one of these planes. The plane of
- a pointer expression is a measure of how much potential for indirection there
- is in that pointer expression.
- All pointers and pointer expressions can be seen as existing on particular
- planes in this model of pointer space. The ladder is visualized as connecting
- the planes from level 0 upwards to infinity. Each rung up is the next plane on
- the Ladder of Indirection. Each rung down is the next plane down.
- Moving up the ladder of indirection involves the process known as referencing,
- or taking the address of something. When you take the address of something you
- create a reference to that thing. References to objects are exactly what gets
- stored in pointers. Every pointer must have at least one level of indirection
- associated with it, or it couldn't be called a pointer. Some pointer
- expressions have two or more levels of indirection associated with them. By
- the way, don't be fooled by C++ references. References in C++ are a distinct
- set of types, so-called precisely because they do indeed store an address
- rather than a complete object. Referencing and dereferencing in C are general
- terms, synonymous with taking an address of something and with going to
- something by means of its address.
- Every time you take something's address you go up a level on the ladder. Every
- time you go to an address, you go down a level on the ladder. Different
- operators take you up and down the ladder in different ways.
- Imagine the dizzying whole of pointer space, with its Ladder connecting planes
- ascending upward forever. Well, not really forever. Standard C says an
- identifier may be declared with up to 12 modifying declarators, so level 12 is
- the top, though some heavy duty compilers might support more. Objects and
- expressions seem to move around or change values on a given plane, but
- sometimes they leap up and jump to the next plane above. Something's address
- has just been taken. At other times, references snake down from one plane to
- the one below. An address on the higher plane has been used to descend to a
- particular location on the lower. Except for ground level 0, this whole
- organization of planes is highly symmetrical. Each level is equal to every
- other level. Each one has its own precise level of indirection. The critical
- point is that the level of indirection is an intrinsic part of the type of a
- pointer. A level 2 pointer, in general, should not be used where a level one
- pointer is required. Using pointers properly means always keeping track of the
- level of indirection associated with each pointer.
- Level 0 is different for one particular reason. You cannot go beneath it.
- There is no level -1. So indirection has to stop when an expression reaches
- level 0. Only with pointer expressions can you ever go down a level, and you
- always have to stop at the bottom.
-
-
- Master Pointers to Get Arrays
-
-
- The heading for this section, with its fully intended pun, is designed to
- introduce a very powerful, but also subtle, relationship that exists in C
- between pointers and arrays.
- While it is very true that understanding pointers fully might well lead to a
- raise in pay, at least for a professional programmer, the real issue here is
- that arrays in C are much more closely related to pointers than might be
- apparent at first glance. One of the deeper elegances of C concerns this
- special relationship between pointers and arrays. Incidentally, some have
- considered this elegance a weakness in certain contexts.
-
-
-
- Key Fact #7 -- The name of an array usually behaves as if the array name were
- a pointer value.
-
-
- Why does an array name in a C expression often behave like a pointer value?
- The answer is simple, a matter of formal definition, built right into the
- fundamentals of C. An array name used in a program is really an expression in
- its own right. When the translator comes upon an array name, the translator
- will evaluate the array name expression according to the standard rules for
- expression evaluation. In almost every context, the array-name expression will
- evaluate to an address. What address? The address of the data actually stored
- in the first element of the array! Since arrays in C are indexed starting with
- 0, I call this address the address of the "zeroth" element of the array.
-
-
- Key Fact #8 -- The name of an array, in almost every context, evaluates to the
- address of the array's own "zeroth" element.
-
-
- The reason for making such a hedged statement is the desire to avoid a common
- misunderstanding, usually stated something like "an array name is a pointer."
- This apparently reasonable statement is in fact quite false. An array never
- becomes a pointer and a pointer is not the same type as an array. What is true
- is that array names act like pointer values is nearly every context. But not
- always. An array name as the operand of the sizeof operator evaluates to the
- size of the entire array, not the size of a pointer, just one example of an
- array name not behaving like a pointer.
- The [] operator, usually thought of as being related to arrays, is also a
- dereferencing operator. p[n] lives on the plane below p. To see that this is
- the case consider the simple array declaration
- int arr[10];
- What level does the expression arr have in most contexts? arr is a level-one
- expression, evaluating to the address of the zeroth element of arr, in
- virtually every context. arr behaves in this regard like a pointer to int,
- though you must be careful not to say that an array name is a pointer.
- Pointers are modifiable lvalues, array names are non-modifiable array-name
- lvalues, not quite the same thing!
- In any event, arr will typically behave like a level-one value. What about
- arr[0]? arr[0] is clearly a level 0 value. arr[0] represents the actual data
- in the first element of the arr array. So the subscript brings you down one
- level of indirection, just as the * did in a dereference.
-
-
- Summary
-
-
- In this installment, I have defined the Three Attributes and the Ladder of
- Indirection, and discussed the role of arrays. In the next installment, I will
- teach you the game of Pointer Dominos. This is a game which I made up in the
- process of teaching C classes in the early 1980's. The essential notion here
- is that working with pointers is as simple as playing dominos. There are only
- a small number of moves, and the moves are always played in a particular
- order.
- The rules of pointer dominos, and the solution to the puzzle in Listing 1 will
- be described in Part 2 of "Pointer Power in C and C++," appearing in next
- month's C Users Journal.
-
- Listing 1 What does this program print?
- #include <stdio.h>
-
- char *ap[] = {
- "INTEGER",
- "PROPORTION",
- "DEBUGGER",
- "PORTABLE",
- "TOWER!"
- };
-
- char **app[] = { ap + 4, ap + 3, ap + 2, ap + 1, ap };
- char ***ppp = app;
- char ****pppp = &ppp;
-
- void main()
- {
- printf("%.*s", 2, *--**pppp);
- printf("%.*s", 3, *(++*pppp[0] - 4));
- printf("%s " , ++*--*++pppp[0] + 5);
- printf("%.*s", 2, *++pppp[0] [3] + 3);
- printf("%s\n", (*pppp + 2)[-2][2] + 2);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Solving Linear Equations Using C
-
-
- Matt Weisfeld
-
-
- Matt Weisfeld is currently employed by the Allen-Bradley Company in Highland
- Heights, Ohio. He is responsible for the design and development of test
- software on VAX/VMS, UNIX, DOS and other platforms. Matt is currently working
- on a book entitled Building and Testing Portable Libraries in C to be
- published by QED later this year. He can be reached on Compuserve at
- [71620,2171].
-
-
- Resource allocation, a fundamental issue in any discipline, involves
- maximizing value and minimizing costs. As early as Junior High School,
- students solve simultaneous equations to find where these two variables break
- even. However, solving simultaneous equations by hand works only when the
- number of unknowns is small. When the problem involves dozens of equations and
- variables, pencil and paper just aren't enough. Linear Programming (LP) is
- used to solve these large resource-allocation problems. This article presents
- a program for solving linear equations using C.
-
-
- Sample Application
-
-
- To illustrate how Linear Programming works in a real life application,
- consider the following example. The Acme soft drink company markets two
- different lines of cola: diet and regular. Operating at maximum daily
- capacity, Acme can produce four tons of diet and six tons of regular. Most
- ingredients (such as water and sweetener) are obtained upon demand, so they do
- not affect potential output. However, the secret ingredient that makes Acme
- cola taste so special is limited to 24 pounds a day. Each ton of diet needs
- three pounds of the secret ingredient while each ton of regular needs four
- pounds. Finally, each ton of diet sells for $200,000, while each ton of
- regular sells for $600,000. The Acme company must maximize income by producing
- the optimal amount of diet and regular cola.
- The standard LP problem consists of variables, an objective, and constraints.
- The variables in this example represent the two types of cola, diet (x1) and
- regular (x2). The goal is to allocate these variables in a fashion that
- maximizes the company's income (z). The objective function can be written
- z = 2x1 + 6x2 (in units of $100,000)
- To complete the LP model three constraints must be factored in:
- x1 £ 4 (max diet in tons)
- x2 £ 6 (max regular in tons)
- 3x1 + 4x2 £ 24 (in pounds)
- The final problem is:
- Maximize
- z = 2x1 + 6x2 (objective function)
- such that
- x1 £ 4 (constraint 1)
- x2 £ 6 (constraint 2)
- 3x1 + 4x2 £ 24 (constraint 3)
- x1, x2 >=0 (it is impossible to have a negative amount of cola)
-
-
- The Graphical Solution
-
-
- Since this problem has only two variables, you can depict the solution
- graphically. Understanding the problem in terms of a graph makes the
- transition to the algorithmic solution much easier. However, once a third
- variable (and thus a third dimension) enters the picture, graphing becomes
- quite a challenge.
- Figure 1 shows the graph of the Acme problem with only the constraints
- plotted. Notice the area contained within the constraint lines and the x1 and
- x2 axis. This area is called the Feasible Solution Space. Any point within
- this area will yield a combination of diet and regular cola that can be
- produced and yield some income. However, the goal is to find the best
- solution, not simply a feasible one. Notice also that there are five edges
- that surround the Feasible Solution Space. An edge is where two lines meet to
- form a single point. The optimal solution will be found at one of these edges.
- To find the optimal solution, you need the objective function. Figure 2 shows
- two possible solutions based on a different value for z. Since the ratio of x1
- to x2 is known, the slope of the line can be calculated. By varying the
- position of the line (the slope is always the same so the lines must move in a
- parallel manner), different solutions to this problem can be explored. The
- values of x1 and x2 are obtained by examining the points where the objective
- function intersects the lines bounding the feasible solution space. Line 1
- intersects the feasible solution space at edge b (point x1=0, x2=6: z=36).
- Notice that as the line moves away from the origin, the value of z increases.
- Also remember that the objective line must intersect the feasible solution
- space for a valid solution to exist. Looking at the graph, it is apparent that
- the largest value of z that intersects the Feasible Solution Space is at edge
- c (point x1=1.5, x2=6, z=39). An optimal solution not falling on an edge
- represents a situation where the objective function is parallel to one of the
- constraints and more than one (actually infinite) optimal solution exists.
-
-
- The Standard Form of the LP Model
-
-
- In order to have a mathematical algorithm that can solve general LP models as
- well as lend itself to a computer implementation, all LP problems must follow
- the Standard Form of the LP model. The Standard Form includes these features:
- All constraints are equations with a non-negative Right Hand Side (RHS). If a
- RHS is negative, simply multiple both sides by --1.
- All variables are non-negative. In this case there is no choice. There is no
- way to produce a negative amount of diet cola.
- The objective function can be maximized or minimized. In the sample problem
- you are maximizing income. However, the problem could be designed to minimize
- costs (this will be discussed later).
- To satisfy the first rule the constraints must be converted to equations. This
- may seem confusing, but in their present form they are not now equations. The
- operator used in all the constraints is £. To make the constraints conform to
- the Standard Form, equal signs must separate the left-hand-side from the
- right-hand-side. However, simply replacing the £ with an = is inappropriate
- since it actually alters the constraint (eg: i£1 is obviously not the same as
- i=1). To properly convert the constraint to an equation, a slack variable must
- be added. The slack variable accounts for the fact that the £ represents a
- wide range of values and permits the use of the equality.
- Even though the slack variables are necessary in the conversion of the
- constraints, they do not contribute to the value of the objective function.
- Only x1 and x2 affect the income. The slack variables are created to assist in
- the computations, they do not represent real unknowns. To illustrate this
- fact, the objective function multiplier for each slack variable is 0. Thus the
- Acme Cola problem, represented in the Standard Form, is:
- Maximize
- z = 2x1 + 6x2 + 0s1 + 0s2 + 0s3 (objective function)
- such that
- x1 + s1 = 4 (constraint 1)
- x2 +s2 = 6 (constraint 2)
- 3x1 + 4x2 s3 = 24 (constraint 3)
- x1, x2 >= 0 (it is impossible to have a negative amount of cola)
- In this case, the slack variables are all positive because all the constraints
- are of the £ type. If the constraints were >=, the slack variables would be
- negative. Figure 3 shows how the slack variables are represented graphically.
- There is a direct relationship between the number of graph sides (5) and the
- number of variables (x1, x2, s1, s2, s3).
-
-
-
- The Starting Solution
-
-
- To begin solving the Acme problem you must identify an initial, feasible
- starting solution. Since the optimal solution must be an edge, this problem
- has five possible candidates (as can be seen on the graph). Normally, you
- would begin at the origin when a problem is bounded by the x1 and x2 axis
- (where x1, x2 > 0), since this is both a feasible solution and an edge. The
- concept of the algorithm (presented later) is to move from the current edge to
- the next adjacent edge on a path to the optimal solution. The direction to
- move is determined by the algorithm. Thus, from the origin (edge a in Figure
- 2), the algorithm moves to edge b and then stops at edge c, the optimal
- solution. The algorithm must follow adjacent edges (that is, the solution
- cannot move directly from edge a to edge c).
-
-
- The Initial Table
-
-
- A table format is used to group all the information needed to solve the
- problem. The C data structures mimic this table. Table 1 contains the initial
- table. The top row of the table is simply a header. The second row represents
- the objective function. The remaining number of rows are variable and depend
- on the number of constraints.
- The first column identifies the variables that are currently in the basic
- solution. As mentioned before, the starting edge is at the origin (x1 and x2
- are zero). This leaves the slack variables (s1, s2, s3) as the variables in
- the starting solution, which is called the basis. Notice that the rows
- representing the slack variables form an identity matrix (ones down the
- horizontal). Note, if some of the constraints had been >=, the corresponding
- slack variables would be negative and thus not a candidate for entry into the
- basis. In this instance, the algorithm presented would need to be modified to
- construct a proper basis. Finally, the column at the far right represents the
- current values for the variables in the basis.
-
-
- The C Data Structures
-
-
- Since the user interface is not a topic of this discussion, all data
- structures are defined internally. However, in any practical application the
- input data should not be hard-coded. Listing 1 contains all definitions. The C
- program includes three arrays that hold all the data necessary to represent a
- table. The arrays basis and objective hold the names (labels) that represent
- the variables. The actual computations are performed on the array called
- table. The dimensions of the table are always rows by columns. In the example
- table, the dimensions are four rows by seven columns. The only other
- information needed are the number of variables and constraints, which in this
- example are two and three respectively.
-
-
- The Simplex Method
-
-
- The algorithm used to solve this problem is called the simplex method. With
- this method you determine if there are any non-basic variables that, if in the
- basic solution, would enhance the final result. Based on the initial table,
- the basis variables are s1, s2, and s3. This is a feasible solution, however,
- since the variables x1 and x2 are not in the basis, the result is to produce
- nothing (z=0). To identify any variables that should enter the basis, inspect
- the objective function. The objective function is
- z x1 x2 s1 s2 s3 sol
- ---------------------------
- 1 -2 -6 0 0 0 0
- Any variable that is negative (in a maximization problem) will increase the
- value of z, and so should enter the basis. If there is more than one negative
- value, the most negative one is chosen. Thus, there are two candidates for the
- entering value (--2 and --6). In this case, x2 should enter the basis.
- It is obvious that if one variable enters the basis, another currently in the
- basis must leave. Since s1, s2, and s3 are in the basis, one must leave if x2
- is to enter. Determining the leaving variable is done by inspecting the
- numbers in the column under entering variable, x2. They are
- x2
- -----
- s1 0.0
- s2 1.0
- s3 3.0
- All numbers that are not positive are not considered, thus s1 cannot leave the
- basis at this time. To determine which candidate actually leaves the basis,
- the ratios between the values in the entering column and the values in the
- solution are taken. For example, since there are two possible candidates to
- leave the basis, the ratios considered are as follows:
- sol x2 ratio
- ----- ----- -----
- s2 : 6 / 1 = 6
- s3 : 24 / 3 = 8
- The lowest value is chosen to leave the basis, thus s2 receives the honor.
- This makes the value 1.00, at the intersection of column x1 and row s2, the
- pivot element.
- Now that the entering and leaving variables have been identified, the next
- iteration of the table is calculated by using the Gauss-Jordon method. Looking
- at the table, it is evident that for x2 to become part of the basis, it must
- replace s2 as part of the identity matrix. To do this the pivot element must
- be a 1. Thus, all values in the leaving equation are divided by the pivot
- element. In this instance, the value of the pivot element is already a 1 so
- the division does not change anything. The row that was s2 and is now replaced
- by x2 is called the pivot equation.
- As mentioned above, placing x2 in the basis means that it must replace s2 as
- part of the identity matrix. Since the pivot element is now a 1, all the other
- values in the x2 column must be 0. To do this the transformation
- new equation = old equation - (entering column coefficient
- new pivot equation)
- is applied to all elements of all the rows (including the objective function)
- other than the pivot equation.
- Thus the objective function transformation is
- old :1 -2 -6 0 0 0 0
-
- -(-6)0 -(6)(0) -(6)(1) -(6-(0) -(6)(1) -(6)(0) -(6)(6)
- -------------------------------------------------------------------
-
- new :1 -2 0 0 6 0 36
- The s1 equation is unchanged due to the fact that the entering column
- coefficient is already zero. The s3 equation transformation is as follows:
- old :0 4 3 0 0 1 24
-
- -(3)0 -(3)(0) -(3)(1) -(3)(0) -(3)(1) -(3)(0) -(3)(6)
- ------------------------------------------------------------------
-
-
- new :0 4 0 0 -3 1 6
- The first iteration is complete and the second table is presented in Table 2.
- The important information that can be gathered from inspecting this table is
- in the solution column. The 36 in the solution column of the objective
- function indicates that with the current solution the ACME Co. can make $36.00
- (in units of 100,000). To determine the product mix, examine the variables in
- the basis. In this case only x2 is in the basis. Its solution is 6 (if six
- units of x2 are produced, ACME will make $36.00). However, since the objective
- function still has a negative value, the optimum solution has not yet been
- reached.
- The variable x1, which has a value of -2, is the next to enter the basis. The
- ratios are:
- s1: 4/1 = 4
- s3: 6/4 = 1.5
- Thus s3 is the leaving variable. In this case the pivot element is not one, so
- all the elements of the pivot equation are divided by the pivot element, which
- is four. The new pivot equation is as follows:
- 0 1 0 0 -0.75 0.25 1.5
- Applying the Gauss-Jordon technique yields the table in Table 3. The first
- thing to notice is that there are no negative values in the objective
- function. This means that the optimal solution has been found (z=39). Also
- notice that both variables, x1 and x2, are in the basis. Thus, to maximize
- profits, ACME should produce 1.5 units of diet and 6 units of regular.
-
-
- The C Routines
-
-
- The amount of C code necessary to perform the table operations is surprisingly
- small. Listing 2 contains the code for the mainline which loops until there
- are no more entering variables, printing each iteration of the table. The
- entering variable (enter_pos) and leaving variable (leave_pos) are determined
- by the code in Listing 3. If enter_pos is 0 then the optimal solution has
- already been obtained and the loop terminates. The location of the pivot
- element is placed in the variable pivot_element.
- The routines new_pivot and new_equation are presented in Listing 4. new_pivot
- calculates the next pivot equation by dividing all elements in the pivot row
- by the pivot element, while new_equation performs the Gauss-Jordon algorithm
- on the entire table and completes the current iteration.
- To help illustrate the program's performance, and to aid in debugging, two
- additional routines are included. The first, build_basis, labels all the rows
- and columns for better readability when the table is printed. The second,
- print_table, simply prints the contents of the table so that the information
- it contains can be inspected. These routines are presented in Listing 5. All
- of the listing files can be combined into one program file for compilation and
- execution.
-
-
- Conclusion
-
-
- Linear Programming is a powerful tool when attempting to allocate resources.
- The example used in this article presents a solution for one specific case, a
- maximization problem with all constraints of the £ variety. As stated before,
- there are a number of variations to the standard LP model. In practice, there
- can be a mixture of constraints (>=, £, or = ) and the final goal may be to
- minimize the result (as in costs). When the constraints are mixed, extensions
- to the simplex algorithm are required. Furthermore, the results obtained from
- the final table are anything but final. All the numbers in the table represent
- useful information. (The act of gathering this information is called
- sensitivity analysis.)
- References
- Hamdy A. Taha. 1982. Operations Research, An Introduction, 3rd ed. New York,
- NY: MacMillan Publishing Company, Inc.
- Hillier, Frederick S. and Gerald J. Lieberman. 1980. Introduction to
- Operations Research, 3rd ed. San Francisco, CA: Holden-Day Inc.
- Figure 1 Feasible solution space for ACME problem
- Figure 2 Two possible solutions based on a different value for z
- Figure 3 Graphical representation of the slack variables
- Table 1 Original table
- basis z x1 x2 s1 s2 s3 sol
- --------------------------------------------------
- z 1.00 -2.00 -6.00 0.00 0.00 0.00 0.00
- s1 0.00 1.00 0.00 1.00 0.00 0.00 4.00
- s2 0.00 0.00 1.00 0.00 1.00 0.00 6.00
- s3 0.00 4.00 3.00 0.00 0.00 1.00 24.00
- Table 2 Iteration 1
- basis z x1 x2 s1 s2 s3 sol
- --------------------------------------------------
- z 1.00 -2.00 0.00 0.00 6.00 0.00 36.00
- s1 0.00 1.00 0.00 1.00 0.00 0.00 4.00
- x2 0.00 0.00 1.00 0.00 1.00 0.00 6.00
- s3 0.00 4.00 0.00 0.00 -3.00 1.00 6.00
- Table 3 Iteration 2
- basis z x1 x2 s1 s2 s3 sol
- --------------------------------------------------
- z 1.00 0.00 0.00 0.00 4.50 0.50 39.00
- s1 0.00 0.00 0.00 1.00 0.75 -0.25 2.50
- x2 0.00 0.00 1.00 0.00 1.00 0.00 6.00
- x1 0.00 1.00 0.00 0.00 -0.75 0.25 1.50
-
- Listing 1 Function definitions
- #include <stdio.h>
-
- #define LABELSIZE 10
-
- /* define matrix parameters */
- #define ROWS 4
- #define COLUMNS 7
- #define VARIABLES 2
-
- #define EQUATIONS 3
-
- /* initialize matrix */
- float table[ROWS][COLUMNS] = { 1,-2,-6,0,0,0,0,
- 0, 1, 0,1,0,0,4,
- 0, 0, 1,0,1,0,6,
- 0, 4, 3,0,0,1,24, };
-
- /* char strings to hold labels */
- char basis[LABELSIZE][LABELSIZE];
- char objective[LABELSIZE][LABELSIZE];
-
- /* used to build labels */
- char var[LABELSIZE];
- char num[LABELSIZE];
-
- /* save info for leaving & entering var */
- int leave_pos;
- float leave_holder;
-
- int enter_pos;
- float enter_holder;
-
- /* save info for pivot element */
- float pivot_element;
-
- /* count # of iterations */
- int pass;
-
- /* prototypes */
- int select_entering(void);
- int select_leaving(void);
- void new_pivot(void);
- void new_equation(void);
- void build_basis(void);
- void print_table(void);
- /* End of File */
-
-
- Listing 2 Main loop of the LP program
- main(int argc, char **argv)
- {
-
- int i,j; /* loop control */
- char temp[10]; /* workspace */
-
- /* build the initial tableau */
- build_basis();
- printf ("**** ORIGINAL TABLE\n");
- print_table();
- pass = 1;
-
- /* select_entering will return a 0 when
- there are no more entering VARIABLES,
- otherwise, the location of the entering
- variable is returned */
- while (enter_pos = select_entering()) {
-
- /* return pos for leaving variable */
-
- leave_pos = select_leaving();
-
- /* calculate the pivot element */
- pivot_element = table[leave_pos][enter_pos];
-
- /* calculate the new pivot equation */
- new_pivot();
-
- /* calculate all the non-pivot EQUATIONS */
- new_equation();
-
- /* label the new basis */
- strcpy (basis[leave_pos],
- objective[enter_pos]);
-
- printf ("\n**** ITERATION %d\n\n", pass);
- pass++;
-
- print_table();
-
- }
-
- }
- /* End of File */
-
-
- Listing 3 Code to determine entering and leaving variables
- int select_entering(void)
- {
-
- int i,j;
-
- enter_pos = 0;
- enter_holder = 0.0;
-
- /* determine the most neg value, if any */
- for (j=1; j<COLUMNS-1; j++) {
- if (table[0][j]<0) {
- if (table[0][j] < enter holder) {
- enter_holder = table[0][j];
- enter_pos = j;
- }
- }
- }
-
- /* if j has been changed from 0, then we have
- an entering equation */
-
- return(enter_pos);
-
- }
-
- int select_leaving (void)
- {
-
- int i,j;
-
- float ratio[50];
-
-
- leave_pos = 0;
- leave_holder = 999.0;
-
- /* determine the lowest ratio of the
- positive elements */
-
- for (i=1; i<ROWS;i++) {
-
- if (table[i][enter_pos] > 0) {
-
- ratio[i] =
- table[i][COLUMNS-1]/table[i][enter_pos];
-
- if (ratio[i] < leave_holder) {
-
- leave_holder = ratio[i];
- leave_pos = i;
-
- }
-
- }
-
- }
-
- return (leave_pos);
-
- }
- /* End of File */
-
-
- Listing 4 Code for finding pivot row and completing current iteration
- void new_pivot(void)
- {
-
- int i,j;
-
- /* calculate the new pivot equation */
- for (j=0; j<COLUMNS; j++) {
- table[leave_pos][j] =
- table[leave_pos][j]/pivot_element;
- }
- }
-
- void new_equation(void)
- {
-
- int i,j;
-
- float enter_coef;
- float new_pivot;
- float new_pivot_equ;
-
- /* calculate all the non-pivot EQUATIONS */
- for (i=0;i<=ROWS;i++) {
- enter_coef = -table[i][enter_pos];
-
- /* if the pivot coefficient is zero,
- or if this is the leaving equation,
- skip */
-
- if ( (i == leave_pos) (enter_coef == 0) )
- continue;
- for (j=0; j<COLUMNS; j++) {
- new_pivot = table[leave_pos][j];
- new_pivot_equ = new_pivot*enter_coef;
- table[i][j] =
- table[i][j] + new_pivot_equ;
- }
- }
- }
- /* End of File */
-
-
- Listing 5 Code for labeling and printing table
- void build_basis(void)
- {
- int i,j;
- strcpy (objective[0], "z");
- i = 1;
-
- /* create the labels for the orig tableau */
- for (j=1; j<VARIABLES+1; j++) {
- strcpy (var, "X");
- itoa(i, num);
- strcat (var,num);
- strcpy (objective[j], var);
- i++;
- }
- i = 1;
- for (j=VARIABLES+1; j<EQUATIONS+VARIABLES+1;
- j++) {
- strcpy (var, "s");
- itoa(i, num);
- strcat (var,num);
- strcpy (objective[j], var);
- i++;
- }
- strcpy (objective[j], "sol");
- strcpy (var, "z");
- strcpy (basis[0], var);
- for (i=1;i<=EQUATIONS;i++) {
- strcpy (var, "s");
- itoa(i, num);
- strcat (var,num);
- strcpy (basis[i], var);
- }
- }
-
- void print_table(void)
- {
- int i,j;
- printf ("\n");
- printf ("%6s"," basis ");
- for (j=0; j<=COLUMNS; j++) {
- if ((j==1) (j==COLUMNS-1))
- printf (" ");
- printf (" %6s", objective[j]);
- }
- printf ("\n");
-
- for (j=0; j<COLUMNS+2; j++) {
- printf ("-------");
- }
- printf ("\n");
- for (i=0; i<ROWS;i++) {
- printf (" %6s", basis[i]);
- for (j=0; j<COLUMNS; j++) {
- if ((j==0) (j==1)
- (j==COLUMNS-1))
- printf (" ");
- printf (" %6.2f",table[i][j]);
- }
- if (i==0) {
- printf ("\n");
- for (j=0; j<COLUMNS+2; j++) {
- printf ("-------");
- }
- }
- printf ("\n");
- }
- printf ("\n");
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- Time Conversion Functions
-
-
-
-
- P. J. Plauger
-
-
- P.J. Plauger is senior editor of The C Users Journal. He is convenor of the
- ISO C standards committee, WG14, and active on the C++ committee, WG21. His
- latest books are The Standard C Library, published by Prentice-Hall, and ANSI
- and ISO Standard C (with Jim Brodie), published by Microsoft Press. You can
- reach him at pjp@plauger.com.
-
-
-
-
- Introduction
-
-
- Last month, I discussed the basic functions for obtaining scalar estimates of
- times. These include both elapsed execution time (type clock_t) and calendar
- time (type time_t). (See "The Header <time.h>," CUJ January 1993.) With just
- those facilities, you can obtain time stamps and measure small and large time
- intervals.
- But that's not the end of it. The Standard C library also lets you represent
- times in terms of their more familiar clock and calendar components (type
- struct tm). Moreover, the component representation can be in terms of either
- local time or UTC (formerly GMT). It can even keep track of whether Daylight
- Savings Time is in effect.
- My goal this month is to lead you through the code that converts between
- scalar and structured times. If you've ever worked with calendar computations,
- you know that this is hard code to write and debug. That makes it rather hard
- to understand as well. Be prepared to take an occasional deep breath.
-
-
- What the C Standard Says
-
-
-
-
- 7.12.3 Time conversion functions
-
-
- Except for the strftime function, these functions return values in one of two
- static objects: a broken-down time structure and an array of char. Execution
- of any of the functions may overwrite the information returned in either of
- these objects by any of the other functions. The implementation shall behave
- as if no other library functions call these functions.
-
-
- 7.12.2.3 The mktime function
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- time_t mktime
- (struct tm *timeptr);
-
-
- Description
-
-
- The mktime function converts the broken-down time, expressed as local time, in
- the structure pointed to by timeptr into a calendar time value with the same
- encoding as that of the values returned by the time function. The original
- values of the tm_wday and tm_yday components of the structure are ignored, and
- the original values of the other components are not restricted to the ranges
- indicated above.139 On successful completion, the values of the tm_wday and
- tm_yday components of the structure are set appropriately, and the other
- components are set to represent the specified calendar time, but with their
- values forced to the ranges indicated above; the final value of tm_mday is not
- set until tm_mon and tm_year are determined.
-
-
- Returns
-
-
-
- The mktime function returns the specified calendar time encoded as a value of
- type time_t. If the calendar time cannot be represented, the function returns
- the value (time_t)-1.
-
-
- Example
-
-
- What day of the week is July 4, 2001?
- #include <stdio.h>
- #include <time.h>
-
- static const cha[ *const wday[] = {
- "Sunday", "Monday", "Tuesday", "Wednesday",
- "Thursday", "Friday", "Saturday", "-unknown-"
- };
- struct tm time_str;
- /*...*/
-
- time_str.tm_year = 2001 - 1900;
- time_str.tm_mon = 7 - 1;
- time_str.tm_mday = 4;
- time_str.tm_hour = 0;
- time_str.tm_min = 0;
- time_str.tm_sec = 1;
- time_str.tm_isdst = -1;
- if (mktime(&time_str) == -1)
- time_str. tm_wday = 7;
- printf("%s\n",
- wday [time_str.tm_wday]);
-
- ....
-
-
- 7.12.3.3 The gmtime function
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- struct tm *gmtime
- (const time_t *timer);
-
-
- Description
-
-
- The gmtime function converts the calendar time pointed to by timer into a
- broken-down time, expressed as Coordinated Universal Time (UTC).
-
-
- Returns
-
-
- The gmtime function returns a pointer to that object, or a null pointer if UTC
- is not available.
-
-
- 7.12.3.4 The local time function
-
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- struct tm *localtime
- (const time_t *timer);
-
-
- Description
-
-
- The local time function converts the calendar time pointed to by timer into a
- broken-down time, expressed as local time.
-
-
- Returns
-
-
- The localtime function returns a pointer to that object.
- Footnotes
- 139. Thus, a positive or zero value for tm_isdst causes the mktime function to
- presume initially that Daylight Saving Time, respectively, is or is not in
- effect for the specified time. A negative value causes it to attempt to
- determine whether Daylight Saving Time is in effect for the specified time.
-
-
- Using the Conversion Functions
-
-
- Note that the two functions that return a value of type pointer to struct tm
- return a pointer to a static data object of this type. Thus, a call to one of
- these functions can alter the value stored on behalf of an earlier call to
- another (or the same) function. Be careful to copy the value stored in this
- shared data object if you need the value beyond a conflicting function call.
- gmtime -- (The gm comes from GMT, which is now a slight misnomer.) Use this
- function to convert a calendar time to a broken-down UTC time. The member
- tm_isdst should be zero. If you want local time instead, use localtime, below.
- See the warning about shared data objects, above.
- localtime -- Use this function to convert a calendar time to a broken-down
- local time. The member tm_isdst should reflect whatever the system knows about
- Daylight Savings Time for that particular time and date. If you want UTC time
- instead, use gmtime, above. See the warning about shared data objects, above.
- mktime -- This function first puts its argument, a broken-down time, in
- canonical form. That lets you add seconds, for example, to the member tm_sec
- of a broken-down time. The function increases tm_min for every 60 seconds it
- subtracts from tm_sec until tm_sec is in the interval [0, 59]. The function
- then corrects tm_min in a similar way, then each coarser division of time
- through tm_year. It determines tm_wday and tm_yday from the other fields.
- Clearly, you can also alter a broken-down time by minutes, hours, days,
- months, or years just as easily.
- mktime then converts the broken-down time to an equivalent calendar time. It
- assumes the broken-down time represents a local time. If the member tm_isdst
- is less than zero, the function endeavors to determine whether Daylight
- Savings Time was in effect for that particular time and date. Otherwise, it
- honors the original state of the flag. Thus, the only reliable way to modify a
- calendar time is to convert it to a broken-down time by calling localtime,
- modify the appropriate members, then convert the result back to a calendar
- time by calling mktime.
-
-
- Implementing the Conversion Functions
-
-
- Listing 1 shows the file gmtime.c. The function gmtime is the simpler of the
- two functions that convert a calendar time in seconds (type time_t) to a
- broken-down time (type struct_tm). It simply calls the internal function
- _Ttotm. The first argument is a null pointer to tell _Ttotm to store the
- broken-down time in the communal static data object. The third argument is
- zero to insist that Daylight Savings Time is not in effect.
- Listing 2 shows the file xttotm.c. It defines the function _Ttotm that tackles
- the nasty business of converting seconds to years, months, days, and so forth.
- The file also defines the function _Daysto that _Ttotm and other functions use
- for calendar calculations.
- _Daysto counts the extra days beyond 365 per year. To do so, it must determine
- how may leap days have occurred between the year you specify and 1900. The
- function also counts the extra days from the start of the year to the month
- you specify. To do so, it must sometimes determine whether the current year is
- a leap year. The function recognizes that 1900 was not a leap year. It doesn't
- bother to correct for the non-leap years 1800 and earlier, or for 2100 and
- later. (Other problems arise within just a few decades of those extremes
- anyway.)
- _Daysto handles years before 1900 only because the function mktime can develop
- intermediate dates in that range and still yield a representable time_t value.
- (You can start with the year 2000, back up 2,000 months, and advance 2 billion
- seconds, for example.) The logic is carefully crafted to avoid integer
- overflow regardless of argument values. Also, the function counts excess days
- rather than total days so that it can cover a broader range of years without
- fear of having its result overflow.
- _Ttotm uses _Daysto to determine the year corresponding to its time argument
- secsarg. Since the inverse of _Daysto is a nuisance to write, _Ttotm guesses
- and iterates. At worst, it should have to back up one year to correct its
- guess. Both functions use the macro MONTAB, defined at the top of the file, to
- determine how many days precede the start of a given month. The macro also
- assumes that every fourth year is a leap year, except 1900.
- The isdst (third) argument to _Ttotm follows the convention for the isdst
- member of struct tm:
- If isdst is greater than zero, Daylight Savings Time is definitely in effect.
- _Ttotm assumes that its caller has made any necessary adjustment to the time
- argument secsarg.
- If isdst is zero, Daylight Savings Time is definitely not in effect. _Ttotm
- assumes that no adjustment is necessary to the time argument secsarg.
- If isdst is less than zero, the caller doesn't know whether Daylight Savings
- Time is in effect. _Ttotm should endeavor to find out. If the function
- determines that Daylight Savings Time is in effect, it advances the time by
- one hour (3,600 seconds) and recomputes the broken-down time.
- Thus, _Ttotm will loop at most once. It calls the function _Isdst only if it
- needs to determine whether to loop. Even then, it loops only if _Isdst
- concludes that Daylight Savings Time is in effect.
- Listing 3 shows the file xisdst.c. The function _Isdst determines the status
- of Daylight Savings Time (DST). _Times._Isdst points at a string that spells
- out the rules. (I'11 show the file asctime.c next month. It contains the
- definition of _Times.)
- _Isdst works with the rules in encoded form. Those rules are not current the
- first time you call the function or if a change of locale alters the last
- encoded version of the string _Times._Isdst. If that string is empty, _Isdst
- looks for rules appended to the time-zone information _Times._Tzone. It calls
- _Getzone as necessary to obtain the time-zone information. It calls _Gettime
- to locate the start of any rules for DST. The function _Getdst then encodes
- the current array of rules, if that is possible.
- Given an encoded array of rules, _Isdst scans the array for rules that cover
- the relevant year. It adjusts the day specified by the rule for any weekday
- constraint, then compares the rule time against the time that it is testing.
- Note that the first rule for a given starting year begins not in DST.
- Successive rules for the same year go in and out of DST.
- Listing 4 shows the file xgetdst.c. It defines the function _Getdst that
- parses the string pointed to by _Times._Isdst to construct the array of rules.
- The first character of a (non-empty) string serves as a field delimiter, just
- as with other strings that provide locale-specific time information. The
- function first counts these delimiters so that it can allocate the array. It
- then passes over the string once more to parse and check the individual
- fields.
- _Getdst calls the internal function getint to convert the integer subfields in
- a rule. No overflow checks occur because none of the fields can be large
- enough to cause overflow. The logic here and in _Getdst proper is tedious but
- straightforward.
-
-
- Local Time
-
-
- Listing 5 shows the file localtim.c. The function localtime calls _Ttotm much
- like gmtime. Here, however, localtime assumes that it must convert a UTC time
- to a local time. To do so, the function must determine the time difference, in
- seconds, between UTC and the local time zone.
-
- The file localtim.c also defines the function _Tzoff that endeavors to
- determine this time difference (tzoff, in minutes). The time difference is not
- current the first time you call the function or if a change of locale alters
- the last encoded version of the string _Times._Tzone. If that string is empty,
- _Tzoff calls the function _Getzone to determine the time difference from
- environment variables, if that is possible.
- However obtained, the string _Times._Tzone takes the form :EST:EDT:+0300.
- _TzoffS calls the function _Gettime to determine the starting position (p) and
- length (n) of the third field (#2, counting from zero). The function strtol,
- declared in <stdlib.h> must parse this field completely in converting it to an
- encoded integer. Moreover, the magnitude must not be completely insane. (The
- maximum magnitude is greater than 12*60 because funny time zones exist on
- either side of the International Date Line.)
- Listing 6 shows the file xgettime.c. It defines the function _Gettime that
- locates a field in a string that specifies locale-specific time information.
- See the description of _Getdst, above, for how _Gettime interprets field
- delimiters. If _Gettime cannot find the requested field, it returns a pointer
- to an empty string.
- Listing 7 shows the file xgetzone.c. The function_Getzone calls getenv,
- declared in <stdlib.h>, to determine the value of the environment variable
- "TIMEZONE". That value should have the same format as the locale-specific time
- string _Times. _Tzone, described above (possibly with rules for determining
- Daylight Savings Time bolted on).
- If no value exists for "TIMEZONE", the function _Getzone then looks for the
- environment variable "TZ". That value should match the UNIX format ESTO5EDT.
- The internal function reformat uses the value of "TZ" to develop the preferred
- form in its static buffer.
- If _Getzone finds neither of these environment variables, it assumes that the
- local time zone is UTC. In any event, it stores its decision in the static
- internal buffer tzone. Subsequent calls to the function return this remembered
- value. Thus, the environment variables are queried at most once, the first
- time that _Getzone is called.
-
-
- Converting to Scalar Time
-
-
- Listing 8 shows the file mktime.c. The function mktime computes an integer
- time_t from a broken-down time struct tm. It takes extreme pains to avoid
- overflow in doing so. (The function is obliged to return the value --1 if the
- time cannot be properly represented.)
- The first part of mktime determines a year and month. If they can be
- represented as type int, the function calls_Daysto to correct for leap days
- since 1900. mktime then accumulates the time in seconds as type double, to
- minimize further fretting about integer overflow. If the final value is
- representable as type time_t, the function converts it to that type. mktime
- calls _Ttotm to put the broken-down time in canonical form. Finally, the
- function corrects the time in seconds for Daylight Savings Time and converts
- it from local time to UTC. (The resultant code reads much easier than it
- wrote.)
-
-
- Conclusion
-
-
- Next month, I conclude my discussion of the time functions. I also conclude my
- guided tour of the Standard C library. The trip is almost over.
- This article is excerpted in part from P.J. Plauger, The Standard C Library,
- (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
-
- Listing 1 gmtime.c
- /* gmtime function */
- #include "xtime.h"
-
- struct tm *(gmtime)(const time_t *tod)
- { /* convert to Greenwich Mean Time (UTC) */
- return (_Ttotm(NULL, *tod, 0));
- }
- /* End of File */
-
-
- Listing 2 xttotm.c
- /* _Ttotm and_Daysto functions */
- #include "xtime.h"
-
- /* macros */
- #define MONTAB(year) \
- ((year) & 03 (year) == 0 ? mos : lmos)
-
- /* static data */
- static const short lmos[] = {0, 31, 60, 91, 121, 152,
- 182, 213, 244, 274, 305, 335};
- static const short mos[] = {0, 31, 59, 90, 120, 151,
- 181, 212, 243, 273, 304, 334};
-
- int _Daysto(int year, int mon)
- { /* compute extra days to start of month */
- int days;
-
- if (0 < year) /* correct for leap year: 1801-2099 */
- days = (year - 1) / 4;
- else if (year <= -4)
- days = 1 + (4 - year) / 4;
- else
- days = 0;
- return (days + MONTAB(year)[mon]);
- }
-
-
- struct tm *_Ttotm(struct tm *t, time_t secsarg, int isdst)
- { /* convert scalar time to time structure */
- int year;
- long days;
- time_t secs;
- static struct tm ts;
-
- secsarg += _TBIAS;
- if (t == NULL)
- t = &ts;
- t->tm_isdst = isdst;
- for (secs = secsarg; ; secs = secsarg + 3600)
- { /* loop to correct for DST */
- days = secs / 86400;
- t->tm_wday = (days + WDAY) % 7;
- { /* determine year */
- long i;
-
- for (year = days / 365; days <
- (i = _Daysto(year, 0) + 365L * year); )
- --year; /* correct guess and recheck */
- days -= i;
- t->tm_year = year;
- t->tm_yday = days;
- }
- { /* determine month */
- int mon;
- const short *pm = MONTAB(year);
-
- for (mon = 12; days < pm[--mon]; )
- ;
- t->tm_mon = mon;
- t->tm_mday = days - pm[mon] + 1;
- }
- secs %= 86400;
- t->tm_hour =secs / 3600;
- secs %= 3600;
- t->tm_min = secs / 60;
- t->tm_sec = secs % 60;
- if (0 <= t->tm_isdst (t->tm_isdst = _Isdst(t)) <= 0)
- return (t); /* loop only if <0 => 1 */
- }
- }
- /* End of File */
-
-
- Listing 3 xisdst.c
- /* _Isdst function */
- #include <stdlib.h>
- #include "xtime.h"
-
- int _Isdst(const struct tm *t)
- { /* test whether Daylight Savings Time
- in effect */
- Dstrule *pr;
- static const char *olddst = NULL;
- static Dstrule *rules = NULL;
-
- if (olddst != _Times._Isdst)
-
- { /* find current dst_rules */
- if (_Times._Isdst[0] == '\0')
- { /* look beyond time zone info */
- int n;
-
- if (_Times._Tzone[0] == '\0')
- _Times._Tzone = Getzone();
- _Times._Isdst = _Gettime(_Times._Tzone,
- 3, &n);
- if (_Times._Isdst[0] != '\0')
- --_Times._Isdst; /* point to
- delimiter */
- }
- if ((pr = _Getdst(_Times._Isdst)) == NULL)
- return (-1);
- free(rules);
- rules = pr;
- olddst = _Times._Isdst;
- }
- { /* check time against rules */
- int ans = 0;
- const int d0 = _Daysto(t->tm_year, 0);
- const int hour = t->tm_hour + 24 * t->tm_yday;
- const int wd0 = (365L * t->tm_year + d0 + WDAY)
- % 7 + 14;
-
- for (pr = rules; pr->wday != (unsigned char)-1; ++pr)
- if (pr->year <= t->tm_year)
- { /* found early enough year */
- int rday = _Daysto(t->tm_year, pr->mon)
- - d0 + pr->day;
-
- if (0 < pr->wday)
- { /* shift to specific weekday */
- int wd = (rday + wd0 - pr->wday) % 7;
-
- rday += wd == 0 ? 0 : 7 - wd;
- if (pr->wday <= 7)
- rday -= 7; /* strictly before */
- }
- if (hour < rday * 24 + pr->hour)
- return (ans);
- ans = pr->year == (pr + 1)->year
- ? !ans : 0;
- }
- return (ans);
- }
- }
- /* End of File */
-
-
- Listing 4 xgetdst.c
- /* _Getdst function */
- #include <ctype.h>
- #include <stdlib.h>
- #include <string.h>
- #include "xtime.h"
-
- static int getint(const char *s, int n)
-
- { /* accumulate digits */
- int value;
-
- for (value = 0; 0 <= --n && isdigit(*s); ++s)
- value = value * 10 + *s - '0';
- return (0 <= n ? -1 : value);
- }
-
- Dstrule *_Getdst(const char *s)
- { /* parse DST rules */
- const char delim = *s++;
- Dstrule *pr, *rules;
-
- if (delim == '\0')
- return (NULL);
- { /* buy space for rules */
- const char *s1, *s2;
- int i;
-
- for (s1 = s, i = 2; (s2 = strchr(s1, delim)) != NULL; ++i)
- s1 = s2 + 1;
- if ((rules = (Dstrule *)malloc(sizeof (Dstrule) * i)) == NULL)
- return (NULL);
- }
- { /* parse rules */
- int year = 0;
-
- for (pr = rules; ; ++pr, ++s)
- { /* parse next rule */
- if (*s == '(')
- { /* got a year qualifier */
- year = getint(s + 1, 4) - 1900;
- if (year < 0 s[5] != ')')
- break; /* invalid year */
- s += 6;
- }
- pr->year = year;
- pr->mon = getint(s, 2) - 1; s += 2;
- pr->day = getint(s, 2) - 1; s += 2;
- if (isdigit(*s))
- { pr->hour = getint(s, 2); s += 2; }
- else
- pr->hour = 0;
- if (12 <= pr->mon 99 < pr->day 99 < pr->hour)
- break; /* invalid month, day, or hour */
- if (*s != '+' && *s != '-')
- pr->wday = 0;
- else if (s[1] < '0' '6' < s[1])
- break; /* invalid week day */
- else
- { /* compute week day field */
- pr->wday = s[1] == '0' ? 7 : s[1] - '0';
- if (*s == '+') /* '-': strictly before */
- pr->wday += 7; /* '+': on or after */
- s += 2;
- }
- if (*s == '\0')
- { /* done, terminate list */
- (pr + 1)->wday = (unsigned char)-1;
-
- (pr + 1)->year = year;
- return (rules);
- }
- else if (*s != delim)
- break;
- }
- free(rules);
- return (NULL);
- }
- }
- /* End of File */
-
-
- Listing 5 localtim.c
- /* localtime function */
- #include <stdlib.h>
- #include "xtime.h"
- time_t _Tzoff(void)
- { /* determine local time offset */
- static const char *oldzone = NULL;
- static long tzoff = 0;
- static const long maxtz = 60*13;
- if (oldzone ! = _Times._Tzone)
- { /* determine time zone offset
- (East is +) */
- const char *p, *pe;
- int n;
- if {_Times._Tzone[0] == '\0')
- _Times._Tzone = _Getzone();
- p = _Gettime(_Times._Tzone, 2, &n);
- tzoff = strtol(p, (char **)&pe, 10);
- if (pe - p != n
- tzoff <= -maxtz maxtz <= tzoff)
- tzoff = 0;
- oldzone = _Times._Tzone;
- }
- return (-tzoff * 60);
- }
- struct tm *(localtime){const time_t *tod)
- { /* convert to local time structure */
- return (_Ttotm(NULL, *tod + _Tzoff(), -1));
- }
- /* End of File */
-
-
- Listing 6 xgettime.c
- /* _Gettime function */
- #include <string.h>
- #include "xtime.h"
-
- const char *_Gettime(const char *s, int n, int *len)
- { /* get time info from environment */
- const char delim = *s ? *s++ : '\0';
- const char *s1;
-
- for (; ; --n, s = s1 + 1)
- { /* find end of current field */
- if ((s1 = strchr(s, delim)) == NULL)
- s1 = s + strlen(s);
-
- if (n <= 0)
- { /* found proper field */
- *len = s1 - s;
- return (s);
- }
- else if (*s1 == '\0')
- { /* not enough fields */
- *len = 1;
- return (s1);
- }
- }
- }
- /* End of File */
-
-
- Listing 7 xgetzone.c
- /*_Getzone function */
- #include <ctype.h>
- #include <stdlib.h>
- #include <string.h>
- #include "xtime.h"
-
- /* static data */
- static const char *defzone = ":UTC:UTC:0";
- static char *tzone = NULL;
-
- static char *reformat(const char *s)
- { /* reformat TZ */
- int i, val;
- static char tzbuf[] = ":EST:EDT:+0300";
-
- for (i = 1; i <= 3; ++i)
- if (isalpha(*s))
- { tzbuf[i] = *s; tzbuf[i + 4] = *s++; }
- else
- return (NULL);
- tzbuf[9] = *s == '-' *s == '+' ? *s++ : '+';
- if (!isdigit(*s))
- return (NULL);
- val = *s++ - '0';
- if (isdigit(*s))
- val = 10 * val + *s++ - '0';
- for (val *= 60, i = 13; 10 <= i; --i, val /= 10)
- tzbuf[i] = val % 10 + '0';
- if (isalpha(*s))
- for (i = 5; i <= 7; ++i)
- if (isalpha(*s))
- tzbuf[i] = *s++;
- else
- return (NULL);
- return (*s == '\0' ? tzbuf = NULL);
- }
-
- const char *_Getzone(void)
- { /* get time zone information */
- const char *s;
-
- if (tzone)
- ;
-
- else if ((s = getenv("TIMEZONE")) != NULL)
- { /* copy desired format */
- if ((tzone = (char *)malloc(strlen(s) + 1))
- != NULL)
- strcpy(tzone, s);
- }
- else if ((s = getenv("TZ")) != NULL)
- tzone = reformat(s);
- if (tzone == NULL)
- tzone = (char *)defzone;
- return (tzone);
- }
- /* End of File */
-
-
- Listing 8 mktime.c
- /* mktime function */
- #include <limits.h>
- #include "xtime.h"
-
- time_t (mktime)(struct tm *t)
- { /* convert local time structure
- to scalar time */
- double dsecs;
- int mon, year, ymon;
- time_t secs;
-
- ymon = t->tm_mon / 12;
- mon = t->tm_mon - ymon * 12;
- if (mon < 0)
- mon += 12, --ymon;
- if (ymon < 0 && t->tm_year < INT_MIN - ymon
- 0 < ymon && INT_MAX - ymon < t->tm_year)
- return ((time_t)(-1));
- year = t->tm_year + ymon;
- dsecs = 86400.0 * (_Daysto(year, mon) - 1)
- + 31536000.0 * year + 86400.0 * t->tm_mday;
- dsecs += 3600.0 * t->tm hour + 60.0 * t->tm_min
- + (double)t->tm-sec;
- if (dsecs < 0.0 (double)(time_t)(-1) <= dsecs)
- return ((time_t)(-1));
- secs = (time_t)dsecs - _TBIAS;
- _Ttotm(t, secs, t->tm_isdst);
- if (0 < t->tm_isdst)
- secs -= 3600;
- return (secs - _Tzoff());
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On the Networks
-
-
- What Happened -- Again?
-
-
-
-
- Sydney S. Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, author, and
- president of Datacomp Systems, Inc., a consulting and contract programming
- firm specializing in databases, data presentation and windowing, transaction
- processing, networking, testing and test suites, and device management for
- UNIX and MS-DOS. He can be contacted care of Datacomp Systems, Inc., 3837
- Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail on the
- Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
- Internet addressing).
-
-
- Once again, things have dried up in comp.sources.unix. But this time they have
- also dried up in comp.sources.x. Both groups have been extremely quiet, with
- no postings in comp.sources.unix. and only one in comp.sources.x. But what is
- unusual is that there hasn't been any word from the moderators of each group
- about any problems.
- Also different this time is that the other groups have also quieted down. This
- is unusual. Normally when one of the groups quiets down, the others overflow
- with the items that would have been posted to that group. This time even the
- catchall group, comp.sources.misc has been relatively quiet.
- I can only chalk this up to a combination of three things:
- Fewer new sources are being written for free distribution.
- The concept of FTP mirror sites and the vast number of people now able to
- access them has reduced the importance of having the software posted to a main
- stream USENET Network News group.
- CD-ROMs are being widely used as an alternative distribution medium.
- I can say that the last two are true about the Version 2.4 release of Elm. It
- was submitted to the moderators of comp.sources.unix two months ago as of this
- writing, and I haven't pressed as to why it hasn't been distributed yet
- because we now use a set of FTP mirror sites that include ones able to be
- accessed via UUCP. In addition we publish it on several of the CD-ROM
- collections.
- The single posting in comp.sources.x consisted of wscrawl from Brian Wilson
- <brianw@apple.com>. The word "wscrawl" stands for "window-scrawl." The user
- may think of wscrawl v2.0 as a paint program shared by any number of people at
- the same time. When wscrawl is run, it opens up a separate window on each
- participant's display. From that point onward, each participant sees the
- actions and ideas of every other participant as they occur. Each individual
- may simply watch, or participate at any moment. Any individual may exit out of
- the session at any time without affecting the other participants. wscrawl
- requires Motif 1.1 to compile but binaries for common architectures are
- available from 128.109.178.23 via anonymous ftp.
-
-
- Reviews Alive, Barely
-
-
- The only controlled newsgroup that had activity was comp.sources.reviewed,
- with updates to two packages and two patches.
- Mike Lijewski <lijewski@theory.tc.cornell.edu> updated his Directory Editor,
- written in C++, to version 1.8. dired was posted as Volume 2, Issues 32-37.
- dired is a directory editor modeled after Dired Mode of GNU Emacs, but
- targeted for non-emacs users and designed for the UNIX environment. In
- addition to fixing bugs, and portability problems, about two pages worth of
- new features have been added.
- Version 1.7 of cextract from Adam Bryant <adb@cs.bu.edu> was posted in Volume
- 2, Issues 38-43. cextract is a C prototype extractor. It is designed to
- generate header files for large multifile C programs, and will provide an
- automated method for generating all of the prototypes for all of the functions
- in such a program. It may also function as a rudimentary documentation
- extractor, generating a sorted list of all functions and their locations.
- Changes since the last posted version (1.2) include portability fixes, file
- name and line numbers in error messages, improved documentation, support for
- C++ // comments, proper support of the __STDC__ #if construct, and support for
- runtime selection of which C preprocessor to be used.
- On the patch front, ncompress received patch 1 in Volume 2, Issue 27. Peter
- Jannesen <peter@ncs.nl> fixed the -c flag, a utime error, and added support
- for the AMIGA. ncompress is an enhancement to the USENET de facto standard
- file-compaction program to increase its speed.
- Volume 2, Issues 28-31 were patch 2 to mawk from Mike Brennan
- <brennan@boeing.com>. This was a bug fix patch to this awk work-alike and also
- added new configuration support for AIX, Convex, and SVR4/386.
-
-
- Even misc is Updates
-
-
- Most of the postings in comp.sources.misc were also patches and updates to
- existing packages. Please don't tell me that everything good has already been
- written.
- The patches included patch 2 to oraperl v2 from Kevin Stock
- <kstock@encore.com> in Volume 32, Issue 93. oraperl is a set of extensions to
- perl to access Oracle databases. This patch handles null fields properly and a
- change to ora_ titles to allow truncating the titles to the data width or
- retrieving the full titles of the columns.
- ECU, which was highlighted in the last column, had patch 3 posted by Warren
- Tucker <wht@n4hgf.Mt-Park.GA.US> for Volume 32, Issue 96. Changes include
- addition of a -l flag to fkmap to load an alternate map from the library
- directory, changes to how seven-bit mode is reported and recognizing the ,M
- suffix in the UUCP Devices tables for all platform types.
- John F. Haugh II's <jfh@rpp386.cactus.org> shadow password suite, shadow, was
- updated to version 3.2.2 by patch 6 posted as Volume 32, Issues 98-100. The
- most significant change made for this patch is the addition of
- administrator-defined authentication mechanisms. This allows the system
- administrator to replace the standard encrypted password with a program which
- performs the authentication. Any user-written program may be used. This
- feature may be used to add any of a number of authentication schemes. Haugh
- has also started to revise the commands to work both with the shadow password
- and with the standard password file.
- The Mail Users Shell has been updated with patch 5 for version 7.2 by Bart
- Schaefer <bart@zigzag.z-code.com> in Volume 32, Issues 101-103. New in this
- version is POP Support, MIME support, expansion of variables now more closely
- resembles how the shells handle it, and checking for the UNIX From_ line even
- if MMDF is being used. Of course there are also numerous bugs fixed.
- Chip Rosenthal <chip@chinacat.unicom.com> issued a portability patch for SCO
- XENIX 2.3.3 users to his prtscrn2 screen capture program (for SCO UNIX and
- XENIX console screens). This very short patch fixes up the doc a bit and adds
- a missing define for that version of the OS. It appeared as Volume 33, Issue
- 29.
- The delete/undelete utilities for UNIX from Jonathan I. Kamens
- <jik@Athena.MIT.EDU> were updated to patchlevel 15 with Volume 33, Issue 70.
- This is a bug fix patch which corrects malloc problems, POSIX dirent support,
- AFT mount point problems, some memory allocation problems on nesting, and a
- bug in symlink support.
- Leaving the patches and turning to packages, yet another graphical directory
- tree program was contributed by Tom A. Baker <tombaker@world.std.com> for
- Volume 32, Issue 97. tbtree allows users to get a visual idea of where things
- are on their system. It was written on SunOS, and with a little work should
- port to other flavors of UNIX.
- A device driver for UNIX System V.3 to implement the poll and select system
- calls was submitted for Volume 32, Issue 105 by Bruce Momjian
- <candle!root>.poll and select are UNIX C library functions that allow programs
- to determine if a number of file descriptors are ready for reading or writing.
- In addition, his pol package includes a modified version of the public domain
- System V pty device driver written by Jens-Uwe Mager, with changes for System
- V by Michael Bloom.
- Guido Gronek <gg@trillian.tp1.ruhr-uni-bochum.de> had the need for a fast
- substring search routine. He created qsearch, an ANSI-C implementation that
- searches for the leftmost or rightmost occurrence of a pattern string in a
- text string. The algorithm used is "quick search" by D. M. Sunday, which is a
- simple but fast practical method. It's supposed to be even faster than the
- well known Boyer-Moore algorithm. (See Sunday's original paper, CACM 33.8,
- page 132 for several improvements of the basic method as well.) He implemented
- the reverse text scanning by a rather simple variation of the original
- algorithm. qsearch was posted as Volume 32, Issue 106.
- A newer derivative to an older vi (a UNIX text editor) clone package called
- stevie, was contributed by John Downey <jmd@cyclone.bt.co.uk>. Xvi was posted
- as Volume 33, Issues 10-27. While the name starts with X, there is as of yet
- no specific X-window version of the editor. Xvi is a portable multi-window
- version of vi that uses text windows separated by horizontal status lines on
- character-mode displays. The windows may represent different files being
- edited, or different views on to the same file.
- For those just learning vi, Wes Craig <wes.craig@umich.edu> contributed
- vilearn for Volume 33, Issue 35. There are five short tutorials, each a text
- file intended to be edited with vi. The first, "Basic Editing," covers the
- handful of commands required to both navigate all five tutorials and do basic
- editing. The second tutorial, "Moving Efficiently," covers all of the cursor
- positioning commands. These are the commands used later as arguments to
- editing commands. Tutorial three, "Cutting and Pasting," introduces the first
- compound commands, numbering, and copy buffers. The "Inserting Techniques"
- tutorial continues the discussion of compound commands, while completing the
- list of insertion commands first discussed in tutorial one. The final
- tutorial, "Tricks and Timesavers," is less a tutorial than a description of
- common vi commands which don't fit correctly into normal vi logic.
- An interpreter for a superset of the ANSI Standard for Minimal BASIC
- (X3.60-1978) was contributed by Ted A. Campbell <tcamp@acpub.duke.edu> for
- Volume 33, Issues 37-47. bwbasic is implemented in ANSI C, and offers a simple
- interactive environment including some shell program facilities as an
- extension of BASIC. The interpreter has been compiled successfully on a range
- of ANSI C compilers on varying platforms with no alterations to source code
- necessary.
- An update to tarmail v2.3 was contributed for Volume 33, Issue 36 by Paul Lew
- <lew@gsg.gsg.com>. tarmail and untarmail provide a reliable way to send files
- through electronic mail systems. Large files will be divided into smaller
- chunks for transmission. Unlike the previous version of tarmail, the new
- tarmail will attach a CRC checksum on an individual chunk instead of on the
- entire file(s). The ability to retransmit only the faulty chunks make tarmail
- the idea tool for sending files by electronic mail.
- Brendan Kehoe <brendan@cygnus.com> has updated his archie client in Volume 33,
- Issues 50-56. archie is a system to locate programs stored in the various
- anonymous ftp archives on the Internet. This patch takes archie to 1.4.1.
- David F. Skoll <dfs@doe.carleton.ca> has released an update to his remind
- package. Version 3.0.0 was contributed for Volume 33, Issues 58-69. remind is
- a sophisticated reminder program. It has a flexible and powerful script
- language, and allows you to easily specify reminders for most occasions
- including: particular dates (birthdays, etc.), holidays like Labor day, which
- occur on a particular weekday of a month, dates which repeat with an arbitrary
- period, meetings which are automatically moved in the event of holidays among
- others. remind also includes a feature to activate timed alarms in the
- background. remind should work on most UNIX systems, as well as MS-DOS. This
- allows you to use the same script on your UNIX and MS-DOS systems.
- A program to calculate dates in the Jewish calendar for a given gregorian year
- was contributed by Danny Sadinoff <sadinoff@unagi.cis.upenn.edu>. hebcal v1.2,
- Volume 33, Issue 71, is fairly flexible in terms of which events in the Jewish
- calendar it displays. Each of the following can be individually turned on or
- off: the Hebrew date, Jewish holidays (including Yom Ha'atzmaut and Yom
- HaShoah etc.), the weekly Sedrah, the day of the week, and the days of the
- Omer.
- Mike Lijewski <lijewski@rosserv.gsfc.nasa.gov> contributed problem v1.1, a
- database manager for bug reports and such, meant to be used in a UNIX
- environment. It is written in C++, uses the GNU Database Management Library
- (GDBM) for low-level database operations, and uses the termcap(3) library for
- screen control. The basic idea is to provide a central front-end for managing
- the various databases of bugs and miscreant behaviour that a large UNIX site
- might be interested in tracking, and facilitating the sharing of this
- information amongst all interested parties. Version 1.1 was posted in Volume
- 33, Issues 72-78.
-
-
-
- Twice, Twice
-
-
- The trend in comp.sources.games is to post things twice. It happened a bit
- this time, where the same game got posted and then updated with a newer
- version.
- A version of the popular game scrabble was contributed as scrabble2 by James
- A. Cherry <jac@doe.carleton.ca> for Volume 14, Issues 93-110 with patch1 in
- Volume 15, Issue 9. This version of scrabble is curses-based and uses a
- dictionary to allow play against one or more computer opponents. It only
- requires a text-based screen.
- A generic tetris game for X11R4/5 was submitted by Qiang Alex Zhao
- <azhao@cs.arizona.edu> for Volume 15, Issues 5 and 6. gtetris2 is a generic
- version that uses no toolkit, only Xlib, and is highly portable.
- Thomas Grennefors <etxtsg@solsta.ericsson.se> contributed xminesweeper for
- Volume 15, Issue 3. It is a game where your task is to find the hidden mines
- in a minefield. Play is with the mouse and buttons control marking where the
- mines are, or marking safe squares. One wrong move and the game is over.
- Patch1 appeared in Volume 15, Issue 10.
- Xstratego is a X-window-based stratego interface for two players submitted by
- Henk-Jan Visscher <hjvissc@cs.vu.nl> for Volume 15, Issues 11-14. You can
- either play against another player (on the same or different host) or create a
- board setup for later use. It uses the Xaw toolkit.
- Two different curses-based versions of reversi were posted. The first,
- reversi, contributed by Elias Martensson <elias@proxxi.se> was posted in
- Volume 15, Issues 7 and 8. Cursor movement is via the usual h, j, k, and l
- keys. The second, reversi2, was contributed, but not written, by Eric Safern
- <esafern@shearson.COM> and was posted in Volume 15, Issues 18 and 19. This
- version supports both human vs. computer and two human player mode.
- A restructuring of bt4, the broken throne multiplayer real-time conquest game
- was submitted by Tom Boutell <boutell@isis.cshl.org> in Volume 15, Issues
- 15-18. Added are some new features and support for AIX. Note, this game
- requires INET sockets (the BSD socket interface) as well as curses.
-
-
- Previews from alt. sources
-
-
- At least this hasn't been quiet. It sounds like only the moderated groups are
- being shunned. Here are the highlights of what I hope will reappear on the
- moderated source groups.
- Version 1.2 of uustatus, a real-time UUCP status monitor for BSD and System V
- was posted on July 17, 1992 by Ed Carp <unislc!erc> in one part. This simple
- program dynamically displays the status of your UUCP connections for you,
- without your having to cd into all of those pesky directories. It's also
- faster than uustat -m, and it's real-time! Bug fixes were posted in a one-part
- patch on August 5, 1992.
- A program to convert xwd (X-window dump) files into color or gray-scale
- PostScript was posted on August 30, 1992 in two parts by Brian Totty
- <totty@flute.cs.uiuc.edu>. Colors can be stored in an array to allow for
- mapping to be modified after the file is generated.
- Steve Cole <steve@sep.Stanford.EDU> contributed xtpanel, a program to build an
- interactive X program from the command line using a simple scripting language.
- It is not intended as a replacement for a full-featured interface-programming
- toolkit or as a replacement for a simple menu builder. It falls somewhere in
- the gap between the two. It is intended as an easy to use tool that can be
- used to add an interactive wrapper to all those old programs and shells that
- you have lying around. It was posted on September 3, 1992 in eight parts.
- The tin newsreader was updated with two patches by Iain Lea
- <Iain.Lea%anl433.uucp@Germany.EU.net> on September 14, 1992 in 15 parts, a
- complete report at patchlevel 6, and on November 15, 1992 in 10 parts, a patch
- to PL6 taking it to PL7. Many new features have been added to the threader
- news reader including CD-ROM support, INN support, support for many more OS
- types and of course more bug fixes.
- If you find your cursor on the X display is always in the way, unclutter,
- posted in one part on September 28, 1992 by Charles Hannum
- <mycroft@hal.gnu.ai.mit.edu> will help. unclutter is a program which runs
- permanently in the background of an X11 session. It checks on the X11 pointer
- (cursor) position every few seconds, and when it finds it has not moved (and
- no buttons are pressed on the mouse, and the cursor is not in the root window)
- it creates a small sub-window as a child of the window the cursor is in. The
- new window installs a cursor of size 1x1 but a mask of all 0, i.e. an
- invisible cursor. This allows you to see all the text in an xterm or xedit,
- for example. The human-factors crowd would agree it should make things less
- distracting. Once created, the program waits for the pointer to leave the
- window and then destroys it, restoring the original situation and thereby
- redisplaying the cursor.
- Last is the beta release of version 3.0 of Steven Grimm's
- <koreth@hyperion.com> workman v3.0. Posted on November 16, 1992 in six parts,
- its an X CD player program. It requires XView 3.0 or higher (XView source is
- available with the X11R5 distribution) and runs under both SunOS 4.x and
- Solaris 2. The major new feature is that tracks may now be split into sections
- at arbitrary locations. You may split a track while the CD is playing, useful
- for marking off particular sections of a song. Sections may be named and
- selected just like tracks. Of course, if the CD-ROM drive doesn't support data
- across the SCSI bus (and Sun's doesn't) you must use external speakers or a
- patch cable to take the analog sound from the CD-ROM.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Macros and Debugging
-
-
-
-
- Ken Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++
- language courses for corporations. He is the author of C Language for
- Programmers and All On C, and was a member on the ANSI C committee. He also
- does custom C programming for communications, graphics, image databases, and
- hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707.
- You may fax questions for Ken to (919) 489-5239. Ken also receives email at
- kpugh@dukemvs.ac.duke.edu (Internet) and on Compuserve 70125,1142.
-
-
- Q
- It seems sort of ironic that the issue devoted to debugging contains an
- example of using macros to solve the "Fixed Field Files." (This arrangement
- produces a set of macro invocations.) The article, "Glass-Box Testing" hints
- at how to use debugging to break code and verify coverage. Unfortunately
- macros can't be debugged. After reviewing, understanding, and maintaining the
- code in various products, I've come to the conclusion that C programmers abuse
- the macros and the preprocessor in general.
- I am a big convert to C++ because the macro preprocessor hardly needs to be
- used. With true consts and inline functions there is no need to write nasty
- macros. The following code is written in C-style
- #define MAX_LINE 80
- getline( char ** line )
- {
- static char myline[MAX_LINE];
- }
- Compare it to
- const int MAX_LINE=80;
- getline( char ** line )
- {
- static char myline[MAX_LINE];
- }
- which is written in C++-style.
- Now with a debugger you can print MAX_LINE without saying
- find /usr/project/x400 -name *.h -name *.c \
- -exec grep '#define MAX_LINE'
- to search through 200 header files to find the correct macro definition.
- As for C++ I'm just praying that programmers will not go overboard and use
- overloaded functions in C++ too much. Because of the inheritance/dynamic
- binding we can have multiple overloaded function(s) in each class, and the
- superclass (base class) can have those same overloaded classes defined. I
- guess it would look something like Listing 1.
- What's SIZE? The problem gets worse as the program gets larger. I was working
- on debugging my project and reading the C Users Journal in between the
- compiles. I hope in the future programmers will recognize that in the world of
- "reuse" that readability must come before performance and space
- considerations.
- I know a programmer who does the following because he's too lazy to write the
- whole line:
- #define ptr
- a.get.line.alpha.beta ptr->data
- Now when I try and print ptr, the debugger says, ptr not defined. I guess you
- probably get the message. By the way. David Staker's book, C: Style Standard &
- Guidelines is quite informative.
- David A. Dennerline
- A
- I definitely agree with you that C++ can help a programmer write better code,
- if it is utilized properly. But basic design rules need to be followed,
- especially in using meaningful names.
- I cannot emphasize too much that when one writes a program, it should be
- written in English or in whatever native language code set is supported by
- one's compiler. Meaningful variable, member, parameter, and function names go
- a long way to writing maintainable code. Making too many assumptions about
- what the reader knows can result in programs that are difficult to understand.
- The member function names are particularly important in C++ as one is not
- supposed to look at the implementation to determine what the function is
- doing.
- Let me take your example functions, which were created for example purposes,
- rather than as actual code. Looking at the parameter names gives no clue as to
- what is really required.
- virtual int get_size( char * s );
- virtual int get_size( int idx );
- In the first case, the question is whether s is the name of the car or the
- model of the car or a combination of both. Whichever it is should be
- demonstrated by the name, as
- virtual int get_size( char * model_name);
- In the second case, the parameter name is not an English word. It ought to be
- replaced by a real word, such as "index." I do not think that even that is
- enough, as some indication should be given as to what the index is used for.
- The question is whether the index is based on passenger compartment size,
- horsepower, consumer rating, or something else.
- virtual int get_size( int index_of_consumer_rating);
- We are still left with two overloaded functions. I would eliminate the
- overloading by modifying the name of each member function. In addition, there
- is no indication in the name of what size is really being gotten. So the
- function might really be called
- virtual int get_engine_size_by_model_name
- ( char * model_name);
- virtual int get_engine_size_by_consumer_rating
- (int index_of_consumer_rating);
- Then the code might read
- get_engine_size_by_consumer_rating(SIZE);
-
- Now the name SIZE looks rather funny here. That's because it is not long
- enough to describe what it really is. If you are just the class provider, you
- do not have any control over the names by which your classes will be invoked.
- At least you have done your best to provide the means to write readable code.
- One objection to requiring meaningful names is that the source code is longer.
- Sometimes it will take two lines to write an expression that could have fit
- onto one. My response is that statements are like sentences. Look at the last
- letter or article that you wrote. See how many sentences fit onto a single
- line.
- Another objection is that the names can be too long and can exceed the
- 31-character limitation of significance. The words in the name do not have to
- all be in the dictionary. Abbreviations are okay, especially if they appear in
- an industry dictionary or glossary. Proprietary abbreviations are okay, if the
- company has a standard glossary. Abbreviations that are local to your programs
- should be avoided, unless they are consistently applied and a glossary is
- included. Every time you use your own abbreviation, you add an information
- element that has to be absorbed by the reader.
- I remember reading David Copperfield in my youth. It was a bit advanced for me
- at the time, so I had it in one hand with a dictionary in the other. Every
- time I came across an unknown word, I looked it up in the dictionary. You can
- imagine how that affected following the plot. Using too many of your own
- abbreviations can make the reader miss the flow of the code.
- Let me point out that overloading constructors is almost unavoidable, since
- there is only one name for the constructor. However, as pointed out in Tom
- Cargill's C++ Programming Style, many overloaded constructors can be
- eliminated by specifying default arguments in the parameter list for the
- constructor.
- Your other instance of bad programming shows that the designer did not
- properly modularize the code in C. One has to learn to do this in C, as that
- is a design technique, not a feature of the language. C++ makes it easier to
- design modular code as many of the bookkeeping aspects are taken care of, but
- it does not demand it.
- My basic rule of thumb for structure access is that anytime you use more than
- one member operator, you should examine how to redesign your code. If you use
- more than two, you should start the redesign immediately. A program that
- quires a reference as
- a.get.line.alpha.beta
- is going to be difficult to maintain.
-
-
- External Declarations
-
-
- Q
- I had a long debugging session recently when I ran across the following
- problem (see Listing 2). I've cut it down to the essentials. The code appears
- all in the same source file. The value of cnt is incremented by both
- functions. The correct code for the second function should have been:
- static int cnt1;
- ...
- cnt1++;
- printf("cnt is %d", cnt1);
- This code was only a small portion of a large file with lots of small
- functions, so it was not readily apparent that it was incorrect. However I
- would have thought the compiler would have told me that cnt was declared
- twice. No error message was generated, so the bad code was hard to find. What
- gives?
- Sam Mellon Boston, MA
- A
- You have been exposed to the new semantics of external declarations in ANSI C.
- This is a major change from K&R C, but it is not noted as such in many texts.
- Let me review how external variables are linked together, as well as the
- differences in K&R and ANSI.
- In this explanation of externals, K&R refers to the Appendix A in the
- Kernighan & Ritchie The C Programming Language Book. Some compilers
- implemented externals slightly differently.
- The linker matches external references (REFs) to external definitions (DEFs).
- There are REFs and DEFs for both functions and data, though they are generated
- slightly differently. Take functions first. Their operation is the same in
- both K&R and ANSI.
- Each call to a function in your code generates a REF for that function. The
- code for the function itself is the DEF for the function. For example, this
- code segment:
- void calling_function(void)
- {
- called_function();
- ..
- }
- generates a DEF for calling_function and a REF for called_function. You can
- have one and only one DEF for a function in all the files that are linked
- together. Of course, you can and probably will have lots of REFs for a
- function, but you don't even have to have any.
- You may have gotten messages from the linker regarding duplicate definitions
- or the failure to provide a definition. Depending on the vendor, it may or may
- not allow you to create an executable file if those errors occur.
- External REFs and DEFs for data work slightly differently. Under K&R C
- (Appendix A), the same rules applied as to having one and only one DEF and
- allowing zero or more REFs. A DEF was generated by a variable declaration
- outside of any function, such as:
- int global_i;
- calling_function ()
- {
- global_i = 5;
- ...
- }
- This declaration could appear in one and only one source file.
- Optionally, you could initialize the variable as:
- int global_i = 5;
- and the initial value would be set to 5. If you did not specify an
- initializer, its value would default to 0. To generate a REF, the declaration
- with the keyword extern was used:
- extern int global_i;
- another_calling_function()
- {
- global_i = 7;
- ...
- }
- It was permissible to have both a DEF and a REF in the same source file, such
- as:
- int global_i = 5;
- extern int global_i;
- This would not normally be done directly, but indirectly, as the REF or a set
- of REFs would be placed into a header file, such as:
- "external.h"
-
- extern int global_i;
- /* Other extern declarations */
-
- The DEFs could be in individual files or a single file. The header file would
- be included in this file, such as:
- #include "external.h"
- int global_i = 5;
- If the declaration of the DEF does not match the declaration of the REF, the
- compiler generates an error.
- Although K&R was pretty straightforward on how to handle DEFs and REFs,
- vendors developed their own styles, some of which were adapted from other
- languages. I won't go into all the possibilities. ANSI C adopted an
- amalgamation of the styles. It also added the concept of tentative DEF. The
- first declaration of an external without an explicit initializer is considered
- to be a tentative DEF.
- All similar declarations that follow in the same file are also tentative DEFs.
- For example, suppose your file contained:
- int global_i; /* Tentative DEF */
- ...
- int global_i; /* Tentative DEF */
- These are not double definitions of global_i, but simply a set of tentative
- definitions of the same variable. One tentative DEF will be turned into a true
- DEF by the compiler.
- Now if you declare an initializer in the declaration, it becomes a true DEF.
- The compiler will accept one true DEF and ignore all the other tentative DEFs.
- int global_i = 5; /* True DEF */
- ...
- int global_i; /* Tentative DEF - refers to previous*/
- The compiler will complain if two true DEFs appear in the same source file,
- as:
- int global_i = 5; /* True DEF */
- int global_i = 6; /* Compiler error - double DEF */
- Just as with K&R, you can only have a single true DEF in all the linked source
- files. The linker should complain if two true DEFs appear in multiple linked
- source files. However, the way many compilers/linkers work extends the concept
- of tentative DEFs to multiple source files. A tentative DEF in a source file
- continues to act as a tentative DEF, rather than being turned into a true DEF.
- If there is no true DEF in any source file, then the linker turns a tentative
- DEF into a true DEF with an initializer of zero. The linker will accept one
- true DEF and ignore all the other tentative DEFs.
- If you include the keyword static, the scope of the variable is only within
- the source file. However the rules for tentative DEFs still apply:
- static int cnt; /* Tentative DEF */
- static int cnt; /* Tentative DEF */
- Both tentative DEF's refer to the same variable. This is why your code had a
- problem. However, it is acceptable ANSI C, so the compiler did not complain.
- If you used initializations, you should get an error, such as:
- static int cnt = 0; /* True DEF */
- static int cnt = 0; /* 2nd True DEF - compiler
- error */
- If you had simply initialized one, the other would have been treated as a
- tentative DEF for the same variable:
- static int cnt = 0; /* True DEF */
- static int cnt; /* Tentative DEF */
- To complete the external puzzle, the keyword extern has been altered from K&R.
- It now can be used with an initializer, as:
- extern int global_i = 5; /* True DEF */
- extern int global_i; /* REF */
- If used without an initializer, it still means REF.
- I should note that the concept of tentative DEF's does not exist in C++. A set
- of declarations as:
- int global_i;
- int global_i;
- is a compiler error due to the double definition.
- I've covered my guidelines for using global variables long ago. It can be
- summed up in a word--Don't. However, if you must, I suggested the style that
- follows that as shown for K&R above. Use extern declarations (REF's) in a
- header file that gets included in every source file. Then in a single file
- initialize all the externals. For static externals, place all declarations
- together near the top of the source file.
- P.J. Plauger long ago pioneered a similar style with Whitesmith's C, using the
- new ANSI standard for externals. Initialized declarations (extern int i = 1;)
- in one file are marked true DEF's. Uninitialized declarations (extern int i;)
- in a header file were used as REF's.
-
-
- Social Security Feedback
-
-
- I received a note from Bruce Bogert regarding the conventions on Social
- Security numbers. The first three digits are assigned on a state by state
- basis. For example 212 through 220 are for Maryland. The next two digits may
- be used for individual divisions within a state and the last four digits are
- sequentially assigned. If you do need to make up a Social Security number for
- some outfit that requires it but is not legally obligated to have it, you
- might want to know that numbers beginning with 900 to 999 are not used for the
- most part. (KP)
-
- Listing 1 A C++ base class with overloaded functions defined
- class Car
- {
- public:
- virtual int get_size( char * s );
- virtual int get_size( int idx );
- }
-
- class SportsCar :Car
- {
- public:
- virtual int get_size( char * s );
- virtual int get_size( int idx );
-
- }
-
- SportsCar sportsCar;
- main()
- {
- sportsCar.get_size( SIZE );
- }
- /* End of File */
-
-
- Listing 2 The value of cnt is incremented by both functions.
- static int cnt;
- void test()
- {
- cnt++;
- printf("Cnt is %d\n", cnt);
- }
-
- static int cnt;
-
- void test1()
- {
- cnt++;
- printf("cnt is %d", cnt);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Code Capsules
-
-
- A C++ Date Class, Part 1
-
-
-
-
- Chuck Allison
-
-
- Chuck Allison is a software architect for the Family History Department of the
- Church of Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake
- City. He has a B.S. and M.S. in mathematics, has been programming since 1975,
- and has been teaching and developing in C since 1984. His current interest is
- object-oriented technology and education. He is a member of X3J16, the ANSI C
- ++ Standards Committee. Chuck can be reached on the Internet at
- allison@decus.org, or at (801)240-4510.
-
-
- Last month's column introduced a function, date_interval, that calculates the
- number of years, months, and days between two arbitrary dates. This month's
- column presents a C++ solution to the same problem. The essence of this
- approach is to create a new data type, which behaves much the same as built-in
- types do. In other words, you shift from a function-based approach ("How do I
- want to do things?") to an object-based one ("What are the elements, the
- objects, of my problem?"). Using C++ effectively requires a different way of
- thinking about problem solving. To make that shift, it helps to know why C++
- exists in the first place.
-
-
- A Tale of Two Languages
-
-
- C++ had its beginnings at AT&T in the early 1980's with Bjarne Stroustrup's "C
- with Classes". He was seeking a way to speed up simulations written in
- Simula-67. "Class" is the Simula term for a user-defined type, and being able
- to define objects that mirror reality is a key to good simulations. What
- better way to get fast simulations than to add classes to C, the fastest
- procedural language?
- Choosing C not only provided an efficient vehicle for classes, but a portable
- one as well. Although other languages supported data abstraction through
- classes long before C++, it is now the most widespread. Almost every major
- platform that has a C compiler also supports C++. The last I heard, the C++
- user base was doubling every seven months.
- A first look at C++ can be overwhelming. If you're coming from C, you need to
- add the following (and then some) to your vocabulary:abstract class, access
- specifier, base class, catch clause, class, class scope, constructor, copy
- constructor, default argument, default constructor, delete operator, derived
- class, destructor, exception, exception handler, friend, inheritance, inline
- function, manipulator, member function, multiple inheritance, nested class,
- new handler, new operator, overloading, pointer to member, polymorphism,
- private, protected, public, pure virtual function, reference, static member,
- stream, template, this pointer, try block, type-safe linkage, virtual base
- class, virtual function.
- The good news is that C++ is a powerful, efficient, object-oriented language
- able to handle complex applications. The bad news is that the language itself
- must therefore be somewhat complex, and is more difficult to master than C.
- And C itself is part of the problem. C++ is a hybrid, a blending of
- object-oriented features with a popular systems programming language. It is
- impossible to introduce such a rich set of new features without the host
- language having to bend a little. Yet compatibility with C is a major goal of
- the design of C++. As Bjarne stated in his keynote address to the ANSI C++
- committee, C++ is an "engineering compromise," and must be kept "as close as
- possible to C, but no closer." How close is still being determined.
- But there is more good news.
-
-
- An Incremental Journey
-
-
- You can use C++ effectively without having to master all of it. In fact,
- object-oriented technology promises that if vendors do their job (providing
- well-designed class libraries, crafted for reuse and extensibility), then your
- job of building applications will be easier. Current products, such as
- Borland's Application Frameworks, are proving this to be true in many cases.
- If you feel you must master the language, you can do it in steps, and still be
- productive on the way. Three plateaus have emerged:
- 1. A Better C
- 2. Data Abstraction
- 3. Object-oriented programming
- You can use C++ as a better C because it is safer and more expressive than C.
- Features on this plateau include type-safe linkage, mandatory function
- prototypes, inline functions, the const qualifier (yes, ANSI C borrowed it
- from C++), function overloading, default arguments, references, and direct
- language support for dynamic memory management. You will also need to be aware
- of the incompatibilities that exist between the two languages. There is a
- robust subset common to both which Plum and Saks call "Type-safe C" (see C++
- Programming Guidelines, Plum and Saks, Plum-Hall, 1992).
- As I will illustrate in this article and the next, C++ supports data
- abstraction--user-defined types that behave essentially as built-in types. The
- data abstraction mechanisms are classes, access specifiers, constructors and
- destructors, operator over-loading, templates, and exceptions.
- Object-oriented programming takes data abstraction a step further by making
- the relationships between classes explicit. The two key concepts are
- inheritance (defining a new class by stating how it is the same and how it is
- different from another, reusing the sameness) and polymorphism (providing a
- single interface to a family of related operations, transparently resolved at
- runtime). C+ + supports inheritance and polymorphism through class derivation
- and virtual functions, respectively.
-
-
- Classes
-
-
- A class is just an extended struct. In addition to data members, you define
- member functions that act upon objects of the class. The definition of the
- Date class is in the file date.h in Listing 1. It differs from last month's C
- version because interval is a member function instead of a global function.
- The implementation of Date::interval() is in Listing 2. The double colon is
- called the scope resolution operator. It tells the compiler that interval is a
- member of the Date class. The ampersand in the prototype for Date::interval()
- indicates that its parameter is passed by reference. (See the sidebar on
- references.) The program in Listing 3 shows how to use the Date class. You
- must use structure member syntax to call Date:: interval():
- result = d1.interval (d2);
- The structure tag Date serves as a type specifier, just like built-in types do
- (i.e., you can define a Date object without using the struct keyword). There
- is never a need to define
- typedef struct Date Date;
- In fact, the concept of class is so fundamental that C++ has merged the
- separate name space for structure tags that exists in C with that of ordinary
- identifiers.
- Note that I have defined isleap as an inline function in Listing 2 (it was a
- macro in the C version). Inline functions get expanded "in-line" like macros
- do, but also perform all the scope and type checking that normal functions do.
- Unless you need to use the stringizing or token-pasting operations of the
- preprocessor, you don't need function-like macros in C++.
- Now consider the statement
- years = d2.year - year;
- in Listing 2. What object does year refer to? In the C version, this statement
- appeared as
- years = d2.year - d1.year;
- Since a member function is always called in association with an object (e.g.,
- d1. interval (d2)), then un-prefixed occurrences of data members naturally
- refer those of the associated object (in this case, year refers to d1.year).
- The this keyword represents a pointer to the underlying object, so I could
- make the more explicit statement:
- years = d2.year - this->year
- but this is rarely done.
- In Listing 4 I have added the following statements to the class definition:
-
- Date();
- Date(int,int,int);
- These are special member functions called constructors. Constructors allow you
- to specify how to initialize an object when it is created. The first, called
- the default constructor (because it takes no arguments), is used when you
- define a date object without any initializers:
- Date d;
- The following statement invokes the second:
- Date d(10,1,51);
- When the implementation of member functions is trivial, you can make them
- inline by moving the implementation inside the class definition itself. (See
- Listing 7--don't forget to mentally remove them from Listing 5.) The test
- program in Listing 6 puts off constructing the objects d1, d2, and result
- until they are needed. (Object definitions can appear anywhere a statement can
- in C++.)
- I have almost illustrated the key feature of data abstraction, namely
- encapsulation. Encapsulation occurs when a userdefined type has a well-defined
- boundary between its internal representation and its public interface. To
- truly say that I have created a new type that acts like a built-in type, I
- must disallow any inadvertent access to its internal representation. For
- example, at this point, the user could execute a statement such as
- d1.month = 20;
- A well-behaved object controls the access to its data members. In a practical
- date class, I want to allow the user to query the month, day, and year, but
- not to set them directly, so I make them private, and provide accessor
- functions to retrieve their values (see Listing 8). Since having private
- members is the more common situation, I usually replace the struct keyword
- with the keyword class, whose members are private by default (see Listing 9).
- Accessor functions like get_month don't alter the private part of a Date
- object, so I declare them as const member functions. (Date::interval() is also
- const--don't forget to add const to its definition in the implementation file
- date3.cpp also.) I must now replace references to data members with calls to
- the accessor functions in tdate3.cpp (see Listing 10).
- We are only about half way to a complete C++-style approach to a date class.
- Next month we will incorporate stream I/O, static members, and operator
- overloading.
- References in C++
- A reference in C++ is an alias for another object. It can appear wherever the
- object it refers to can. The following program uses the reference iref as a
- substitute for i:
- /* ref1.c: Illustrate references */
- #include <stdio.h>
-
- main()
- {
- int i = 10;
- int &iref = i; // An alias for i
-
- printf("%d\n",iref);
- iref = 20;
- printf("%d\n",i);
- return 0;
- }
-
- /* Output:
- 10
- 20
- */
- You can think of a reference as a "smart" pointer, since it refers to another
- object without requiring explicit addressing and dereferencing like pointers
- do:
- /* ptr.c: Do the same thing with pointers */
- #include <stdio.h>
-
- main()
- {
- int i = 10;
- int *iref = &i;
-
- printf("%d\n" ,*iref);
- *iref = 20;
- printf("%d\n",i);
- return 0;
- }
- The major differences between a pointer and a reference are:
- You must initialize a reference with the object it refers to. A declaration
- such as
- int &iref;
- is meaningless (except as a function parameter). Once initialized, you can't
- modify a reference to refer to a different object. Since a reference always
- refers to something, you don't need to check for NULL as you do with pointers.
- References neither require nor allow the use of the & or * operators. All
- addressing and dereferencing are automatic. You can think of a reference as a
- const pointer that is derefenced each time it is used.
- Like pointers, however, references can be function return values. Since a
- reference is by definition an lvalue, this allows the unusual practice of
- placing a function call on the left-hand side of an assignment:
- /* ref2.cpp: Returning a reference */
- #include <stdio.h>
-
- int & current(); // Returns a reference
-
- int a[4] = {0,1,2,3};
- int index = 0;
-
-
- main()
- {
- current() = 10;
- index = 3;
- current() = 20;
- for (int i = 0; i < 4; ++i)
- printf("%d ",a[i]);
- putchar('\n');
- return 0;
- }
-
- int & current()
- {
- return a[index];
- }
-
- /* Output:
- 10 1 2 20
- */
- Another use for references is to implement pass-by-reference semantics,
- meaning that changes to function parameters persist in the calling procedure
- after the called function returns. You can do this with pointers, but
- references are cleaner:
- /* ref3.cpp: Swap via references */
- #include <stdio.h>
-
- void swap(int &, int &);
-
- main()
- {
- int i = 1, j = 2;
-
- swap(i,j);
- printf("i == %d, j == %d\n",i,j);
- return 0;
- }
-
- void swap(int &x, int &y)
- {
- int temp = x;
- x = y;
- y = temp;
- }
-
- /* Output:
- i==2, j == 1
- */
- Even if you aren't going to modify a function parameter, it is a good idea to
- pass large objects by reference for efficiency. For example, if the data type
- X is large:
- struct X
- {
- // lotsa stuff
- };
- then a function f that takes a parameter of type X, but doesn't modify it,
- should have a prototype similar to
- void f(const X&);
- For a more complete treatment of references, see Dan Saks' column in the
- September 1991 issue ("Reference Types", CUJ Vol. 9, No. 9).
-
- Listing 1 Member definitions for the Date class
- // date.h: A simple date class
-
- struct Date
- {
- int month;
-
- int day;
- int year;
-
- Date * interval(const Date&);
- };
-
- /* End of File */
-
-
- LISTING 2 Implementation of Date::interval()
- //date.cpp: Implement the Date class
-
- #include "date.h"
-
- inline int isleap(int y)
- {return y%4 == 0 && y%100 != 0 y%400 == 0;}
-
- static int Dtab[2][13] =
- {
- {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
- };
-
- Date * Date::interval(const Date& d2)
- {
- static Date result;
- int months, days, years, prev_month;
-
- // Compute the interval - assume d1 precedes d2
- years = d2.year - year;
- months = d2.month - month;
- days = d2.day - day;
-
- // Do obvious corrections (days before months!)
- //
- // This is a loop in case the previous month is
- // February, and days < -28.
- prev_month = d2.month - 1;
- while (days < 0)
- {
- // Borrow from the previous month
- if (prev_month == 0)
- prev_month = 12;
- --months;
- days += Dtab[isleap(d2.year)][prev_month--];
- }
-
- if {months < 0)
- {
- // Borrow from the previous year
- --years;
- months += 12;
- }
-
- /* Prepare output */
- result.month = months;
- result.day = days;
- result.year = years;
- return &result;
-
- }
-
- /* End of File */
-
-
- Listing 3 A program that shows how to use the Date class
- // tdate.cpp: Test the Date class
-
- #include <stdio.h>
- #include <stdlib.h>
- #include "date.h"
-
- main()
- {
- Date d1, d2, *result;
- int nargs;
-
- // Read in two dates - assume 1st precedes 2nd
- fputs("Enter a date, MM/DD/YY> ",stderr);
- nargs = scanf("%d/%d/%d%*c", &d1.month,
- &d1.day, &d1.year);
- if (nargs != 3)
- return EXIT_FAILURE;
-
- fputs("Enter a later date, MM/DD/YY> ",stderr);
- nargs = scanf("%d/%d/%d%*c", &d2.month,
- &d2.day, &d2.year);
- if (nargs != 3)
- return EXIT_FAILURE;
-
- // Compute interval in years, months, and days
- result = d1.interval(d2);
- printf("years: %d, months: %d, days: %d\n",
- result->year, result->month, result->day);
- return EXIT_SUCCESS;
- }
-
- /* Sample Execution:
- Enter a date, MM/DD/YY> 10/1/51
- Enter a later date, MM/DD/YY> 11/14/92
- years: 41, months: 1, days: 13
- */
-
- /* End of File */
-
-
- Listing 4 Definitions for a variation of the Date class shown in Listing 5
- // date2.h
-
- struct Date
- {
- int month;
- int day;
- int year;
-
- // Constructors
- Date();
- Date(int, int, int);
-
-
- Date * interval (const Date&);
- };
-
- /* End of File */
-
-
- Listing 5 A variation on the Date class
- // date2.cpp
-
- #include "date2.h"
-
- inline int isleap(int y)
- {return y%4 == O && y%100 != 0 y%400 == 0;}
-
- static int Dtab [2][13] =
- {
- {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
- };
-
- Date * Date::interval (const Date& d2)
- {
- (same as in Listing 2)
- }
-
- Date::Date()
- {
- month = day = year = 0;
- }
-
- Date::Date(int m, int d, int y)
- {
- month = m;
- day = d;
- year = y;
- }
-
- /* End of File */
-
-
- Listing 6 A program for testing the Date class
- // tdate2.cpp
-
- #include <stdio.h>
- #include <stdlib.h>
- #include "date2.h"
-
- main()
- {
- int m, d, y, nargs;
-
- // Read in two dates - assume 1st precedes 2nd
- fputs("Enter a date, MM/DD/YY> ",stderr);
- nargs = scanf("%d/%d/%d%*c", &m,&d,&y);
- if (nargs != 3)
- return EXIT_FAILURE;
- Date d1(m,d,y);
-
- fputs("Enter a later date, MM/DD/YY> ",stderr);
-
- nargs = scanf("%d/%d/%d%*c", &m,&d,&y);
- if (nargs != 3)
- return EXIT_FAILURE;
- Date d2(m,d,y);
-
- // Compute interval in years, months, and days
- Date *result = d1.interval(d2);
- printf("years: %d, months: %d, days: %d\n",
- result->year, result->month, result->day);
- return EXIT_SUCCESS;
- }
- /* End of File */
-
-
- Listing 7 Moving implementation of member functions into the class definition
- // date2.h
-
- struct Date
- {
- int month;
- int day;
- int year;
-
- // Constructors
- Date()
- {month = day = year = 0;}
- Date(int m, int d, int y)
- {month = m; day = d; year = y;}
-
- Date * interval(const Date&);
- };
-
- /* End of File */
-
-
- Listing 8 Definitions for a class that uses accessor functions to retrieve
- values
- // date3.h
-
- struct Date
- {
- private:
- int month;
- int day;
- int year;
-
- public:
- // Constructors
- Date()
- {month = day = year = 0;}
- Date(int m, int d, int y)
- {month = m; day = d; year = y;}
-
- // Accessor Functions
- int get_month() const
- {return month;}
- int get_day() const
- {return day;}
- int get_year() const
- {return year;}
-
-
- Date * interval(const Date&) const;
- };
-
- /* End of File */
-
-
- Listing 9 Definitions for a class that replaces struct with class
- // date3.h
-
- class Date
- {
- int month;
- int day;
- int year;
-
- public:
- // Constructors
- Date()
- {month = day = year = 0;}
- Date(int m, int d, int y)
- {month = m; day = d; year = y;}
-
- // Accessor Functions
- int get_month() const
- {return month;}
- int get_day() const
- {return day;}
- int get_year() const
- {return year;}
-
- Date * interval(const Date&) const;
- };
-
- /* End of File */
-
-
- Listing 10 A test for version 3 of the Date class
- // tdate3. cpp
- #include <stdio.h>
- #include <stdlib.h>
- #include "date3.h"
- main()
- {
- int m, d, y, nargs;
-
- // Read in two dates - assume 1st precedes 2nd
- fputs("Enter a date, MM/DD/YY> ",stderr);
- nargs = scanf("%d/%d/%d%*c", &m,&d,&y);
- if (nargs != 3)
- return EXIT_FAILURE;
- Date d1(m,d,y);
-
- fputs("Enter a later date, MM/DD/YY> ",stderr);
- nargs = scanf("%d/%d/%d%*c", &m,&d,&y);
- if (nargs != 3)
- return EXIT_FAILURE;
- Date d2(m,d,y);
-
-
- // Compute interval in years, months, and days
- Date *result = d1.interval(d2);
- printf("years: %d, months: %d, days: %d\n",
- result->get_year (),
- result->get_month (),
- result->get_day ());
- return EXIT_SUCCESS;
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- C++ Components and Algorithms
-
-
- Steve Halladay
-
-
- Steve Halladay owns Creative C Corp. in Louisville, CO. He has over 10 years
- experience developing products like CSIM and Multi-C in C and C++. Steve
- received his MS in Computer Science in 1982 from Brigham Young University. You
- can reach Steve at Creative C Corp. (303) 673-6683.
-
-
- C++ Components and Algorithms is not just another text on how to use C++. This
- book focuses on a few useful software components and shows how to implement
- them in C++. In addition, Scott Ladd provides the kind of insight associated
- with experienced software developers. Ladd expects the reader to have a
- working knowledge of C++.
-
-
- Organization and Contents
-
-
- C++ Components and Algorithms includes C++ source code that shows the
- implementation of strings, arrays, sorting algorithms, statistical functions,
- persistent objects, hash tables, binary trees, and B-trees. The 779 pages of
- the book are divided into four major sections entitled "Groundwork," "Arrays,"
- "Indexing, Hashing and Filing," and "Appendices."
- In his first section, entitled "Groundwork", Ladd describes the philosophy
- that guides the development of the objects he presents. As he points out, it
- is important to understand the developer's general philosophy to fully
- understand the software. Ladd briefly describes how object-oriented software
- differs from traditional software and defines some of its associated
- terminology. Ladd also takes time to describe some pitfalls associated with
- "trendy" C++ such as the additional opportunity the language gives you to make
- mistakes.
- In this first section Ladd also introduces some basic types such as booleans,
- error reporters, random-number generators, and strings. Ladd mentions that the
- string class he presents in the third chapter is very similar to string
- classes he has presented in previous books, but notes that he has included the
- string class in this book for completeness. This is certainly useful for those
- who do not own his previous books.
- The second section of the book, entitled "Arrays," introduces a couple
- different types of arrays. The inherent implementation of arrays in C and C++
- gives programmers enough rope with which to hang themselves. Ladd shows how to
- build an array class that helps programmers avoid common array pitfalls by
- supplying enhancements like index bounds checking. The array types he
- describes include integer and floating-point arrays. Ladd avoids the use of
- templates for his implementation of arrays because many current compilers have
- template implementation deficiencies. But since Ladd gives both integer and
- floating-point examples of arrays, it is easy to see how to create additional
- types of arrays using his base classes.
- In the section on arrays, Ladd also takes advantage of array constructs to
- discuss some related topics. For example, Ladd presents one chapter on sorting
- in which he describes the quicksoft algorithm and shows how to implement it in
- C++. He also has a chapter about statistics in which he discusses some basic
- statistical concepts like mean, variance, and correlation. Ladd shows how both
- sorting and statistics relate to his array classes.
- The third section of the book, entitled "Indexing, Hashing and Filing,"
- discusses some basic database concepts. Ladd starts the third section by
- talking about persistent objects. He shows how to store and retrieve objects
- with files. The third section also discusses hash tables and shows how to use
- them as an indexing mechanism for a simple database. Ladd ends the third
- section of the book by discussing tree structures as database indexing
- mechanisms. In the chapters on tree structures, Ladd shows a binary tree and a
- B-tree implementation. He also explains the strengths and weaknesses of each
- structure. Ladd's implementation of the B-tree is thorough enough that he
- includes code to perform not only key insertion and searching, but also key
- deletion.
- The fourth section of the book is an appendix that includes the complete
- source code for all the classes presented in the earlier parts of the book.
- The appendix is 279 pages of listings. The book comes with an accompanying
- disk that also contains the complete source listings as contained in the
- appendix.
-
-
- Commentary
-
-
- C++ Components and Algorithms is Ladd's third book. His comfortable writing
- style attests to his experience as a writer. The explanations and examples are
- clear and concise. Most of the book is written in the first person as though
- you were looking over his shoulder as he guides you through his code.
- This book is not for the passive reader. Ladd includes lots of source code.
- The average page probably has more lines of source code than accompanying
- textual explanation. However, the code is commented where necessary and easy
- to understand. If you like to see examples of good clean code, this book is
- full of them.
- Ladd's source code also is reasonably compiler and system independent. There
- is nothing more frustrating than getting a book of programming examples only
- to find that they will not work on your system. The only system specific code
- handles the different PC compilers' file opening command modes.
- You might think that a book with so much source code is bound to have many
- errors. On the contrary, Ladd seems to have taken meticulous care to make sure
- his code compiles under at least Borland's and Microsoft's compilers. The very
- few errors I noticed in the book were not in the source code at all.
- The most interesting part of the book to me was Ladd's use of sound
- development techniques throughout the book. Ladd's class designs are elegant
- and simple. He avoids the convoluted inheritance hierarchies that are often
- prevalent in many current C++ programming examples. He also uses a fundamental
- programming style that avoids the many confusing extensions that C++ offers.
- But Ladd's avoidance of the exotic features of C++ does not prevent him from
- showing how to use the major valuable aspects of the language. It was
- refreshing to see code that did not try to impress you with its extreme use of
- the language.
-
-
- Conclusion
-
-
- C++ Components and Algorithms fills a niche for the intermediate software
- developer. The book focuses on useful, sound implementations of data
- structures and algorithms in C++. Ladd's down-to-earth approach and clear
- explanations make both his descriptions and examples useful to students of C++
- and software engineering.
- Title: C++ Components and Algorithms
- Author: Scott Robert Ladd
- Publisher: M&T Books, 411 Borel Avenue, San Mateo, CA 94402
- Price: $39.95 (disk included)
- ISBN: 1-55851-227-6
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- Here's an update on the name game. Back in the May 1990 Editor's Forum, I
- discussed an offhand suggestion I'd heard. Someone felt we should change the
- name of this magazine to The C++ Users Journal. After all, C++ is where all
- the action is these days, nicht wahr? Why not change with the times? I
- declined to make such a recommendation to Robert Ward at the time. I felt then
- that C still had a lot of life left in it. And legions of programmers still
- needed to read about style, techniques, and esoterica peculiar to the C
- language proper. We were running articles on C++ (and other dialects) on a
- regular basis. That was enough.
- I predicted then that our C++ coverage would increase if the need arose. And
- indeed the need has arisen. Lots of programmers are finding reasons to try
- C++. Some are grappling with new coding styles. Some with new techniques. Many
- are finding lots more esoterica to master. You'll now find that a typical
- issue of The C Users Journal devotes substantial editorial real estate to
- articles on C++. We still differ from the new "object-oriented" publications
- in one important regard. Few of those articles we now include are preachy.
- Rather, they are written by C/C++ programmers to help others use this new
- dialect to advantage. That's been our strength in the C world for years. It
- will continue to be our focus in the emerging era of C++. And we will continue
- to cover the huge, and growing, C market for the foreseeable future.
- So should we change our name? I cite two other organizations who kept their
- old names even as they changed with the times. One is the Association for
- Computing Machinery (ACM). Many members felt that the focus of that
- organization had shifted from mere machinery to vastly more important topics.
- They called for a referendum to keep the initials ACM, but have them in future
- stand for Association for CoMputing. It was defeated. (I voted against the
- change on the grounds that computer science would be just an esoteric branch
- of mathematics were there no fast, reliable, and inexpensive computing
- machines.)
- The other group is the League of Women Voters. When they began allowing men to
- join, someone suggested that they should henceforth be the League of Voters
- (or the League of People Voters). My favorite argument against the change was
- made by one of the leaders. She suggested that they simply explain to men that
- "women" in this context was a generic term that also included men. The men
- would understand. If we don't change our name, I'm sure the C++ programmers
- will understand too.
- P.J. Plauger
- pjp@plauger.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- Micro-Processor Services Releases MASM to C Translator
-
-
- Micro-Processor Services, Inc., has released MASM2C, a tool designed to
- translate MASM code generated by a disassembler into C source code. Working
- with a disassembler, MASM2C can be used to translate from an .EXE file to
- intelligible C source. MASM2C contains a syntax analyzer, a MASM-to-tertiary
- converter, and a tertiary-to-C converter. The tertiary language is used to
- maintain logical equivalence between source and target languages, and is used
- in the entire Micro-Processor Services family of translators. MASM2C is
- compatible with V Communications' Sourcer and other disassemblers, and is
- error message compatible with "Brief" (or other editors supporting Microsoft
- C) permitting translation within an editor environment. MASM2C supports 8086,
- 80186, 80286, 80286P, 80386, and 80386P instructions. The translation includes
- MASM error bypass and C beautifier.
- MASM2C is available in both a standard version ($475) and a protected-mode
- version ($850). The standard version can handle files of 2000 to 3000 lines,
- while the protected-mode version can handle up to 40,000 lines at 16MB.
- Translation speed is 400 to 500 lines per minute. For information, contact
- Micro-Processor Services, Inc., 92 Stone Hurst Lane, Dix Hills, NY 11746,
- (516) 499-4461; FAX: (516) 499-4727.
-
-
- Compass Point Software Introduces application::ctor
-
-
- Compass Point Software, Inc., has announced their application::ctor
- (pronounced "application constructor") product, a GUI application-development
- tool for Microsoft Windows. application::ctor includes an object-oriented
- "view Editor", a user-interface class library containing over 100 classes, and
- a C++ class browser. Application::ctor can be used to build Multiple Document
- Interface (MDI) windows and provide attachment and gravity allowing automatic
- rearranging and resizing of controls when the window is resized.
- application::ctor can also subdivide a window into regions called "hot areas."
- Protoypers can use the View Editor to construct an application's
- user-interface and to attach pre-defined prototyping functions from the class
- library. Once the prototype's functionality and appearance are established,
- developers can attach C++ code to the events in the View Editor to build a
- deliverable application.
- application::ctor requires Windows 3.1 and a C++ compiler. The price is $99.
- Contact Compass Point Software, 332A Hungerford Drive, Rockvill, MD 20850,
- (301) 738-9109.
-
-
- Intel's Tunes C Compiler for Superscalar Microprocessor
-
-
- Intel Corporation has introduced CTOOLS960 Release 4.0, a C compiler which
- exploits the superscalar architectures of their i960R CA and i960 CF
- microprocessors. Intel's announcement claimed improvements of up to 40% in
- application program performance. The CTOOLS960 compiler collects information
- about an application program during execution of the program to drive its
- optimization. The CTOOLS960 compiler was designed to bring optimization to
- both superscalar and non-superscalar, and can optimize programs written for
- any i960 microprocessor, including the original Kx and the Sx series.
- The CTOOLS960 package includes the compiler, the assembler, and utilities
- designed for appliciation development and performance optimization on the i960
- microprocessor family. CTOOLS960 Release 4.0 is priced at $2000 for MS-DOS
- hosts, $3500 for HP9000 hosts, and $4300 for SUN4 or IBM RS6000. For more
- information, contact the Intel Literature Center at (800) 548-4725, or write
- for: Intel Literature Pack #A9P71, P.O. Box 7641, Mt. Prospect, IL 60056-7641.
-
-
- Microtech Research Supports Cross Development for Intel processors on IBM
- RS/6000
-
-
- Microtec Research, Inc., has announced it's Intel i960 and 8086-family
- microprocessor development tools for the IBM RISC System/6000. These tools
- complement the Motorola 68000-family tools already provided by Microtec
- Research, and were ported under an agreement with IBM. On the RS/6000, the
- cross development tools include an ANSI C cross compiler, a Macro Cross
- Assembler, and the XRAY Debugger. A C++ cross compiler is available for 68000
- targets. The XRAY Debugger debugs optimized C code, allowing engineers to work
- with production-quality code. The XRAY Debugger provides an X Window System
- Motif interface. The XRAY Debugger on the RS/6000 supports instruction set
- simulation and debug monitor as execution environments. With instruction set
- simulation, software development can begin before the target hardware is
- available, while with monitor-based debugging, developers can perform
- real-time debugging while software runs on the target hardware. Microtec
- Research C compilers comply with ANSI C, but also support K&R C. The C++
- compilers comply with version 2.1 of the AT&T specification.
- For information, contact Microtec Research Inc., 2350 Mission College
- Boulevard, Santa Clara, CA 95054, (800) 950-5554; FAX: (408) 982-8266.
-
-
- TauMetric Announces C++/C Debugger/Browser for OpenWindows
-
-
- TauMetric Corporation has announced a Windowed Debugger/Browser (WDB) for C++
- and C programs on Sun SPARCs. WDB provides an OPEN LOOK interface to a
- source-level debugger which includes source, data, and class browsing
- facilities. WDB runs under OpenWindows 2.0 or 3.0, and debugs programs
- compiled with TauMetric's Oregon C++/C compiler.
- WDB includes both a Class Hierarchy Browser and Class Member Browsers. The
- Class Hierarchy Browser graphically depicts inheritance relationships between
- all classes in a program. The Class Member Browsers displays the members of a
- class (both data members and member functions), including inherited members,
- and allows access to the source code defining a class and its members.
- WDB is sold with the Oregon C++ Development System for SPARC, and is available
- at a discount for current Oregon C++ users. Oregon C++ is also available for
- VAX/VMS and DECstation/Ultrix. TauMetric plans calls for ports of WDB to the
- other platforms along with expansion into a complete object-oriented
- programming environment. For information, contact TauMetric Corporation, 8765
- Fletcher Parkway, Suite 301, La Mesa, CA 91942, (61) 697-7607, or (800)
- 874-8501; FAX: (619) 697-1140.
-
-
- SunPro Licenses MetaWare Code Generation Technology
-
-
- MetaWare Incorporated and SunPro, a Sun Microsystems, Inc. business, have
- announced a licensing agreement in which MetaWare's x86 code generation
- technology will be used in a new family of SunPro compilers for Intel-based
- personal computers. SunPro and MetaWare, in their release, project the new
- family of compilers, ProCompilers, to be the first compiler suite available
- for Solaris 2.0 for x86. ProCompilers will provide source compatibility across
- SPARC and Intel x86.
- ProCompilers are now available in SunSoft's Solaris 2.0 for x86 early access
- kit, the developers version of the new operating environment for Intel 32-bit
- platforms. SunSoft has announced that the Intel version of Solaris 2.0 willbe
- available in volume toward the end of 1992. Contact MetaWare (408) 429-6832 or
- SunPro (415) 336-4638.
-
-
- Sierra Systems Upgrades Sierra C Compiler
-
-
- Sierra Systems has upgraded their C cross compiler and development system for
- the Motorola 68000 family. Sierra C 3.0 was designed specifically for the
- embedded systems engineer, and generates position independent, re-entrant, and
- ROMable code for any memory configuration. Version 3.0 incorporates a "Hole
- Compression" Optimization Utility. Sierra C's Linker can determine on the
- first pass when a more compact addressing mode can be used, relays the
- information to the assembler, which makes a second pass and adjusts the code
- improving size (4-7% on average) and performance. Version 3.0 also includes
- extended-memory support, floating-point enhancements, enhanced multiplication
- by constant, improved function-call stack cleanup, and inline strcpy and
- unrollable structure copy code.
- Sierra C is priced at $2,000 for PCs or $3,500 for UNIX workstations. Contact
- Sierra Systems, 6728 Evergreen Avenue, Oakland, CA 94611, (800) 776-4888 or
- (510) 339-8200; FAX: (510) 339-3844.
-
-
-
- SLR Systems Releases OPTLINK v3.0 for Windows
-
-
- SLR Systems, Inc. has released OPTLINK v3.0 for Windows. OPTLINK v3.0 for
- Windows adds smart-linking technology and p-code support for Microsoft's C/C++
- v7.0, and supports elimination of all unreferenced functions. OPTLINK v3.0
- allows up to 64K each of libraries, symbols, segments, modules, and other link
- specific information. OPTLINK v3.0 for Windows replaces LINK, and supports all
- compilers and assemblers that generate standard object files. OPTLINK v3.0
- also supports CodeView 3.x and 4.x. Operating from MS-DOS, OPTLINK v3.0 for
- Windows generates 16-bit .EXEs and .DLLs for both Windows and OS/2. OPTLINK
- v3.0 Segmented accomodates MS-DOS and OS/2 hosted linking for Windows and OS/2
- executable. OPTLINK v3.0 for Windows and OPTLINK v3.0 Segmented are priced at
- $350 and $499, respectively. Contact SLR Systems, Inc., 1622 N. Main Street,
- Butler, PA 16001, (412) 282-0864; FAX: (412) 282-7965; BBS: (412) 282-2799.
-
-
- Magna Carta Moves Communications Library to Windows
-
-
- Magna Carta Software has released their C Communications Toolkit/Windows, a C
- developer's library for serial communications under Windows. The library
- supports single or multiport communications under Windows and works with all
- Hayes-compatible modems. The library supports data transmission and reception,
- flow control, and a variety of file transfer protocols, including XMODEM,
- YMODEM, ZMODEM, and Kermit.
- C Communications Toolkit/Windows costs $299. The library supports Borland C++,
- Turbo C++, Microsoft C/C++, and Microsoft Quick C for WIndows. The package
- comes with full, commented source code, 600 pages of documentation, an da
- 30-day money-back guarantee. For more information, contact Magna Carta
- Software, P.O. Box 475594, Garland, TX 75047-5594, (214) 226-6900.
-
-
- NeruoDynamX Provides Embeddable Neural Network Software
-
-
- NeuroDynamX, Inc., has released DynaMind Developer neural network software for
- implementing neural networks. The package bundles DynaMind neural network
- training software (3.0) with several runtime options, including linkable
- C-code routines and neural network hardware simulation. Networks trained with
- DynaMind can be run standalone, embedded into C applications, or used in
- simulations of neural network hardware. The package includes NeuroLink v2.0, a
- library of C routinges for linking networks into applications. The library can
- link multiple networks serially or in parallel. The package includes a
- simulator for Intel's 80170NX ETANN chip.
- NeuroLink compiles under Borland Turbo C 2.0, Turbo C++, or Borland C++ and
- carries no run-time license fee. DynaMind Developer is priced at $495, while
- DynaMind 3.0 software is available separately for $145. Contact NeuroDymanX,
- Inc., P.O. Box 323, Boulder, CO 80306, (800) 747-3531; FAX: (303)442-2854.
-
-
- Tom Sawyer Software Introduces Graph Layout Toolkit
-
-
- Tom Sawyer Software has introduced the Graph Layout Toolkit, an automated
- object positioning tool which helps application programs provide visual
- representations of objects and their connections. The Graph Layout Toolkit
- provides real-time graph layout services for both directed and undirected
- graphs through a set of extensible class libraries written in C++. An ANSI C
- API is furnished as well. The package can handle cyclic graphs, multiple
- disconnected subgraphs, multigraphs, and reflexive edges. The toolkit supports
- crossing minimization, graph reduction, hierarchical layout, and subgraph
- position balancing.
- The software is available for PC, Mac, Sun, HP 9000, RS/6000, and NeXT
- platforms. Tom Sawyer is seeking to license this graph layout technology for
- embedding/repackaging by product-oriented companies. Price is determined by
- contract. Contact Tom Sawyer Software, 1824B Fourth Street, Berkeley, CA
- 94720, (510) 848-0853; FAX: (510) 848-0854.
-
-
- XVT Announces Developer Toolkit for Windows NT
-
-
- XVT Software, Inc., has announced support for Windows NT with its latest
- version of the XVT Portability Toolkit. The XVT Portability Toolkit allows
- programmers to build a C/C++ application, then recompile to other GUIs without
- rewriting code. Applications built with XVT/NT will use a 32-bit linear memory
- model. With NT's pre-emptive multitasking system, XVT/NT will offer memory
- protection for each application. XVT/NT supports all Portability Toolkit
- Release 3.x functionality.
- The Portability Toolkit for NT is priced at $1,450 on Intel 486 systems, and
- at $4,400 for DEC and MIPS. XVT does not charge royalties. Contact XVT
- Software Inc., 4900 Pearl East Circle, Boulder, CO 80301, (303) 443-4223.
-
-
- IDE Introduces C++ Tools and Support
-
-
- Interactive Development Environments, Inc., has added Object-Oriented
- Structured Design/C++ (OOSD/C++) to its Software through Pictures family of
- CASE tools. OOSD/C++ provides developers with a graphical design tool, a Reuse
- Browser, and a Code Generator for C++. IDE is also marketing a coordinated
- program (the Success Package for C++) of tools, training, consulting,
- maintenance, and support that includes OOSD/C++ and is based on the premise
- that in order to exploit object-oriented design, comprehensive training and
- support are essential.
- Contact Interactive Development Environments, Inc., 595 Market Street, 10th
- Floor, San Francisco, CA 94105, (415) 543-0900; FAX: (415) 543-0145.
-
-
- Vitamin C/CE Released for COHERENT
-
-
- Creative Programming Consultants, Inc., and Mark Williams Company have
- announced the release of Creative Programming's Vitamin C/CE, an advanced user
- interface library for COHERENT. An alternative to curses, Vitamin C/CE enables
- programmers to create portable, multiwindowed, terminal independent
- applications with full data entry, validation, help, and menuing capabilities.
- Vitamin C/CE provides source level compatibility over a variety of platforms,
- including MS-DOS, SYSV, BSD, SUN/OS, HP/UX, AIX, Ultrix, and VMS.
- Vitamin C/CE is available for $99 from Creative Programming Consultants, 2013
- N. Broadway, Carrollton, TX 75006, (214) 245-9139; FAX: (214) 245-9717.
-
-
- Symantec Announces MultiScope Debuggers for Borland and Microsoft C++
-
-
- Symantec Corporation has announced version 2.0 of its MultiScope Debuggers.
- Version 2.0 supports Borland C++ and Microsoft C 6.0 and C/C++ 8.0 languages
- for programming Windows and MS-DOS applications. The MultiScope Debuggers
- provide a Windows-hosted user interface, with a "CommandBar" which allows
- quick access to frequently-used commands. MultiScope debuggers are designed
- specifically for C++ code and include C++ specific features. New features
- include a "point-and-shoot" collapse and expand C++ class hierarchy browser,
- C++ object browsing, automatics C++ object mapping for all C++ inheritance
- types, alternative C++ class information member scope display, object-oriented
- breakpoints directly on object methods, direct browsing of member pointers,
- complete C++ expression evaluation, name unmangling, and the ability to update
- the source window to the actual function by selecting the method while
- browsing. MultiScope includes a Crash Analyzer System, with a Monitor
- Execution and Dump (MED) utility, which allows analysis of program crashes.
- MultiScope Debuggers for Windows are priced at #379. MS-DOS products are
- priced at $179. Upgrades for registered users are available. For information
- contact Symantec Corporation, 10201 Torre Avenue, Cupertino, CA 95014-2132,
- (800) 441-7234 or (408) 252-3570; FAX: (408) 253-4092 or 252-4694.
-
-
- Blaise Computing Announces C ASYNC MANAGER Version 4.0
-
-
-
- Blaise Computing Inc., has announced version 4.0 of their C ASYNCH MANAGER,
- for applications in Microsoft and Borland C/C++, Quick C, and Turbo C and C++.
- Version 4.0 supports XMODEM, YMODEM, ZMODEM, and Kermit file transfer
- protocols; background transfers; and multiple simultaneous transfers. Other
- features include new modem control functions, enhanced support for 16550A
- UART, and support for multiport boards such as Digiboard PC/X. Contact Blaise
- Computing Inc., 819 Bancroft Way, Berkeley, CA 94710, (510) 540-5441; FAX:
- (510) 540-1938.
-
-
- GUI Computer Releases ObjectTable C/C++ V1.5
-
-
- GUI Computer has released version 1.5 of their ObjectTable C/C++, a
- Windows-oriented library that implements a programmable multicolumn table
- object. This release supports protected column, draggable column width,
- international currency, different true-type font and color for column, and
- various title options. ObjectTable C/C++ can work as a custom control and is
- compatible with Borland Resource Workshop and Microsoft Dialog Editor. Contact
- GUI Computer, Inc., P. O. Box 795908, Dallas, TX 75379, (800) 800-9010; FAX:
- (214) 250-1355; BBS: (214) 250-2077.
-
-
- Lucid's Energize 1.1 Expands Tools and Utilities
-
-
- Lucid, Inc., has announced Energize 1.1, with support for more tools and
- utilities and improved performance in its native code C++ and C compiler. New
- releases of Lucid C and Lucid C++ have begun shipping. Energize 1.1 supports
- tools and libraries for C and C++, including system math libraries and the
- latest version (3.01) of the InterViews library from Stanford University.
- Energize support what Lucid calls Computer-Aided Programming (CAP). CAP
- offloads routine and repetitive tasks to the computer. Energize also supports
- incremental compilation and linking. Contact Lucid, Inc., 707 Laurel Street,
- Menlo Park, CA 94025, (415) 329-8400; FAX: (415) 329-8480.
-
-
- Non Standard Logics Introduces Version 2.0 of XFaceMaker
-
-
- Non Standard Logics has introduced version 2.0 of XFaceMaker (XFM), its
- X/Motif application interface management system. XFM's new WidgetMaker feature
- allows designers to build customized widgets that can be used with Motif, Open
- Look and other toolkits. XFM's templates can be used to assemble widgets into
- objects with assigned behavior, inheritance, and open-ended reusability. XFM
- includes a resource editor, a C-like scripting language (FACE) and a test
- mode. Contact Non Standard Logics, 99 Bedford Street, Boston, MA 02111, (617)
- 482-6393; (617) 482-9707.
-
-
- Microware Announces ANSI C Compiler for Intel and Motorola Processors
-
-
- Microware Systems Corporation has announced their Ultra C compiler, an ANSI C
- compatible compiler with an architecture and optimizing algorithms designed to
- maximize performance in real-time applications. Ultra C has been tested for
- compliance with the Plum Hall C Validation Suite. Ultra C is closely tied to
- Microware's OS-9 and OS-9000 real-time operating systems, including I/O and
- system calls. The Ultra C compiler is compatible with any processor running a
- Microware operating system. With Ultra C, Microware provides a tightly-coupled
- compiler and operating system, and a single source of technical support.
- The Ultra C architecture divides compilation into four processes: a front-end
- language process, an optimization and linking function, a target processor
- back end, and the assembly and linking function. Ultra C makes extensive use
- of an intermediate code (I-Code), performing linking and optimization on the
- I-Code, in addition to the other three stages of compilation. The language
- processor is compatible with source for previous Microware compilers, ANSI C
- source, and ANSI-extendable code. Ultra C can generate object code for any of
- its target processors, facilitating application development for multiple
- target systems.
- For information, contact Microware Systems Corp., 1900 NW 114th Street, Des
- Moines, IA 50325-7077, (800) 475-9000; FAX: (515) 224-1352; Telex: (910)
- 520-2535.
-
-
- Phar Lap's 286 DOS-Extender Supports Borland C++ 3.1 and Microsoft C/C++ 7.0
-
-
- Phar Lap Software, Inc., has announced version 2.5 of their 286 DOS-Extender
- which is compatible with Borland C++ 3.1 and Microsoft C/C++ 7.0, as well as
- Turbo Debugger and CodeView for Windows. Version 2.5 includes faster floating
- point for Microsoft C/C++ users and huge memory model support for Borland C++
- users. 286 DOS-Extender allows programs to access up to 16MB of memory on any
- DOS-based 80286, 386, or i486 PC. 286 DOS-Extender is priced at $495, upgrade
- discounts are available. Contact Phar Lap Software, Inc., 60 Aberdeen Avenue,
- Cambridge, MA 02138, (617) 661-1510; FAX: (617) 876-2972.
-
-
- Sequiter Software Releases CodeTranslator 2.0
-
-
- Sequiter Software, Inc., has released CodeTranslator 2.0, which translates
- Clipper '87 and dBASE III PLUS applications into C. This tool can be used for
- performance enhancement, porting to UNIX, Microsoft Windows, or OS/2, and
- teaching xBASE programmers the C language. CodeTranslator generates ANSI
- compliant C code and can be combined with a portable library, CodeBase
- (currently version 4.5, but with version 5.0 planned for release in November
- 1992). CodeTranslator 2.0 is priced at $245, and CodeBase 4.5 retails for
- $395. Contact Sequiter Software, Inc., #209, 9644 - 54 Avenue, Edmonton,
- Alberta, Canada, T6E 5V1, (403) 437-2410; FAX: (403) 436-2999.
-
-
- Pinnacle Publishing Upgrades Graphics Server SDK to Version 2.0
-
-
- Pinnacle Publishing, Inc. has announce Version 2.0 of the Graphics Server
- Software Development Kit (SDK), a dynamic link library (DLL) for adding
- graphing and charting capabilities to Windows applications. Graphics Server
- SDK supports C/C++, Turbo Pascal, Visual Basic, SQL Windows, Superbase,
- PowerBuilder, and Actor. Version 2.0 includes three new graph types--bubble
- graph, tape graph, and 3d area graph, automatic "hot graphs", improved axis
- and font control, multiple graphs, a print cancel option, and curve fitting
- capabilities. Contact Pinnacle Publishing, Inc., P.O. Box 1088, Kent, WA
- 98035, (206) 251-1900; FAX: (206) 251-5057.
-
-
- JMI Announces C EXECUTIVE for AMD Am29205
-
-
- MMI Software Consultants, Inc., has announced C EXECUTIVE support for AMD's
- new Am29205 microcontroller. JMI's C EXECUTIVE is a real-time, multitasking,
- ROMable operating system kernel used in embedded systems. C EXECUTIVE is
- written in C for portability and is available for 80186, R3000, 29000, i960,
- 80386, 68000 and other processors. C EXECUTIVE also offers a file system,
- CE-DOSFILE, and a debugger, CE-VIEW. For some processors, C EXECUTIVE will be
- available packaged with FUSION TCP/IP from Network Research Corp. Contact JMI
- Software Consultants, Inc., 904 Sheble Lane, P.O. Box 481, Spring House, PA
- 19477, (215) 628-0840.
-
-
- Intermetrics Releases Three Debuggers for 68HC16 Processor
-
-
- Intermetrics Microsystems Software, Inc., has announced CXDBmon, CXDBsim, and
- CXDBice, C source-level debuggers for the Motorola 68HC16 processor. CXDBice
- is pre-integrated with in-circuit emulators from Motorola, Pentica, Nohau,
- EST, and Huntsville Microsystems; CXDBsim provides an instruction set
- simulator; and CXDBmon provides a monitor target program and supports the
- Motorola 68HC16 EVB development board. Contact Intermetrics Microsystems
- Software, Inc., 733 Concord Avenue, Cambridge, MA 02138-1002, (617) 661-0072;
- FAX: (617) 868-2843.
-
-
-
- TeleSoft Announces TeleUSE Version 2.1
-
-
- TeleSoft has announced version 2.1 of TeleUSE, a user interface management
- system (UIMS) for developing GUIs based on OSF/Motif. The new version includes
- a session manager, an improved interface, a more powerful Dialog Manager with
- expanded C++ support, and third-party tool integration. TeleUSE is priced at
- $7,500 and supports UNIX platforms including: SPARC, HP, and IBM. Contact
- TeleSoft, (619) 457-2700.
-
-
- Electronic Imagery's ImageScale Plus Adds JPEG Compression
-
-
- Electronic Imagery has announced that its ImageScale Plus Developer's Toolkit
- for MS-DOS and UNIX applications, now includes the JPEG compression algorithm.
- The toolkit provides command-line executable programs, and graphics, text, and
- cursor routines in an image processing library written in C. Contact
- Electronic Imagery, Inc, 1100 Park Central Boulevard South, Suite 3400,
- Pompano Beach, FL 33064, (305) 968-7100; FAX: (305) 968-7319.
-
-
- EMS Professional Shareware Ships dBUtilily and C Utilily Libraries on CD-ROM
-
-
- EMS Professional Shareware has begun shipping two new CD-ROMs: the Library of
- PD/Shareware C Utilities, with 722 programs, and the dBUtility Library, with
- 2238 different public domain and shareware products. The disks are priced at
- $125, are updated quarterly, and include a substantial database of PC products
- (35,000) and vendors (13,000). Contact EMS Professional Shareware, 4505
- Buckhurst Ct., Olney, MD 20832, (301) 924-3594; FAX: (301) 963-2708.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Mr. Plauger,
- For some years, I wrote most of my code in assembler, first on a CP\M system,
- and later, under MS-DOS. A friend introduced me to C just over a year ago, and
- I could kick myself for waiting so long to discover it. What used to take days
- in MASM can be written in hours (or minutes) with C, especially the
- input/output functions.
- When I recently upgraded to QuickC v2.5, I returned the CUG membership card in
- the package, and began receiving the C Users Journal. I've enjoyed the
- magazine almost as much as I've enjoyed working with the language.
- Your column and comments are always informative, and I appreciate the
- magazine's coverage of specific issues--C++, Liana, Windows, etc.
- These articles are so useful because they represent real-world programming,
- where I might spend one evening coding a device controller, then spend three
- months coding the user interface (even in robotics and automation, they won't
- buy it unless it has pull-down menus and bells and whistles).
- That brings me to my point.
- A reader recently complained that C doesn't have a cls function. You answered
- that the ANSI committee was reluctant to set any standards of that type,
- because they insisted on viewing all input and output as streams.
- That letter struck a chord with me. By being so fanatical about portability,
- and by insisting on viewing I/O from a single standpoint--that of streams--the
- ANSI committee may have ended up making the language less portable.
- Simply put:
- 1. Few people will be impressed with and/or use (or more importantly, buy) a
- program that has a dumb-terminal display. Programs that scroll one line at a
- time are almost as exciting as watching mold grow.
- 2. Because the ANSI refused to specify standards for these basic display
- functions, the programmer is forced to sacrifice portability with the first
- clearscreen or locate cursor function.
- Sure, I could write a very portable program that uses ANSI.SYS to control the
- screen. The problem is, it would run slower than itch, and no one would buy
- it. Now, I realize that setting a GUI standard might be beyond the scope of an
- ANSI committee. But after giving it a lot of thought, I believe that the
- current standard is too limited. More simply put: I respect ANSI's viewpoint,
- but in this day and age of 486 PCs and VGA monitors and Visual BASIC and
- MacIntoshes, can't I expect my programming language to have a standard
- function for wiping a screen or locating a text cursor?
- A number of objections are commonly raised. The first one goes, "You're
- opening Pandora's box; where do we stop? Next, they'll want functions like
- SET_CIRCLE_COLOR ..."
- I agree that you have to know where to stop. Graphics functions, in
- particular, might not be easily standardized. But couldn't we agree on a
- couple of functions that would at least bring C up to par with BASIC for text
- display?
- My list would include cls, locate_cursor, and set_text_color. These could be
- implemented on virtually all modern computer systems.
- The next objection would be that many computers don't support text
- positioning, color and/or graphics. So? If the computer won't support color,
- the system just ignores set_color and displays in monochrome text. If the
- computer won't support hardware functions such as moving the cursor, I'll just
- have to work around it.
- That's precisely what I'm doing now, so what have I lost?
- In closing, I don't want to criticize the ANSI committee unfairly. I respect
- what they've done, and agree that a standard was needed. All I'm saying is
- that they could have looked a bit more at the real world, rather than just at
- a theoretical stream concept, before setting that standard.
- As computer hardware becomes more sophisticated, we're actually going to pay
- the price down the road in reduced portability, as programmers are
- increasingly forced to resort to their own--and quite non-ANSI--solutions to
- each user interface.
- Yours truly,
- Stephen M. Poole
- 122 N. Main Street
- Raeford, NC 28376
- You missed the point. We were not in love with a "theoretical stream concept"
- to the exclusion of all reason. Rather, we did nearly all our work at a time
- when streams were very real and screens were very new. It wasn't clear that
- your particular set of primitives were worth standardizing. Now the issues are
- more clear cut. You can still isolate the disturbance in a few
- system-dependent functions. Remember, portability is a statement about
- relative cost, not a true or false condition. -- pjp
- Dear CUJ:
- Mr. Wilbon Davis' article on time complexity in the September 1992 issue was
- quite interesting.
- However, it gave only a single sentence to a very serious problem concerning
- many quicksort implementations; that the use a fixed pivot point can result in
- O(n2) time, the same as a bubble sort. qsort's run time is very much
- probabilistic. There are a great many programmers who believe otherwise.
- In the Febrary 1992 issue of Unix Review, Nr. John Bently explained the reason
- for this and showed that the problem is present in many of the popular qsort
- implementations. Since any fixed pivot will result in quadratic run time he
- suggests adding a bit of randomness to the pivot selection. The possibility of
- quadratic run time still exists, but only if the data to be sorted is in
- cahoots with the random number generator.
- In the August 1992 issue of Unix Review. Mr. John Bently also examines the
- heapsort and its problems.
- Sincerely,
- Mr. Carey Bloodworth
- 1601 North Hills Blvd.
- Van Bure, AR 72956
- Your point is well taken. Quicksort can have nasty time complexity for some
- patterns of input. -- pjp
- Dear Sir/Madam:
- I would like to know if you could help me with the following. I would like to
- create computer games and animation (musical animation). I have some
- programming background, and I have Borland's Turbo C++ and Assembler.
- I have a 386SX IBM compatible (16 MHz) with a VGA monitor. I use v3.3 MS-DOS.
- I have 1 Meg of RAM, a 60 Meg hard drive (Drive C), two high density drives 5
- 1/4" (Drive A) and a 3 1/2" (Drive B), a 24 pin dot matrix printer (Roland
- Raven 2417), and use a keyboard for everything. I am presently upgrading to
- the following: MS-DOS 5.0; Windows 3.1; Soundblaster Pro; 10 Megs of RAM; 245
- Meg hard drive; math co-processor; and a mouse.
- I am considering at the end of this year looking into a 33 MHz or higher
- motherboard and next year a CD-ROM, but I am considering waiting a little
- longer for the CD-ROM as I want to read and write it and the ones available
- now are read only. I would like to create computer games like Conquest of
- Longbow, Wolfpack, Indiana Jones Last Crusade, Fate of Atlantis, Prince of
- Persia etc. I would like to create my games as life like as the ones
- mentioned.
- I have a game in mind that deals with ancient Egypt. It needs to be able to
- create pictures such as pyramids, coffins, King Tut's gold mask,
- hieroglyphics, etc. I would also like to create animated films such as Walt
- Disney's Beauty and the Beast and 101 Dalmations, etc. What I want to do (if
- possible) is to create the animation, with the music from the CD-player, and
- record it on diskette. Once this done I would like to transfer it on a VCR
- tape and play it on my T. V. Can this be done?
- Can you also provide me with the following:
- 1. Can you recommend books that show how to create games and animation? If so,
- please give title of book, author's name, and if possible where to purchase.
- 2. Call you recommend any schools that provide correspondence learning in this
- matter. Please give name and address.
- For the programs listed in your magazine, can diskette with the programs
- already in the magazines be purchased. If so please provide cost, shipping and
- handling, and method of payment. Also can they be used with Borland's Turbo
- C++?
- If you can kindly provide me with any other information that is not requested
- in this letter, please do so as I have written letters to schools here in
- Canada, magazines, and computer companies. But all say they cannot help me.
- Please understand that I would like very much to learn as I enjoy playing the
- games and am fascinated by them. I would really like to learn how I can create
- my own.
- Thank you for your cooperation in this matter. Hope to hear from you soon.
- Yours truly,
- Victoria Ceolin
- 510 Acadia Drive
- Hamilton, Ontario
- Canada
- L8W 3A4
- You've laid out a very ambitious program. You have much to learn to get where
- you want to go. You'll also have to wait another year or two, as you've
- already guessed, for inexpensive hardware to come within reach of your dreams.
- But please don't stop dreaming. Your fascination with what can be done with
- computers can see you through the tough learning.
- My recommendation is that you get one of the simpler animation programs
- currently available and start practicing your craft with it. Reading CUJ will
- help you learn whatever programming you may need, but I suspect you want to
- keep that to a minimum. Also read PC Magazine from time to time to keep track
- of the latest advances in PC hardware and software. Sorry I can't be more
- specific, but I encourage any readers who share your interests to contact you
- directly. Good luck. -- pjp
- Dear Dr. Plauger:
- One of the few things more humbling than admitting to bugs in your
- carefully-crafted software is the introduction of new bugs when attempting to
- squash the old ones! Or, as Charlie Brown would say, ARRRRRRRRRRRGH! I am
- referring to your article in the September, 1992 issue of the C Users Journal,
- in which you attempt to fix a bug in which a failed memory allocation with
- malloc is ignored and data are copied to an address specified by a null
- pointer. (The code in question can be found on page 12.)
- Either the replacement code has fallen victim to a typesetting error, or else
- it must not been successfully passed through a conforming C compiler. This is
- a very real risk that we expose ourselves to whenever a code change seems so
- trivial that we only test it with the C compiler located between our ears!
- Have you noticed the error, now that I've directed your attention to the code
- fragment?
-
- Maybe it's true that the only time we can say with assurance that all the bugs
- are gone from a program is when the very last copy of the program in existence
- has been incinerated! I feel that this is true for most non-trivial programs
- I've written!
- Yours truly,
- John P. Toscano
- PharmData Systems
- P.O. Box 11537
- St. Paul, MN 55111-0537
- Error noted. It was indeed a transcription error. Thanks for reporting it. --
- pjp
- Mr. Plauger,
- I thoroughly enjoyed Dwayne Phillips's, "The Foundation of Neural Networks:
- The Adaline and Madaline" (September 1992). It very clearly presented the
- fundamentals of neural network programming.
- I feel that a few small changes in the C code provided will yield improved
- performance. I am referring specifically to Listing 4, which makes the final
- AND/OR/MAJORITY decision. In the first two cases AND and OR), basic logical
- principles allow the for loops to be terminated immediately upon finding the
- first of the appropriate values. For example, in the AND decision, the loop
- may be terminated upon finding the first FALSE value (--1). There is no need
- to check any further inputs, as only one FALSE input is necessary to ensure a
- FALSE output in an AND condition. Similarly, the OR decision can be terminated
- as soon as the first TRUE value (1) is found. This is the same method used by
- C compilers to "short-circuit" logical tests. In both cases, inserting a break
- statement into the loop should do the trick. [Code available on monthly code
- disk.]
- The MAJORITY decision code can also be improved by noting that the outputs
- being checked conveniently have the values 1 and --1. All that is necessary is
- to sum up all outputs. If the sum is positive, the 1 values are in the
- majority; if the sum is negative, the --1 values are in the majority. To be
- consistent with the original code, a sum of O should also yield a result of
- --1. Refer to the listing for the exact method.
- Admittedly, with the small size of the networks presented in the article, the
- effects of these changes will probably be negligible. However, the performance
- improvement from these modifications could be important if this (or similar)
- code is used as the basis of larger, more complex networks.
- Yours very truly,
- Eric B. Schuyler
- 81 Yorktown Road
- Snyder, NY 14226
- Dear Dr. Plauger,
- Thanks for your encouraging response to my earlier letter (CUJ Sep. 1992)
- concerning a proposed article on fail/fool-proof data input functions in C. At
- your request, I will expand a bit further on my suggestion. I see two aspects
- of the problem: input of individual data fields of various types, and
- combination of these fields in a data input screen.
- Concerning the first aspect, an input function for a given data type, e.g.
- currency, date, string of limited length, and other specialized types, should
- validate input immediately during data entry and alert the operator on errors.
- It should also allow correction of mistakes made during entry.
- Enclosed is an example of such a function for input of currency data that I
- wrote some time ago. It uses non-portable, unbuffered input functions, getch
- and putch, supported by Borland and Microsoft compilers, but not necessarily
- by others. Possibly, getchar could be made to work as well. Of more
- importance, I suspect that this function could be implemented more succinctly
- by a better programmer--hence my suggestion for the article!
- As to the second aspect, a collection of data input functions should be
- combined in such a manner that the operator can return to an earlier data
- field to make last-minute corrections, e.g. through use of the PgUp and PgDn
- keys. This may be the easier of the two aspects, but is certainly not trivial.
- After my letter appeared (the first time!) in the August issue of CUJ, I
- received a response from Robert A. Radcliffe (Philadelphia, PA), author of
- Data Handling Utilities in Microsoft C. Unfortunately, that text is now out of
- print, although the author still has some of the code disks. Maybe you could
- convince him to write the kind of article I have in mind!
- Thanks for your interest,
- W.F.H. Borman
- 209 Logwood Drive
- Evansville, IN 47710
- Tel: 812 464-5435
- Dear Mr. Plauger:
- I was much impressed by your mea culpa contained in the "Standard C: Bugs"
- column in the September, 1992 issue. It was a pleasure to find someone noble
- enough to admit mistakes can occur and that he, too, is a mere mortal.
- Unfortunately, character as fine as yours is a rare commodity!
- I do think, however, that you were a little too hard on yourself. There is
- another certainty in life besides Death and Taxes for anyone who writes (or
- uses) software: bugs.
- A bug in the vaunted Kernighan and Ritchie getline function on page 26 in the
- first edition and page 29 in the second edition of The C Programming Language
- causes the example to fail, as do dozens of other examples throughout both
- editions of "the book." They use getline expecting it to return 0 (zero) for a
- blank line when in fact it always returns 1 (one) or greater. The precedence
- table on page 49 of the first edition has errors in it. Borland's C compilers
- happily fopen files whose names have spaces in them (which of course can't be
- deleted by normal means). In Turbo C v2.0 and older versions, free failed if
- gets and related functions were used for keyboard input.
- If the authors and all those people involved at Bell Labs and Prentice Hall
- couldn't get a 13-line function right after ten years and two editions, why
- should anyone expect you to write perfect software? A true cynic sees known
- bugs as job insurance!
- If even the originators of such a great lean and mean language as C can't
- avoid a few bugs shouldn't one place simplicity and reliability at the top of
- one's priorities and always expect bugs? Ronnie R's favorite Russian proverb,
- "Trust but verify," should hang on every programmer's wall.
- Kindest regards,
- Elliott K. Rand
- President, Keep It Simple Systems
- P.O. Box 510093
- Melbourne Beach, Florida 32951
- (407) 729-0187
- Thanks, I needed that. -- pjp
- Mr. Plauger,
- Jodi Leonard informs me that you doubled the price for your C library code
- disk since it was "updated and expanded." Humpf! Other than fix bugs, what
- does "expanded" include?
- I read your article describing the bugs found, it was somewhat amusing as a
- programmer's "confession", if not very scientific.... Care to expand in your
- next column what was changed? Does the current disk also come with a list of
- known bugs?
- Thanks,
- Mike MacFaden
- Group Leader Unix NMS Development
- Premisys Communications, Inc
- 1032 Elwell Court Suite 111 Palo Alto,
- CA 94303
- email: ...!fernwood!premisys!mike
- mike@premisys.com
- Compuserve: 72711.2060
- voice: 415-940-4787
- fax: 415-940-7713
- The new code disk contains no bug list because I fixed all known bugs at the
- time I released it. I have since accumulated two (small) bug fixes, but I'm
- not about to thaw a frozen release whenever I find yet another bug. -- pjp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- SSX -- Stack Swap eXecutive
-
-
- Tom Green and Dennis Cronin
-
-
- Tom Green is a UNIX Software engineer who specializes in UNIX driver
- development. He also writes MS-DOS, Windows and embedded 80x86 applications.
- He may be reached via electronic mail at tomg@cd.com.
-
-
- Dennis Cronin almost completed an EE degree but got lured into the sordid
- world of fast computers, easy money, and loose connections. Specialties: UNIX
- driver development and embedded systems. He may be reached via electronic mail
- at denny@cd.com.
-
-
-
-
- Introduction
-
-
- A project we worked on recently required us to port existing disk-controller
- software from an 80186-based board to a new 80386-based product. The total
- port required that we port not only the actual controller software, but also
- our real-time executive and a debugger as well. Our existing real-time
- executive was written in C and assembly language. We simply set out to
- directly port it to the 386. Oh yeah, we also committed to a pretty aggressive
- (translation: insane) schedule.
- The existing executive used a software-interrupt entry point with parameters
- passed in registers--pretty standard stuff. Task switches were implemented in
- assembly language, swapping in and out registers for the new task and the
- running task. We used a similar interrupt interface in the protected-mode
- version with 80386 task gates used to swap in and out registers for tasks.
- After we ported the disk controller software to protected mode, we tied the
- whole thing together and tried to run the new software. The new software
- worked fine. There was one problem though--it was slower than the old version
- running on the 80186!
- We were quite surprised (the words dismayed and panicked come to mind as well)
- by the results of our first test. We set out to find the problem. We
- discovered that our disk-controller software was doing a rather large number
- of task switches per second and making a pile of calls into the real-time
- executive.
- Referring to our trusty Intel black book, we then noted that calling an 80386
- task gate takes 309 clock cycles--not good. But after looking over the
- executive, we decided that calling 80386 task gates was not our only speed
- problem. The whole process of calling our executive took a lot of time. First
- an assembly-language routine was called from the disk-controller C-code
- software. The assembly-language routine would get all of the parameters passed
- C-style and then stuff them in registers. Next a software interrupt was
- executed, followed by getting the registers back onto the stack so we could
- call into the C portion of the executive. We also had to preserve the state of
- the registers so that, on return from the executive, the registers would be
- restored for the calling software. It should be noted that this basic approach
- resulted from our early experiences with a particular commercial real-time
- executive and its C support libraries.
- After studying things, we realized we could streamline the whole process
- dramatically. Why not simply take advantage of the fact that a C compiler
- saves and restores as much context as necessary between C function calls? The
- compiler knows when making a call to another C routine which registers will be
- saved across the call. This meant that we could ditch swapping tasks with an
- 80386 task gate and skip the 309 cycles used to do a task switch. And
- switching tasks was now a simple C-language call into the linked executive--a
- small set of subroutines linked with the main application, instead of a remote
- set of services accessible only through software interrupts.
- Since the basic mechanism of the task switch is now a rather trivial stack
- frame switch, we called the design the Stack Swap eXecutive, or SSX (Listing
- 1, Listing 2, and Listing 3).
-
-
- Pros and Cons of SSX
-
-
- There are many things to recommend SSX. The native C-language interface and
- frisky stack-swap task switch make it very fast and efficient. And it is very
- small. The small size can save precious RAM or EPROM space that a larger
- executive might take up. SSX is also very portable. This article uses the
- executive in the MS-DOS environment but it is flexible enough to use in
- embedded applications or on different processor families. And since SSX is a
- very minimal executive, it is also easy to understand, port, modify, and
- extend.
- There are, of course, a few limitations to using SSX. SSX is written in C and
- works best with an application written in C or C++, although it's not too hard
- to call into SSX from assembly after saving a few registers. SSX must be
- linked with your application. If you have a number of separate programs which
- much share CPU time and resources, they must all be linked together. Another
- disadvantage with SSX is its lack of features compared with many
- commercially-available executives.
-
-
- Features of SSX
-
-
- SSX is a real-time, preemptive, multitasking executive. Tasks are allowed to
- run until:
- The task readies another task of equal or higher priority
- A time slice (a clock tick) passes and there are other ready tasks of equal
- priority
- An interrupt readies a task of equal or higher priority
- The task explicitly gives up the CPU by calling the ssx_delay routine
- Synchronization of tasks is accomplished via shared data structures of type
- wait_q. A task that needs to wait for an event calls ssx_wait with a wait_q as
- an argument. If the event has already occurred, as indicated by a message flag
- at the queue, the task does not get suspended but remains ready.
- When another task (or interrupt) wishes to ready a sleeping task, it issues an
- ssx_alert to the queue. The highest priority task waiting is readied. If no
- task is waiting, the message flag is set.
- Out of these two basic primitives you can build just about anything you could
- possibly need!
- This model of synchronization has the advantage that it is quite efficient to
- implement. There is no hashing of addresses onto sleep queues or any of that
- kind of messiness. Moreover, it seems reasonable to assume in today's
- object-oriented society that if tasks are cozy enough to be synchronizing with
- each other, they should be well enough acquainted to share the wait_q data
- object.
- At the risk of making our lean mean executive seem feature-laden, we also
- provided a primitive for waiting at a wait_q with an alarm timer set. If the
- event does not occur within the specified period of time, the alarm goes off
- and the task becomes ready again with a return value indicating the reason for
- the resumption.
- And to round out our real-time toolkit, we have the ssx_task_delay call which
- simply excuses the task from execution for a specified number of clock ticks.
- A crucial aspect of any real-time executive is how it is accessed from an
- interrupt to signal events to task-level code. SSX provides two calls which
- are used to frame the interrupt handler code. ssx_lock is called right after
- the interrupt handler saves the necessary CPU register. ssx_lock disables task
- switching so that the executive doesn't try to switch out the interrupt
- handler before it has completed its processing. ssx_unlock is called at the
- end of the interrupt handler right before it restores the CPU registers. This
- allows task switching to take place again.
- It should be noted that for extra efficiency you can skip both these calls if
- the interrupt handler meets the following criteria:
- It only makes one ssx_alert call.
- All hardware processing is done before the call to ssx_alert (e.g. the
- interrupt controller has been reset and any other steps necessary to clear the
- interrupt at the requestor have been taken).
- The idea is that at that point, the interrupt thread has just become a
- continuation of the task that was running. The actual interrupt return can now
- happen at any time.
- The ssx_lock and ssx_unlock calls can also be used from the task level to
- temporarily disable rescheduling. If a task is going to do something
- time-consuming enough that it doesn't want to risk masking interrupts, but
- can't afford to be switched out while performing the specific activities, it
- can call ssx_lock to lock control of the CPU. Interrupts can still be handled,
- but any changes they make to the task state will not be examined until the
- ssx_unlock call is invoked.
- For a complete list of function calls see Table 1.
-
-
- Porting SSX
-
-
-
- SSX currently works with Borland C and C++ compilers in the small model. SSX
- will need porting to a different memory model or an environment other that
- MS-DOS. To port SSX from the MS-DOS environment to a new one you must port
- four functions. They are:
- ssx_task_create
- stack_swap
- disable_ints
- enable_ints
- A task is created by a call to ssx_task_create before or after ssx_run is
- called. You must have at least one task created before calling ssx_run. When a
- task is created a stack space is allocated. This stack is then set up so that
- when the task's stack pointer is swapped in, a simple return from stack_swap
- will start the task off. Figure 1 shows what the newly-created task's stack
- looks like after being created for the MS-DOS executive.
- This sets the task up to be run for the first time. The code
- /* stack_swap - switch from stack of current task
- * to stack of new task
- */
-
- LOCAL void
- stack_swap(unsigned int **old_stack_ptr,
- unsigned int **new_stack_ptr)
- {
- asm or di,di /* fool compiler into saving */
- asm or si,si /* di and si registers */
-
- /* save stack ptr of old task */
- *old_stack_ptr = (unsigned int *)_SP;
- /* load stack ptr register with stack ptr */
- /* of new task */
- _SP=(unsigned int)*new_stack_ptr;
- }
- shows the C listing of stack_swap. On entry, the code executes two lines of
- in-line assembly language. These instructions make sure the compiler saves and
- restores the two register variables, 80X86 registers di and si. The last two
- instructions use the pseudo-variable _SP. With Borland C compilers _SP allows
- you to directly access the 80X86 sp (stack pointer) register. This part of the
- code stores the stack pointer of the old task and puts the stack pointer of
- the new task in the sp register. This is all the code has to do to switch
- tasks.
- The code
- stack_swap proc near
-
- ; prologue for a C function
- push bp
- mov bp,sp
- push si
- push di
-
- ; this fools compiler into saving
- ; si and di because
- ; they are register variables
- or di,di
- or si,si
-
- ; save stack pointer for old task
- mov bx,word ptr [bp+4]
- mov word ptr [bx],sp
-
- ; load stack ptr register with stack
- ; ptr of new task
- mov bx,word ptr [bp+6]
- mov sp,word ptr [bx]
-
- ; epilogue for a C function
- pop di
- pop si
- pop bp
- ret
- stack_swap endp
- shows an 80X86 assembly-language listing of the C function stack_swap. In the
- epilogue of the function stack_swap, registers di, si, and bp are popped off
- the stack. We have placed these values on the task's stack during
- ssx_task_create. The last instruction is ret which will pop the return address
- off the stack and execute the function run_new_task. run_new_task enables
- interrupts and runs the new task by calling a function pointer. The code
- /* run_new_task - starts up a new
- * task making sure interrupts are
-
- * enabled
- */
-
- LOCAL void
- run_new_task(void)
- {
- ints_on();
- (t_current-task_ptr)();
- }
- shows the function run_new_task.
- disable_ints is a routine that gets the current state of interrupts and then
- disables interrupts and returns the previous state of interrupts. In the
- MS-DOS version of SSX this function is coded as in-line assembly language
- code. The pseudocode
- disable_ints(void)
- {
- save current state of interrupts
- (enabled or disabled);
- disable interrupts;
- returned saved state of interrupts
- (positive integer if they were enabled
- 0 is disabled)
- }
- indicates what is necessary to port disable_ints to other environments.
- enable_ints enables interrupts. In the MS-DOS version of this function we have
- used a Turbo C macro that places an 80X86 sti instruction in the code.
-
-
- SSX Demo Code
-
-
- The file demo.c (Listing 4) demonstrates many of the calls in SSX. This MS-DOS
- demo program sets up a timer interrupt and then creates several tasks. The
- timer interrupt handler uses vector 8 on the MS-DOS PC. This vector is called
- 18.2 times a second. Each time the interrupt routine is called ssx_clock_tick
- is called. This is one area of the code that is not portable. On an MS-DOS AT
- PC you could also use the interrupt that is called 1,024 times a second if you
- need more resolution than this timer supplies.
- After the timer handler is setup the demo program sets up several tasks. Here
- is a description of the tasks that are made.
- Print queue tasks--These tasks increment a counter (one for each task) and
- print the value of the counter. These tasks use a semaphore that allows only
- one task at a time to call cprintf since it is not reentrant. The code
- /* initialize semaphore to having
- * waiting message */
- #define SET_SEMAPHORE(wqptr) \
- (wqptr)-mesg_flg=1; \
- (wqptr)-task_ptr=NULL
-
- /* initialize wait_q to NULL task_ptr
- * and no */
- /* message waiting */
- #define INIT_WAIT_Q(wqptr) \
- (wqptr)-mesg_flg=0; \
- (wqptr)-task_ptr=NULL
- contains macros to setup semaphores and wait queues. Many Standard C functions
- are not reentrant, so keep that in mind when using C library functions. Five
- of these tasks are created.
- Time-slice tasks--These tasks get a full time slice to increment a counter
- (one for each task). Since print-queue tasks have to get permission to print
- each time through, their counters will increment slower than these time-slice
- tasks. Five of these tasks are created.
- Print-time-slice task--This task prints the counter values for each time-slice
- task once every three seconds. This task must also use our print semaphore
- each time it needs to print.
- Keypress task--This task checks for a keypress every two seconds and calls
- ssx_stop if it finds one.
- System-time task--This prints the system time in seconds every second. This
- task must also use our print semaphore each time it needs to print.
- This code has been tested with Turbo C 2.0, Turbo C++ 1.0, and Borland C 3.1.
- To make the demo program type:
- tcc demo.c ssx.c
- or
- bcc demo.c ssx.c
- Since this code uses in-line assembly language you will also need tasm if you
- are using Turbo C 2.0 or Turbo C++ 1.0.
- We hope you will find this executive portable and easy to use. For many
- applications this will have more than enough features. If you find it is
- missing something you need, no big deal. Change it. It's easy. It's all in C.
- Figure 1
- Table 1 SSX Function Calls
- -----------------------------------------------------------------------
- int ssx_alert(wait_q ssx_alert sends a message to a task waiting
- *wqptr) on the specified wait_q. If there is already
- a message waiting on the specified wait_q,
-
- then a MW_ERR (see ssx.h) is returned. If
- there are any tasks waiting on the wait_q,
- the first task is scheduled if it has a high
- enoughpriority. If no task is waiting then a
- message is left on the wait_q.
-
- int ssx_init(void) ssx_init is called before creating tasks or
- running the SSX executive. This initializes
- SSX executivedata.
-
- int ssx_task_create ssx_task_create is called to create a task to
- (unsigned char task_pri, run with the SSX executive. Following is list
- unsigned char task_id, of parameters passed to ssx_task_create:
- fptr task_ptr, unsigned name -- an eight-character name for the
- int stack_size, char task. This is useful in debugging SSX
- *name) applications. The size of name is setup in
- ssx_conf.h. stack_size -- the size of the
- stack for this task in bytes. In our demo
- application we are using 0x200. This will
- depend on your application. Start high if you
- think you will be deeply nested or you are
- using a lot of automatic variables.
- task_id -- the ID for the task being created.
- IDs range from 1 to 0xfe. ID 0xff is reserved
- for a background task that SSX creates. ID 0
- is used in some SSX calls when a task wants
- to refer to itself.
- task_pri -- the task priority. Tasks with the
- highest priority are scheduled to run first.
- Priorities range from 0 (highest priority) to
- 0xff (lowest priority).
- task_ptr -- a pointer to a function. (Its
- typedef is in ssx.h.) Pass a pointer to the C
- function that is to be used as a task. A
- function used as a task must have no
- parameters passed and return no value. A task
- function is generally a loop without exit.
-
- int ssx_task_delete ssx_task_delete deletes the task with the
- (unsigned char task_id) task_id passed. If you pass in ID 0, the
- calling task is deleted.
-
- int ssx_wait_with_alarm ssx_wait_with_alarm causes a task to wait for
- (wait_q *wqptr, long a message for the number of ticks passed. If
- timeout) no message is received within the timeout
- number of ticks, a TO_ERR is returned (see
- ssx.h). ssx_wait_with_alarm returns SUCCESS
- (see ssx.h) if the task gets a message on the
- wait_q.
-
- long ssx_get_time(void) ssx_get_time gets the current clock ticks in
- SSX.
-
- unsigned char ssx_change_priority changes the priority of
- ssx_change_priority the calling task, but does not immediately
- (unsigned char) reschedule tasks.
- new_priority
-
- void ssx_clock_tick(void) ssx_clock_tick is called to inject a clock
-
- tick into SSX. This is usually called by a
- timer interrupt handler.
-
- void ssx_lock(void) ssx_lock disables task switching. Called
- either at the entry to an interrupt handler
- to prevent the interrupt handler from getting
- switched out or from task code to reserve
- control of the CPU for a while. Note: a task
- cannot wait, delay, or otherwise cause itself
- to become unrunnable while it has task
- switching locked.
-
- void ssx_run(void) ssx_run is called after you have created at
- least one task and started the executive
- running.
-
- void ssx_set_time(long ssx_set_time sets the number of clock ticks
- ticks) in SSX to the number passed in.
-
- void ssx_stop(void) ssx_stop is called to shutdown the SSX
- executive. It returns where ssx_run was
- called.
-
- void ssx_switch(void) ssx_switch forces rescheduling of tasks by
- SSX. ssx_switch is usually called from within
- the executive. It schedules the highest
- priority task in the front of the ready
- queue.
-
- void ssx_task_delay(long ssx_task_delay delays the calling task for
- ticks) the number of ticks passed. It will be
- scheduled back in after the number of ticks
- have passed and when it is highest priority
- on the ready queue.
-
- void ssx_unlock(void) ssx_unlock re-enables task switching. A
- matching call to ssx_lock must already have
- been made. Called at the exit of an interrupt
- handler, or from task level code to release
- the CPU to other tasks.
-
- void ssx_wait(wait_q ssx_wait causes a task to wait for a message
- *wqptr) on the passed wait_q. The typedef for a
- wait_q data structure is in the file ssx.h.
- If there is a message waiting, ssx_wait
- returns immediately. Ifthere is no message
- waiting, ssx_wait schedules a new task while
- the calling task waits for a message.
-
- Listing 1 SSX.C -- Stack Swap eXecutive
- /****************************************************/
- /* By Tom Green and Dennis Cronin */
- /* 10/19/92 */
- /****************************************************/
-
- /* turn on inline asm */
- #pragma inline
-
- #include <alloc.h>
-
- #include <dos.h>
- #include <string.h>
- #include <setjmp.h>
- #include "ssx.h"
- #include "ssx_conf.h"
-
- /* Task Control Block */
-
- typedef struct tcb {
- /* task chain forward ptr */
- struct tcb *forw;
- /* task chain backward ptr */
- struct tcb *back;
- /* delay chain forward ptr */
- struct tcb *dforw;
- /* delay chain backward ptr */
- struct tcb *dback;
- /* pointer to task code */
- fptr task_ptr;
- /* pointer to start of allocated stack */
- unsigned int *stack;
- /* task's current stack pointer */
- unsigned int *stack_ptr;
- /* delay counter */
- long timeout;
- /* flag for task timed out */
- unsigned char timedout;
- /* flag for TCB in use */
- unsigned char active;
- /* status flags */
- unsigned char status;
- /* task priority */
- unsigned char priority;
- /* task ID */
- unsigned char id;
- /* for storing extra task context */
- char context[CNTXT_SZ];
- } tcb;
-
- /* misc. defines */
- #define TRUE 1
- #define FALSE 0
-
- /* background task defines */
- #define BG_TASK_ID 0xff
- #define BG_TASK_PRI 0xff
-
- /* make data and code local to this file */
- #define LOCAL static
-
- /* flags for the TCB status word */
- #define T_READY 0 /* ready to run */
- #define T_WAITING 1 /* waiting on wait_q */
- #define T_DELAYED 2 /* delay timer running */
-
- /* local function prototypes */
-
- LOCAL tcb *get_tcb(void);
- LOCAL void free_tcb(tcb *tbp);
-
- LOCAL void put_ready(tcb *tbp);
- LOCAL void rotate_tasks(tcb *tbp);
- LOCAL void put_delay(long timeout);
- LOCAL void run_new_task(void);
- LOCAL void bg_task(void);
- LOCAL void stack_swap(unsigned int **old_stack_ptr,
- unsigned int **new_stack_ptr);
- LOCAL int disable_ints(void);
-
- /* local variables */
-
- LOCAL long sys_time; /* system timer */
- LOCAL unsigned char slice_cnt;
- LOCAL int running;
- LOCAL int initd;
- LOCAL jmp_buf jbuf;
- LOCAL int switch_lock;
-
- /* task control */
- LOCAL tcb t_pool[MAX_TASKS + 1]; /* pool of TCBs */
- LOCAL tcb t_ready; /* head of ready task queue */
- LOCAL tcb t_null; /* the NULL task */
- LOCAL tcb *t_free; /* head of free queue */
- LOCAL tcb *t_current; /* pointer to current task */
-
- /* delay control */
- LOCAL tcb d_chain; /* q head for delayed tasks */
-
- /* MACROS to unlink from task and delay queues */
-
- /* t_unlink - must be used w/ interrupts off */
- #define t_unlink(tbp) \
-
- { \
- (tbp)->back->forw = (tbp)->forw; \
- if((tbp)->forw != NULL) \
- (tbp)->forw->back = (tbp)->back; \
- }
-
- /* d_unlink - must be used w/ interrupts off */
- #define d_unlink(tbp) \
- { \
- (tbp)->dback->dforw = (tbp)->dforw; \
- if((tbp)->dforw != NULL) \
- (tbp)->dforw->dback = (tbp)->dback; \
- }
-
- /* ssx_init - init ssx data */
-
- int
- ssx_init(void)
- {
- int i;
- tcb *tcbp;
-
- if(initd)
- return(INIT_ERROR);
-
- memset(&d_chain,0,sizeof(d_chain));
-
-
- /* init TCB free queue links */
- for(i=0,tcbp=t_pool;i < MAX_TASKS-1;i++,tcbp++){
- tcbp->forw = &t_pool[i+1];
-
- }
- t_pool[i].forw = NULL;
-
- for(i = 0;i < MAX_TASKS;i++){
- t_pool[i].active=FALSE;
- }
-
- t_current = NULL;
- t_free = t_pool;
- t_ready.forw = NULL;
- switch_lack = 0;
-
- /* set up background task */
- if((ssx_task_create(BG_TASK_PRI,BG_TASK_ID,
- bg_task,0x200, "bg_task"))
- != SUCCESS)
- return(INIT_ERROR);
-
- initd = TRUE;
-
- return(SUCCESS);
- }
-
- /* sx_run - this starts executive */
-
- void
- ssx_run(void)
- {
- int val;
-
- val = setjmp(jbuf);
-
- if(val != 0)
- return;
-
- slice_cnt = 0;
- sys_time = 0;
-
- /* make current task ptr point to dummy tcb so
- * beginning of time stack pointer save will
- * have a safe place to save to. */
-
- t_current = &t_null;
-
- /* mark SSX as active */
- running = TRUE;
-
- /* this will start the first task rolling */
- ssx_switch( );
- }
-
- /* sx_stop - this stops executive */
-
- void
-
- ssx_stop(void)
- {
- int i;
- int_state_var istate;
-
- ints_off(istate);
-
- /* free any allocated stacks */
- for(i = 0; i < MAX_TASKS; i++){
- if(t_pool[i].stack != NULL){
- free(t_pool[i].stack);
- t_pool [i].stack = NULL;
- }
- }
- initd = FALSE;
- running = FALSE;
-
- restore_ints(istate);
-
- longjmp(jbuf,1);
- }
-
- /* ssx_task_create - create task and set up tcb */
-
- int
- ssx_task_create(unsigned char task_pri,
- unsigned char task_id,fptr task_ptr,
- unsigned int stack_size,char *name)
- {
- unsigned int i;
- tcb *tbp;
- int_state_var istate;
-
- ints_off(istate);
-
- if(task_id == 0) {
- restore_ints(istate);
- return(TID_ERR);
- }
-
- /* check for TID already in use */
- for(i = 0,tbp = t_pool;i < MAX_TASKS;i++,tbp++) {
- if(tbp->active && tbp->id == task_id) {
- restore_ints(istate);
- return(TID_ERR);
- }
- }
-
- if((tbp = get_tcb()) == NULL) { /* get a tcb */
- restore_ints(istate);
- return(TCB_ERR);
- }
-
- /* allocate stack for this task */
- if((tbp->stack = (unsigned int *)
- malloc(stack_size)) == NULL){
- restore_ints(istate);
- return(STACK_ERR);
- }
-
-
- /* fill in the blanks */
- strncpy(tbp->context,name,CNTXT_SZ);
- tbp->priority = task_pri;
- tbp->id = task_id;
- tbp->status = T_READY;
- tbp->timedout = FALSE;
- tbp->timeout = OL;
- tbp->forw = tbp->back =
- tbp->dforw = tbp->dback = NULL;
-
- tbp->task_ptr = task_ptr;
-
- tbp->stack_ptr = (unsigned int *)(tbp->stack +
- (stack_size / 2));
-
- /* setup task stack to have address of start up
- * routine and fake di, si, bp registers to pop.
- * This part is not portable. the stack looks
- * like this:
- *
- * - high
- * address of run_new_task
- * -
- * bp
- * ---------------------
- * si
- * -
- * di
- * - low */
-
- *(-tbp->stack_ptr) = (unsigned int)run_new_task;
- *(-tbp->stack_ptr) = 0; /* fake BP,SI,DI */
- *(-tbp->stack_ptr) = 0; /* on stack */
- *(--tbp->stack_ptr) = 0;
-
- /* put on active chain */
- rotate_tasks(tbp);
-
- ssx_switch();
-
- restore_ints(istate);
-
- return(SUCCESS);
- }
-
- /* ssx_task_delay - cause task to delay for number of ticks */
-
- void
- ssx_task_delay(long timeout)
- {
- int_state_var istate;
-
- ints_off(istate);
-
- if(timeout == 0) {
- ssx_switch();
- restore_ints(istate);
- return;
-
- }
-
- put_delay(timeout); /* put current task on */
- /* delay queue */
- t_unlink(t_current); /* take off ready queue */
-
- ssx_switch();
- restore_ints(istate);
- }
-
- /* ssx_task_delete - delete a task and remove from queues */
-
- int
- ssx_task_delete(unsigned char task_id)
- {
- unsigned int i;
- tcb *tp;
- int_state_var istate;
-
- ints_off(istate);
-
- /* look for background task ID */
- if(task_id == BG_TASK_ID){
- restore_ints(istate);
- return(TID_ERR);
- }
-
- /* look for 'self' form */
- if(task_id == 0) {
- if(t_current) {
- /* get current task's id */
- task_id = t_current->id;
- }
- }
-
- /* brute force, search all TCBs for matching ID */
- for(i = 0,tp = t_pool;i < MAX_TASKS;i++,tp++) {
- if(tp->active && tp->id == task_id) {
- break;
-
- }
- }
-
- /* see if found match */
- if(i == MAX_TASKS){
- restore_ints(istate);
- return(TID_ERR);
- }
-
- switch(tp->status & (T_DELAYED T_WAITING)) {
- case T_DELAYED:
- d_unlink(tp); /* remove from delay q */
- break;
- case T_WAITING:
- t_unlink(tp); /* remove from some */
- /* wait_q */
- break;
- case T_DELAYED T_WAITING:
- t_unlink(tp); /* remove from some */
-
- /* wait_q */
- d_unlink(tp); /* remove from delay q */
- break;
- case T_READY:
- t_unlink(tp); /* remove from ready q */
- break;
- }
-
- free(tp->stack); /* free allocated stack */
- tp->stack = NULL;
- free_tcb(tp); /* free up the TCB */
-
- ssx_switch();
- restore_ints(istate);
- return(SUCCESS);
- }
-
- /* ssx_change_priority - change priority for currently
- * running task,
- * don't immediately reschedule
- * returns old priority */
-
- unsigned char
- ssx_change_priority(unsigned char new_priority)
- {
- unsigned char old_priority;
- int_state_var istate;
-
- ints_off(istate);
- old_priority = t_current->priority;
- t_current->priority = new_priority;
- t_unlink(t_current);
- rotate_tasks(t_current);
- restore_ints(istate);
- return(old_priority);
- }
-
- /* ssx_wait - wait on wait_q. reschedule later */
-
- void
- ssx_wait(wait_q *wqptr)
- {
-
- tcb *tp;
- tcb *t_cur;
- int_state_var istate;
-
- ints_off(istate);
-
- /* check for message flag already set */
- if(wqptr->mesg_flg){
- wqptr->mesg_flg = FALSE;
- restore_ints(istate);
- return;
- }
-
- t_cur = t_current;
- t_unlink(t_cur); /* take off ready queue */
-
-
- tp = (tcb *)&wqptr->task_ptr;
-
- /* find where to insert waiting task into
- * wait queue */
- while((tp->forw) != NULL) {
- if(t_cur->priority <= tp->forw->priority)
- break;
- tp = tp->forw;
- }
-
- /* insert into queue */
- if((t_cur->forw = tp->forw)!= NULL)
- t_cur->forw->back = t_cur;
-
- tp->forw = t_cur;
- t_cur->back = tp;
- t_cur->status = T_WAITING;
-
- ssx_switch();
- restore_ints(istate);
- }
-
- /* ssx_wait_with alarm - wait on wait_q with alarm.
- * reschedule now */
-
- int
- ssx_wait_with_alarm(wait_q *wqptr,long timeout)
- {
- tcb *tp;
- tcb *t_cur;
- int_state_var istate;
-
- ints_off(istate);
-
- /* check for message flag already set */
- if(wqptr->mesg_flg){
- wqptr->mesg_flg = FALSE;
- restore_ints(istate);
- return(SUCCESS);
- }
-
- t_cur = t_current;
- t_unlink(t_cur); /* take off ready queue */
-
- tp = (tcb *)&wqptr->task_ptr;
-
- /* find where to insert waiting task into wait queue */
- while((tp->forw) != NULL) {
- if(t_cur->priority <= tp->forw->priority)
- break;
- tp = tp->forw;
- }
-
- /* insert into queue */
- if((t_cur->forw = tp->forw) != NULL)
- t_cur->forw->back = t_cur;
- tp->forw = t_cur;
- t_cur->back = tp;
- t_cur->status = T_WAITING;
-
-
- /* if there is timeout value, put task on delay queue */
- if(timeout)
- put_delay(timeout);
-
- ssx_switch();
-
- /* we were scheduled back in so
- * see if task timed out and return error */
-
- if(t_cur->timedout){
- t_cur->timedout = FALSE;
- restore_ints(istate);
- return(TO_ERR);
- }
-
- restore_ints(istate);
-
- /* task did not time out, so return success */
- return(SUCCESS);
- }
-
- /* ssx_alert - alert wait_q. reshedule if task
- * alerted is equal or higher
- * priority than current task */
-
- int
- ssx_alert(wait_q *wqptr)
- {
- tcb *np;
- tcb *oldtcb;
- int_state_var istate;
-
- ints_off(istate);
-
- /* check for message waiting */
- if(wqptr->mesg_flg){
- restore_ints(istate); /* cannot alert if */
- return(MW_ERR); /* messgae is waiting */
- }
-
- np=(tcb *)wqptr->task_ptr;
-
- /* check if there is a task waiting on wait_q */
- if(np != NULL){
- t_unlink(np);
- if(np->status & T_DELAYED)
- d_unlink(np);
- np->status &= ~(T_WAITING T_DELAYED);
- put_ready(np);
- /* switch to waiting task if it is equal
- * or higher priority */
- if(np->priority <= t_current->priority){
- oldtcb = t_current;
- t_current = np;
- /* check and see if scheduling is disabled */
- if(switch_lock == 0)
- stack_swap(&oldtcb->stack_ptr
- ,&np->stack_ptr);
-
- }
- restore_ints(istate);
- return(SUCCESS);
- }
-
- /* fell thru, simply leave message in wait_q */
- wqptr->mesg_flg = TRUE;
- restore_ints(istate);
- return(SUCCESS);
- }
-
- /* ssx_clock_tick - call this to update ssx clock from
- * timer interrupt handler */
-
- void
- ssx_clock_tick(void)
- {
- tcb *tp;
- int_state_var istate;
-
- ints_off(istate);
-
- if(running == FALSE){
- restore_ints(istate);
- return;
- }
-
- sys_time++;
-
- /* do time updates */
- tp = (tcb *)&d_chain;
- /* check for timed out tasks */
- while((tp = tp->dforw) != NULL) {
- if((sys_time - tp->timeout) >= 0) {
- d_unlink(tp); /* this one's ready */
- tp->timedout = TRUE;
- if(tp->status & T_WAITING)
- t_unlink(tp);
- tp->status = T_READY;
- /* put task on ready queue */
- rotate_tasks(tp);
- }
- else
- break; /* passed the ready ones */
- }
-
- /* round robin rotation */
- if((++slice_cnt) == TIME_SLICE){
- slice_cnt = 0;
- /* if task is running and was left ready */
- if(t_current && t_current->status
- == T_READY){
- /* remove from ready queue */
- t_unlink(t_current);
- /* puts at back of pri group */
- rotate_tasks(t_current);
- }
- }
-
-
- ssx_switch();
- restore_ints(istate);
- }
-
- /* ssx_set_time - this sets SSX system time */
-
- void
- ssx_set_time(long time)
- {
- int_state_var istate;
-
- ints_off(istate);
- sys_time = time;
- restore_ints(istate);
- }
-
- /* ssx_get_time - this returns SSX system time */
-
- long
- ssx_get_time(void)
- {
- return(sys_time);
- }
-
- /* ssx_lock - disable task switching */
-
- void
- ssx_lock(void)
- {
- int_state_var istate;
-
- ints_off(istate);
- switch_lock++;
- restore_ints(istate);
- }
-
- /* ssx_unlock - enable task switching */
-
- void
- ssx_unlock(void)
- {
- int_state_var istate;
-
- ints_off(istate);
- /* call ssx_switch if we are not nested */
- if(-switch_lock == 0)
- ssx_switch();
- restore_ints(istate);
- }
-
- /* ssx_switch - run next ready task
- * notes: there must always be a runnable task w/
- * SSX. a background task is created by ssx_init
- * and this task can never wait or do anything
- * that would remove it from the active queue.
- * this saves checks in this routine, making
- * it more efficient. */
-
- void
-
- ssx_switch(void)
- {
- tcb *oldtcb;
-
- if(!running)
- return;
-
- oldtcb = t_current;
-
- /* switch tasks */
- t_current = t_ready.forw; /* get next */
- /* ready task */
- /*
- * if new task is same as old, do not
- * bother with switch
- */
- if(t_current == oldtcb)
- return;
-
- /* check and see if scheduling is disabled */
- if(switch_lock == 0){
- /* we have a new task so do a task switch */
- stack_swap(&oldtcb->stack_ptr,
- &t_current->stack_ptr);
- }
- }
-
- /* get_tcb - get task control block */
-
- LOCAL tcb *
- get_tcb(void)
- {
- tcb *tbp;
-
- if(t_free == NULL)
- return(NULL);
- tbp = t_free;
- t_free = tbp->forw;
- tbp->active = TRUE;
-
- return(tbp);
- }
-
- /* free_tcb - free task control block */
-
- LOCAL void
- free_tcb(tcb *tbp)
- {
- /* '****' for debug TCB not in use */
- tbp->timeout = 0x2a2a2a2aL;
- tbp->active = FALSE;
- tbp->forw = t_free;
- t_free = tbp;
- }
-
- /* put_ready - put task at head of ready queue */
-
- LOCAL void
- put_ready(tcb *tbp)
-
- {
- tcb *tp,*np;
- unsigned int priority;
-
- /* get priority of task to be inserted in chain */
- priority = tbp->priority;
-
- /* put on the active chain */
- tp = (tcb *)&t_ready;
- /* sort in order of decreasing priority, put at
- * head of pri group */
- while((np = tp->forw) != NULL && np->priority
- < priority)
- tp = np;
- /* link in */
- tbp->forw = np;
- tbp->back = tp;
- tp->forw = tbp;
- if(np != NULL)
- np->back = tbp;
- }
-
- /* rotate_tasks - put task at back of ready queue */
-
- LOCAL void
- rotate_tasks(tcb *tbp)
- {
-
- tcb *tp,*np;
- unsigned int priority;
-
- /* get priority of task to be inserted in chain */
- priority = tbp->priority;
-
- /* put on the active chain */
- tp = (tcb *)&t_ready;
- /* sort in order of decreasing priority,
- * put at back of pri group */
- while((np = tp->forw) != NULL && np->priority
- <= priority)
- tp = np;
- /* link in */
- tbp->forw = np;
- tbp->back = tp;
- tp->forw = tbp;
- if(np != NULL)
- np->back = tbp;
- }
-
- /* put_delay - put task on delay queue */
-
- LOCAL void
- put_delay(long timeout)
- {
- tcb *tp,*np;
-
- t_current->timeout =
- timeout + sys_time; /* actual time ready */
- t_current->timedout=FALSE;
-
- t_current->status = T_DELAYED;
- tp = (tcb *)&d_chain;
- /* sort in order increasing target time */
- /* trick to solve wrap of sys_time */
- while((np = tp->dforw) != NULL) {
- if(timeout <= np->timeout - sys_time)
- /* hit a more future one */
- break;
- tp = np;
- }
- /* link in */
- t_current->dforw = np;
- t_current->dback = tp;
- tp->dforw = t_current;
- if(np != NULL)
- np->dback = t_current;
- }
-
- /* run_new_task - starts up a new task making sure
- * interrupts are enabled */
-
- LOCAL void
- run_new_task(void)
- {
- ints_on();
- (t_current->task_ptr) ();
- }
-
- /* bg_task - must have a background task */
-
- LOCAL void
- bg_task(void)
- {
- while(1);
- }
-
- /* WARNING - routines from here on are not portable */
-
- /* stack_swap - switch from stack of curent task to
- * stack of new task */
-
- LOCAL void
- stack_swap(unsigned int **old_stack_ptr,
- unsigned int **new_stack_ptr)
- {
- asm or di,di /* fool compiler into saving */
- asm or si,si /* di and si registers */
-
- /* save stack pointer of old task from sp reg */
- *old_stack_ptr = (unsigned int *)_SP;
-
- /* load sp reg with stack pointer of new task */
- _SP=(unsigned int)*new_stack_ptr;
- }
-
- /* disable_ints - disable interrupts and return state
- * of interrupts before before they
- * were disabled. Returns positive
- * integer if they were enabled */
-
-
- LOCAL int
- disable_ints(void)
- {
- asm pushf /* save flags to get */
- /* interupt status */
- asm cli /* interrupts off */
- asm pop ax /* get interrupt state from */
- /* flags that were pushed */
- asm and ax,0200h /* and flags to get interrupt */
- /* status */
- return(_AX);
- }
- /* End of File */
-
-
- Listing 2 SSX.H -- definitions for SSX
- /****************************************************
- /* By Tom Green and Dennis Cronin */
- /* 10/19/92 */
- /****************************************************/
-
- /* function pointer */
- typedef void (*fptr)(void);
-
- /* this is a wait_q structure */
- typedef struct wait_q{
- void *task_ptr;
- int mesg_flg;
- }wait_q;
-
- /* SSX prototypes */
- int ssx_init(void);
- void ssx_run(void);
- void ssx_stop(void);
- int ssx_task_create(unsigned char task_pri,
- unsigned char task_id,fptr task_ptr,
- unsigned int stack_size,char *name);
- void ssx_task_delay(long ticks);
- int ssx_task_delete(unsigned char task_id);
- unsigned char ssx_change_priority(unsigned char
- new_priority);
- void ssx_wait(wait_q *wqptr);
- int ssx_wait_with_alarm(wait_q *wqptr,long timeout);
- int ssx_alert(wait_q *wqptr);
- void ssx_clock_tick(void);
- void ssx_set_time(long ticks);
- long ssx_get_time(void);
- void ssx_lock(void);
- void ssx_unlock(void);
- void ssx_switch(void);
-
- /* SSX status codes */
-
- #define SUCCESS 0
- /* task ID error */
- #define TID_ERR 1
- /* message waiting error */
- #define MW_ERR 2
-
- /* no TCBs error */
- #define TCB_ERR 3
- /* could not allocate stack for task */
- #define STACK_ERR 4
- /* task timed out (wait_with_alarm) */
- #define TO_ERR 5
- /* error initializing SSX */
- #define INIT_ERROR 6
-
- /* initialize semaphore to having waiting message */
- #define SET_SEMAPHORE(wqptr) (wqptr)->mesg_flg=1; \
- (wqptr)->task_ptr=NULL
-
- /* initialize wait_q to NULL task_ptr and no
- * message waiting */
- #define INIT_WAIT_Q(wqptr) (wqptr)->mesg_flg=0; \
- (wqptr)->task_ptr=NULL
- /* End of File */
-
-
- Listing 3 SSX_CONF.H -- configuration info for SSX
- /*****************************************************/
- /* By Tom Green and Dennis Cronin */
- /* 10/19/92 */
- /*****************************************************/
-
- /* main config params */
-
- /* maximum number of tasks */
- #define MAX_TASKS 24
- /* 1 tick per slice */
- #define TIME_SLICE 1
- /* number of bytes of context info */
- #define CNTXT_SZ 8
-
- /* enable interrupts - this will have to be ported
- * in other environments */
- #define enable_ints enable
-
- /* variable type to keep interrupt status in */
- typedef
- int int_state_var;
- /* save off current state, disable all interrupts */
- #define ints_off(isv) isv=disable_ints()
- /* explicitly enable interrupts */
- #define ints_on() enable_ints()
- /* reload previous interrupt enables */
- #define restore_ints(isv) { if(isv) enable(); }
- /* End of File */
-
-
- Listing 4 DEMO.C -- demo code for SSX
- *****************************************************/
- /* By Tom Green and Dennis Cronin */
- /* 10/19/92 */
- /****************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
-
- #include <dos.h>
- #include <bios.h>
- #include <conio.h>
- #include "ssx.h"
-
- /* vector for PC timer interrupt */
- #define TIMER_INT_VEC 8
- /* 18.2 clock ticks per second on PC */
- #define TICKS_PER_SECOND 18L
-
- #define NUM_PQ_TASKS 5
- #define NUM_TS_TASKS 5
-
- void key_task(void);
- void print_ts_count_task(void);
- void sys_time_task(void);
- void ts_task(void);
- void print_q_task(void);
- void interrupt tick_handler(void);
-
- unsigned int pq_task_counter=0;
- unsigned int ts_task_counter=0;
- wait_q print_q;
- void interrupt (*old_timer_int)(void);
- unsigned long pq_count[NUM_PQ_TASKS];
- unsigned long ts_count[NUM_TS_TASKS];
-
- void main(void)
-
- {
- unsigned int i;
-
- clrscr();
-
- pq_task_counter=0;
- ts_task_counter=0;
-
- if((ssx_init()) == INIT_ERROR){
- printf("\nError initting SSX");
- exit(1);
- }
-
- /* set up timer tick interrupt handler */
- old_timer_int = getvect(TIMER_INT_VEC);
- setvect(TIMER_INT_VEC,tick_handler);
-
- /* install print q tasks */
- for(i = 1;i <= NUM_PQ_TASKS;i++){
- if((ssx_task_create(1,i,print_q_task,0x200,
- "task"))!=SUCCESS)
- cprintf("\nError creating task %d",i);
- }
- /* install time slice tasks */
- for(i = NUM_PQ_TASKS + 1;i <= (NUM_TS_TASKS +
- NUM_PQ_TASKS);i++){
- if((ssx_task_create(1,i,ts_task,0x200,
- "ts_task"))!=SUCCESS)
- cprintf("\nError creating ts task");
- }
-
-
- /* install print ts count task */
- if((ssx_task_create(1,22,print_ts_count_task,0x200,
- "pts_task"))!=SUCCESS)
- cprintf("\nError creating ts count task");
- /* install keypress task */
- if((ssx_task_create(1,21,key_task,0x200,
- "key_task"))!=SUCCESS)
- cprintf("\nError creating keypress task");
- /* install system time task */
- if((ssx_task_create(1,23,sys_time_task,0x200,
- "st_task"))!=SUCCESS)
- cprintf("\nError creating system time task");
- cprintf("\r\nDone creating tasks");
- cprintf("\r\nPress key to start SSX");
- bioskey(0);
- clrscr();
- SET_SEMAPHORE(&print_q);
- ssx_run();
-
- /* reset to old timer int handler */
- setvect(TIMER_INT_VEC,old_timer_int);
-
- clrscr();
- }
-
- /* ts_task - time slice task. this task gets a full
- * time slice to increment counter.
- * print_ts_count_task prints counters. */
-
- void ts_task(void)
-
- {
- int task_num;
-
- task_num = ts_task_counter++;
- ts_count[task_num] = 0L;
-
- while(1){
- ts_count[task_num]++;
- }
- }
-
- /* print_q_task - this task waits for print q we have
- * set up and then increments counter
- * and prints and then releases print q */
-
- void print_q_task(void)
- {
- unsigned int y,task_num;
-
- task_num = pq_task_counter++;
- y = task_num + 1;
- pq_count[task_num] = 0L;
-
- while(1){
- ssx_wait(&print_q);
- pq_count[task_num]++;
- gotoxy(1,y);
-
- cprintf("Task %02d %08lx",task_num,
-
- pq_count[task_num]);
- ssx_alert(&print_q);
- }
- }
-
- /* print_ts_count_task - task that prints counts
- * from time sliced tasks
- * once every 3 seconds */
-
- void print_ts_count_task(void)
- {
- int task_num;
-
- while(1){
- ssx_wait(&print_q);
- for(task_num = 0;task_num < NUM_TS_TASKS;
- task_num++){
- gotoxy(1,task_num + NUM_TS_TASKS + 1);
- cprintf("TS Task %02d %08lx",task_num,
- ts_count[task_num]);
- }
- ssx_alert(&print_q);
- ssx_task_delay(3 * TICKS_PER_SECOND);
- }
- }
-
- /* key_task - task that checks for keypress every
- * 2 seconds. calls ssx_stop when key
- * is pressed */
-
- void key_task(void)
- {
- while(1){
- if((bioskey(1)) != 0){
- bioskey(0);
- ssx_stop();
- }
- ssx_task_delay(2 * TICKS_PER_SECOND);
- }
- }
-
- /* sys_time_task - prints how long it has been
- * running once every minute */
-
- void sys_time_task(void)
- {
- int task_num;
-
- while(1){
- ssx_wait(&print_q);
- gotoxy(1,15);
- cprintf("Sytem time = %08lx seconds",
- ssx_get_time()/ TICKS_PER_SECOND);
- ssx_alert(&print_q);
- ssx_task_delay(1 * TICKS_PER_SECOND);
- }
- }
-
-
- /* tick_handler - handles 18.2 per second timer
- * interrupts on PC */
-
- void interrupt tick_handler(void)
- {
- /* call original PC timer handler */
- (*old_timer_int)();
- /* inform SSX of clock tick */
- ssx_clock_tick();
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Developing 80x86-Based Embedded Systems
-
-
- Andrew P. Beck
-
-
- Andrew Beck is a systems engineer specializing in the design of computers for
- industrial and scientific applications. He has been designing embedded systems
- more than 15 years. Andrew can be reached at (908) 806-8262.
-
-
-
-
- Introduction
-
-
- Embedded systems were once programmed only by those brave souls who to dared
- write in assembly language and who knew enough about the hardware to program
- to the chip level. But as the embedded systems market continues to expand, and
- the need for embedded systems programmers grows, all of that has changed.
- Most manufacturers are reducing the size of their products. With the shortage
- of experienced embedded systems programmers, more MS-DOS programmers will be
- required to develop embedded programs. In this article, I will try to ease the
- path for those of you who have never developed an embedded system. I address
- the fundamental issues of developing a program to run on an 80x86-based
- system. I explain the steps necessary to convert your C programs into embedded
- code, And I give you some hints on how to avoid some of the common pitfalls of
- embedded systems design.
-
-
- A Little History
-
-
- In 1978 Intel developed the 8086 microprocessor. Due primarily to IBM's use of
- it in their first PC, the 80x86 family has become the industry's most popular
- microprocessor. There are literally millions of 80x86-based computer systems
- in operation today. Because of the PC's popularity, scores of lowcost,
- high-quality software development tools have been created for it.
- Soon after the introduction of the 8086, embedded systems designers started
- developing new products around it. These early designers had to rely on
- high-priced development systems to help them refine their designs and develop
- their software. This, along with the large number of support chips required
- for even a modest 8086 design, prevented many designers from using the 8086 in
- their embedded designs. Only those who could justify the high initial
- development cost and long development cycle could afford to use it.
- Intel soon realized that embedded systems offered a potentially large market,
- so in 1982 they announced the 80186 microcontroller. The 80186 is a
- highly-integrated controller that includes some of the most often used
- peripherals on the same chip as the microprocessor. In the past couple of
- years there has been a dramatic increase in the number of new designs
- utilizing this controller. In addition to Intel, NEC and AMD currently produce
- microcontrollers that are code compatible with the 8086.
- Users of all types of equipment, from consumer electronics to high-end
- instrumentation, are demanding ever more intelligent devices. Manufacturers
- are continuing to put more powerful systems into ever smaller boxes. One
- market that has seen a tremendous growth in the last few years is the field of
- industrial instrumentation and automation. The typical handheld instrument of
- today is required to outperform desktop systems produced just a few years ago.
- In addition, manufacturers are striving to make these systems easier to use,
- while introducing new products faster than ever.
- All of these factors have combined to force the embedded systems designer to
- look for tools to help produce better products in a shorter period of time.
- One of the most powerful tools currently available is the C programming
- language. C allows the designer to develop programs faster, and with fewer
- bugs, than assembly language. C programs can be better structured, and
- therefore more portable and maintainable, than their assembly-language
- counterparts.
- A few years ago, many designers were reluctant to use C in their designs. They
- felt the language used too much memory. It was often difficult to write
- interrupt handlers in C, and the designer had little control over placement of
- data or code. All of this has changed. Today's C compilers offer a high level
- of optimization. In large complex programs, the C implementation is often no
- larger than the assembly version. Most compilers provide support for
- generating interrupt function completely in C.
-
-
- What is an Embedded System?
-
-
- An embedded system generally consists of a microprocessor and a few
- peripherals that run a dedicated program. That program is usually stored in
- non-volatile memory, such as PROM. Embedded systems are not programmable by
- the end user, and usually have very limited I/O resources. While many embedded
- systems are designed with off-the-shelf components, the majority of them are
- based on custom-designed hardware.
- Since many embedded systems do not run under an operating system, the
- programmer is responsible for supplying all of the low-level I/O functions.
- And unlike MS-DOS-based programs that can be loaded from disk, embedded
- systems require you to load the program into PROM. Consequently, developing
- and debugging these systems presents some unique challenges.
-
-
- Embedded Systems Development Tools
-
-
- Due to the popularity of the PC, there are a number of low-cost, high-quality
- development tools available for the 80x86 processors. Two of the most popular
- C compilers for the PC are produced by Borland and Microsoft. Because of their
- popularity, these compilers have led to the development of many third-party
- design tools that support embedded systems.
- To create an 80x86-based embedded system, you will need a standard
- MS-DOS-based assembler, compiler, and linker. You will also need a locate
- program. This program converts a standard MS-DOS .exe file into a form that
- can be burned into PROM, typically a hex or binary file.
- If this is your first embedded system design, I'd recommend that you purchase
- a third-party embedded-systems development package. In addition to the locate
- program, the vendor will supply the requisite startup code and runtime support
- functions for your compiler. Most vendors will also supply their own
- libraries, or a set of utilities to remove non-ROMable functions from your
- compiler's library.
-
-
- Basic Principles
-
-
- Programs written for MS-DOS-based systems run in the system's RAM. When a
- program is executed, the program loader locates an available block of memory
- and loads the program into it. MS-DOS .exe files are simply relocatable files.
- As the program is loaded, the program loader uses a table stored in the file
- to resolve the relocatable segment addresses.
- Embedded systems require two types of memory, volatile RAM and non-volatile
- PROM (or ROM). All of the program's variables, as well as the stack, are
- located in RAM. The executable portion of the program is stored in PROM.
- Constants, such as string data and initialized variables, are initially stored
- in the PROM. However, before the program can be executed, this data must be
- copied to RAM. This is the responsibility of the system's startup code. This
- code is also responsible for initializing the segment registers, clearing the
- uninitiliazed RAM, and setting up the heap. Finally the startup code must call
- the program's main function.
- The startup code is the most important piece of software in the system. It is
- also the most difficult for the first-time embedded systems programmer to
- develop. If you purchase a third-party embedded development package, the
- vendor will supply the requisite startup code.
- When the 80x86 processor begins running after reset, it executes the code
- located at 0xffff:0xfff0. Normally the only instruction at this location is a
- jump to a lower address in PROM, where the actual program is stored. The first
- piece of code to be executed after the initial jump is the startup code. The
- startup code is responsible for establishing an environment that the C program
- can run in, consequently it is always written in assembly.
- All embedded C programs require that at least three segments be defined. The
- code segment contains executable instructions--your program. The data segment
- contains all of the program's static variables. Finally, the stack segment is
- where non-static variables, passed arguments, and function return addresses
- are stored. Depending on the memory model used, some programs will also
- require a far data segment. Some advanced applications may make use of
- multiple code, data, or stack segments.
- Since your programs, constants, and initialized data will be stored in PROM,
- the startup code must copy them into RAM. It must also zero out the
- uninitialized data area so that all other static variables will be initialized
- to zero. Finally it has to setup all of the segment registers and the stack
- pointer.
- Most of the time the startup code will also be responsible for setting the
- system's interrupt vectors. Interrupts 0 through 6 are special interrupts
- generated by the CPU. Interrupts 0, 4, 5, 6 and 7 are processor exception
- interrupts. They should point to an exception-handler function. Interrupt 2 is
- the nonmaskable interrupt (NMI). If your hardware utilizes the NMI, its vector
- must be initialized to your NMI handler's address.
- If you are using a microcontroller, its on-board peripherals will use several
- other interrupts. If you operate them in the interrupt mode, you must
- initialize their vectors. The remaining interrupts are available for your
- application. All unused interrupts should be initialized to point to a dummy
- function that simply performs a return. This prevents an errant interrupt from
- crashing the system.
- In some systems, the startup code may also be responsible for initializing
- some, or all, of the system's hardware. Once the initialization is complete,
- the startup code calls the main function. Command-line arguments can be
- emulated by pushing data onto the stack before main is called. These arguments
- can then be accessed via the standard argv and argc variables. Typically,
- these would be used to indicate the status of the system's setup switches, or
- some other hardware interface.
-
-
-
- Memory Considerations
-
-
- Since constants are stored in PROM and then copied to RAM, you pay a penalty
- in terms of memory usage. Constants require twice as much memory in embedded
- systems as they do in MS-DOS-based system. Careful design can reduce this
- penalty. One way to reduce memory usage is by eliminating storage of duplicate
- constants. Both the Turbo-C and Microsoft compilers can be instructed to merge
- duplicate string data. However, they can eliminate duplicate strings only
- within a single program module. If you use a lot of strings, careful attention
- to where and how you display them can help you conserve your system's precious
- memory.
- You can place all of your display functions, along with their associated
- strings, in one module. Or you can simply place all of your text in one module
- and reference it via pointers. Consider how your strings are constructed. If
- you use a lot of common substrings, you may want to break them into separate
- strings so that the compiler can eliminate duplicates for you.
- For example, consider an application in which you need to display three labels
- across the bottom of your screen. The text of each label varies depending on
- which mode the system is in. You could simply generate separate print
- statements for each set of labels as shown below:
- printf("DOWN RUN UP");
- printf("LEFT RUN RIGHT");
- printf(" RUN STOP");
- However, if you had several dozen statements like these, you would find that
- you had a lot of duplicate substrings. In that case a better approach would be
- to create pointers to each substring as depicted below. You could then direct
- the compiler to merge duplicate strings, and save a considerable amount of
- memory.
- printf("%s %s %s", "DOWN ", " RUN ", " UP");
- printf("%s %s %s", "LEFT ", " RUN ", "RIGHT");
- printf("%s %s %s", " ", " RUN ", " STOP");
- Another technique for conserving memory is to place all of your strings into
- their own far data segment and reference them via far pointers. This technique
- does not need the data to be copied to RAM. Unfortunately the Turbo-C compiler
- does not support the printf far string pointer argument (%Fs), so this
- technique is only valid if you are using the Microsoft compiler.
- printf("%Fs %Fs %Fs", down_text, run_text, up_text);
- printf("%Fs %Fs %Fs", left_text, run_text, right_text);
- printf("%Fs %Fs %Fs", blank_text, run_text, stop_text);
-
-
- Building an Application
-
-
- You can program the bulk of your embedded program just as you would an
- MS-DOS-based program. However, you must avoid using any library functions that
- are not ROMable. The ROMability of functions varies tremendously from library
- to library. If you are using an embedded-system development package, the
- vendor will indicate which functions can be ROMed.
- Many of the low-level I/O functions in MS-DOS, such as putch and getch,
- interface to the hardware via MS-DOS's interrupt 21 handler. High-level
- functions, such as printf and scanf, use this same interrupt. If you emulate
- the MS-DOS interrupt 21 handler you can use most of your library's standard
- I/O functions. Except for the disk-I/O functions, all of Turbo C's I/O
- functions are fully-ROMable if you supply an interrupt 21 handler. If you use
- your library's standard functions, instead of developing your own, your
- development time will be reduced and your program will be more portable.
- If your development package supports it, you can also include floating-point
- math in your application. You will have to supply a runtime interface to the
- floating-point emulator, or coprocessor, as well as a floating-point exception
- handler. Most embedded-systems development packages supply the requisite code.
- If you are developing your own embedded support functions, you have to make
- sure you don't inadvertently use a library function that is MS-DOS dependent.
- The library that comes with Borland's Turbo C compiler tends to be more
- independent of MS-DOS than does Microsoft's. For example, if you supply
- emulation for MS-DOS's interrupt 21 console I/O functions, you can use Turbo
- C's printf function without any other support. On the other hand, Microsoft's
- printf depends on several low-level MS-DOS functions and cannot be embedded
- unless you supply emulations of them. If you are using Microsoft's C, refer to
- their C Compiler User's Guide for a list of the functions that are MS-DOS
- independent. Both Microsoft and Borland will sell you the source code for most
- of their library functions, the exceptions being their math and graphics
- libraries.
-
-
- Compiling, Linking, and Locating
-
-
- Once you've written your program, you can compile and link it just as you
- would any MS-DOS-based program. The only difference is that you will have to
- link in your startup code in place of the compiler's. You will also have to
- link in any special runtime support functions that are required by your
- system. Make sure that your startup code is the first module listed in the
- link list, this ensures that it is the first code executed. You must also
- instruct your linker to create a map file. This will be used by the locate
- program to aid in determining where code and data are to be placed in your
- target system.
- The output file produced by your linker will be an .exe file, a relocatable
- image of your program. This file contains two parts, a relocation header and
- the actual program data and code. The relocation header consists of a series
- of offsets into the program that point to segment references. The locate
- program must modify these references so that they match the memory layout of
- your target system. The locate program will use the link map to determine the
- length and location of each of the segments contained in the .exe file.
- As a final step the locate program must output your program in a form that can
- be downloaded into a test system or burned into PROM. Usually this will be a
- hex or binary file as dictated by the needs of your PROM programmer, emulator,
- or debugger. The actual locate procedure will vary somewhat depending on your
- locate program. Most of them require you to create a file that contains a
- memory map of the target system, along with directives indicating where each
- of the segments is to be located. You must also indicate which segments are to
- be duplicated in PROM. Typically this will consist of only the segment that
- contains your initialized data.
-
-
- Debugging
-
-
- Embedded systems present their own unique problems when it comes to debugging.
- If you simply burned your program into PROM and ran it, it would be virtually
- impossible to determine where a problem existed when it didn't run properly.
- Most embedded systems have very limited I/O capability--often nothing more
- than a DIP switch and a few LEDs. These would prove useless if you were trying
- to debug anything more than the simplest of programs.
- Traditionally embedded systems designers have used an incircuit emulator
- (ICE), or a dedicated development system to debug their designs. While
- development systems provide the most integrated development environment, they
- tend to be very expensive, often costing upwards of $20,000. Many lowcost
- emulators are available, and they are fine for developing hardware, but most
- fall short when it comes to debugging complicated programs.
- If you've opted to use your compiler's libraries, and you've carefully
- designed and structured your program, you can test a large percentage of your
- code right on your PC. You can generate test functions that emulate your
- target system's low-level I/O routines. These routines can make calls to
- MS-DOS to display data on the PC's screen and get input from the keyboard. If
- you've emulated the MS-DOS interrupt 21 handler in your program you may not
- need to generate any low-level test functions.
- Perhaps's the best debugging environment for embedded systems is one with
- which most programmer's are already familiar--the source-level debugger. Most
- MS-DOS programmers have, at one time or another, debugged their applications
- with either Borland's Turbo Debugger or Microsoft's Codeview. Both of these
- products provide a host of options for debugging your program. They allow you
- view your source code in its native form, including comments. Both support
- multiple breakpoints and allow you to single step your program. They will also
- display the contents of memory as well as the CPU registers. However, their
- most powerful feature is their ability to display the values of complex data
- types such as arrays, structures, and unions.
- Several vendors offer a version of Borland's Turbo Debugger for use on
- embedded systems. By utilizing the debugger's remote mode you can download and
- debug your program on your target system.
- Some vendors offer their own remote debuggers. Like Turbo Debugger, most of
- these communicate with your target system via an RS232 serial link. Some, such
- as Paradigm's DEBUG/RT, can be configured to communicate with any type of
- interface, including parallel ports and PROM emulators.
- With the power of these debuggers available to you, there is no reason to even
- test your code on the PC. You can perform all of your testing and debugging
- right on your target system. This will help you uncover subtle problems,
- especially timing-related ones, early in the development cycle.
-
-
- Special Considerations
-
-
- Embedded systems are often required to maintain some of their data even when
- their power has been turned off. This may be data collected and stored for
- later retrieval, or simply setup information such as the configuration of its
- peripherals. It's relatively easy to provide this ability in hardware. The
- most popular way is by equipping the system with battery-backed RAM. While
- system power is turned off, a small battery applies enough voltage to the RAM
- for it to maintain its data.
- From a programmer's standpoint, non-volatile data presents something of a
- problem. You already know that at reset the startup code copies over any
- initialized variables and zeros out the uninitialized area. This will
- reinitialize all data stored in your data segment. So you have to find a way
- to protect your non-volatile data from the startup code.
- The simplest method would be eliminate the code that zeros out the
- uninitialized data area. This presents its own set of problems. If you didn't
- zero out this area you couldn't depend on uninitialized static variables to be
- zero. An unwary maintenance programmer who wasn't aware of this particular
- peculiarity could easily be tripped up by this.
- The best way to solve this problem is to create a segment for all of your
- non-volatile variables. Since they're not included in either the initialized
- or uninitialized data areas, the startup code will not modify them.
- You still face one other problem though. If your program depends on any of
- them being in a known state at reset time, and the startup code isn't
- modifying them, how do they every get initialized in the first place? Again a
- simple solution is at hand. You must provide a function that sets all of these
- variables to default values. Then in the startup code, or at the beginning of
- main, you call this function if some predefined condition exists. For example,
- you could generate a checksum for all of the non-volatile variables, and call
- your reset function if the checksum is invalid. Or you can test for some
- user-generated condition such as a certain switch setting or keyboard input.
-
-
-
- Performance Issues
-
-
- The first time you run your embedded application you may be disappointed with
- its performance. Often your program will simply not run as fast as expected.
- This problem is particularly prevalent if you test and debug your program on
- the PC. Most embedded systems run much slower than today's PCs. Testing your
- program on the target system is one way to identify performance problems
- early, before they overwhelm the project.
- Most PCs are so fast that many MS-DOS programmers do not pay much attention to
- the speed issues that were a common concern just a couple of years ago.
- Embedded systems programming still requires that extra attention. Careful
- attention to selection and implementation of algorithms is paramount.
- One of the simplest ways to get a little more performance out of your system
- is to set a couple of your compiler's command-line switches. If you're using
- the Intel 80186 microprocessor, set the switch that enables code generation
- for it. The 80186 includes several instructions, not included in the 8086,
- that can increase the speed of interrupts and function calls. If you have
- plenty of PROM in your system, set your compiler to optimize for speed,
- instead of for code size.
- One area that causes a lot of problems for embedded systems programmers is the
- use of floating-point math. Most embedded systems lack a math coprocessor, so
- you'll have to depend on the floating-point emulator in your math library.
- Even with a highly-optimized, floating-point library, floating-point math is
- much slower than integer math. Many embedded applications don't require the
- range of numbers that floating-point supports. Often fixed-point numbers will
- offer more range and precision than you need. If addition to the improvement
- in speed, switching from floating-point to fixed-point math can save a
- considerable amount of RAM.
- One of the biggest issues in the design of embedded systems is the tradeoff of
- hardware versus software. Often hardware designers will opt to eliminate a
- simple circuit that can be emulated in software. With the rising cost of
- software development, these tradeoffs have to be considered carefully. In
- low-volume applications, the software development cost may far exceed the
- savings in hardware cost.
- If your application makes heavy demands on the processor, one or two simple
- circuits may significantly improve overall system performance. One example of
- a common input device that is often emulated in software is the keyboard
- controller. Often it will be implemented by simply connecting a few switches
- to one of the system's input ports. The software must then periodically poll
- the status of the switches to determine if any has been pressed. The software
- must also determine if it is a valid switch closure or simply "switch bounce"
- from the last key press. In simple systems this rarely taxes the processor.
- In many real-time systems, however, the processor may service several I/O
- devices. It may not be able to spend the time necessary to poll and debounce
- the keyboard. In this case a simple hardware circuit can be used to decode and
- debounce the keyboard and interrupt the processor only when a valid key press
- occurs. Obviously, the better your understanding of the hardware, the easier
- it will be for you to spot potential problems and inform the hardware
- designers before the design is finalized.
-
-
- Conclusion
-
-
- Developing software for an embedded system is different than for a
- MS-DOS-based system, but it isn't necessarily harder. In this article I've
- only skimmed the surface of embedded systems programming. There are many
- issues that are beyond the scope of an introductory text. Before embarking on
- your first design, I'd recommend that you develop a good understanding of your
- target hardware platform. Also look carefully at the development tools that
- are available, and consider which best meet your needs and budget.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Three-Dimensional Modeling Under Windows 3.1
-
-
- Thomas W. Olsen
-
-
- Thomas writes a variety of Windows and DOS software for a major insurance
- company. He can be reached on the CompuServe Information Service at
- (76450,1767).
-
-
- Nothing communicates an idea better than a picture. It's the defining
- principle behind the popularity of Microsoft Windows and other graphical
- environments. Not long ago, though, you might recall serious debate over
- whether Windows would ever meet the performance demands of a graphical user
- interface. Much of the trepidation resulted, no doubt, from the tremendous
- freedom of MS-DOS programs to directly access video hardware. However, faster
- CPUs, accelerated video adapters, local bus connections, multimedia, and a
- mature Graphical Display Interface (GDI) have since conspired in favor of
- Windows.
- These changes pose significant opportunities in 3-D modeling software
- development. Three-dimensional modeling has become increasingly important over
- time because it mimics elements in the real world. This article focuses on
- simple strategies for 3-D modeling. The accompanying source code was compiled
- and linked with the Microsoft C/C++ Optimizing Compiler Version 7.0 and
- Microsoft Windows 3.1 Software Developer Kit (SDK). Every effort has been made
- to ensure compatibility with other compilers. Compile instructions are
- provided in comments at the top of each listing. The bitmaps were created with
- Microsoft Paintbrush.
- This article barely scratches the surface of 3-D graphics. Rendering, shading,
- ray tracing, and texture mapping get a more thorough treatment from the
- references in the bibliography. This article will present a building block for
- such advanced features.
- Do not confuse 3-D modeling with Windows metafiles. A metafile is a
- binary-encoded collection of GDI function calls. You create one by sending the
- output from various GDI function calls to a special device context. A recorded
- metafile can be played back by passing its associated resource handle to
- PlayMetaFile. The resulting picture may look three-dimensional but it's just a
- two-dimensional facade.
-
-
- Vector Graphics
-
-
- Microsoft Windows derives its characteristic look and feel primarily from
- raster-based or bitmapped graphics technology. Most fonts, controls, and icons
- are nothing more than bitmaps--two-dimensional blocks of pixels--that appear
- three-dimensional due to varying color gradients. Bitmaps look great but
- generally experience some kind of image distortion when rotated, stretched, or
- scaled onto devices of varying resolutions. To address such deficiencies
- Microsoft incorporated so-called TrueType font technology into Windows 3.1.
- TrueType is a form of vector-based graphics, in which images are constructed
- with individual graphical primitives such as lines, points, rectangles,
- ellipses, and curves rather than bitmaps. Vector graphics are scalable,
- require less memory than bitmaps, and enable us to model three-dimensional
- objects with relative ease.
-
-
- Coordinate Systems
-
-
- The Windows GDI does not contain explicit support for 3-D modeling, but it is
- not difficult to build a suitable framework atop existing primitive functions.
- Nearly any 3-D object can be drawn with a set of interconnected points or
- vertices. For example, a simple cube has eight vertices (one for each corner)
- while the General Dynamics F16 Falcon aircraft in 3D.C in Listing 1 (along
- with the files in Listing 2 and Listing 3) contains literally hundreds of
- vertices. Of course, a collection of points is worthless without some frame of
- reference, so they are placed in a domain called the World Coordinate System
- (WCS). You can change the object's orientation in WCS by multiplying each
- vertex by a series of transformation matrices. There are distinct
- transformation matrices for rotation, reflection, shearing, scaling, and
- translation operations, respectively.
- Try to imagine yourself floating in space around a motionless object. As you
- change position, each WCS vertex is transformed with respect to an Eye
- Coordinate System (ECS) that emanates from your eye and points toward the
- object (Figure 1). To complicate matters even more, the resulting ECS vertices
- must be transformed from three-dimensional to two-dimensional screen
- coordinates (SCS) before the object can be displayed. Once these screen
- positions are known, you can connect-the-dots to produce a transparent
- wireframe model, or use filled polygons for a more realistic solid model
- (Figure 2). Three-dimensional objects are drawn one surface at a time, so
- vertices are generally grouped in that order.
-
-
- Hidden-Surface Removal
-
-
- Hidden-surface removal is one of the more complicated and
- computation-in-tensive facets of 3-D modeling. The most popular method of
- hidden-surface removal is called backplane elimination. Basically, it involves
- computing whether a normal (perpendicular) vector emanating from a given
- surface points away from or toward the viewer's line of sight. Those surfaces
- facing away from the viewer cannot be seen and, therefore, need not be drawn.
- It does have its drawbacks, too. Objects with irregular or overlapping
- surfaces will not be drawn properly. One quick-and-dirty solution is to sort
- the surfaces in terms of decreasing distance from the viewer (ECS
- z-coordinate). Surfaces lying farther away from the viewer are drawn first and
- subsequently masked by closer surfaces. Depth sorting has its flaws but
- performance-conscious applications usually don't mind.
-
-
- Constructing Models
-
-
- 3D.C (Listing 1) supports both wireframe and solid models. It first creates a
- window with horizontal and vertical scroll bars, and uses SetScrollRange to
- lock the thumb between 0 and 360 degrees. Depressing the scroll bars changes
- the angular position of the viewer relative to the object. This is especially
- convenient because the viewer's position is given in spherical coordinates
- (distance, theta, phi). All measurements are given in device units. The F16
- object "database" has been optimized to keep code size to a minimum.
- 3D.C calls DrawObject whenever the viewer depresses the scroll bars or resizes
- the window. DrawObject determines the center point of the window and creates a
- compatible work bitmap. Using an intermediate bitmap prevents the flashing
- effects that occur while drawing straight to a display context. DrawObject
- also precalculates sine and cosine values for global variables theta and phi.
- These values are needed when f16.vertex[].world vertices are transformed to
- f16.vertex[].eye vertices and, finally, to f16.vertex[].screen coordinates.
- DrawObject clears the work bitmap and loops through each surface in the
- f16.info[] array. For solid models, DrawObject performs a depth sort on the
- f16.vertex[].eye vertices, removes hidden surfaces with backplane elimination,
- selects a brush color from f16.info[].brushColor, and calls Polygon;
- otherwise, it calls PolyLine for a wire-frame surface. DrawObject then updates
- the client area with the completed work bitmap and deletes unused resources.
-
-
- Computations
-
-
- Floating-point computations incur a considerable amount of overhead--even with
- a math coprocessor installed--but you can improve performance significantly by
- substituting fixed-point integers for floating-point numbers. Fixed-point
- integers incorporate both the whole and fractional components of
- floating-point numbers but can be manipulated in single arithmetic operations,
- such as IMUL and IDIV. For example, it is possible to represent the
- floating-point number 12.345 with integer 12345 by shifting the decimal place
- three positions. There are certain problems with this technique, as well.
- Integers can only represent so many digits before overflowing, so you must
- strike a balance between the scale and precision of the 3-D model.
- NASA's Jet Propulsion Laboratories unveiled a computer-generated film several
- months ago that depicted the surface topography of some distant planet. JPL
- had downloaded countless bits of radar imaging data from a distant probe into
- three Cray super computers and rendered the surreal landscape frame-by-frame
- over the course of three solid weeks. The resulting bitmaps were finally
- transferred to videotape and made available for public consumption. There's a
- lesson hiding behind this madness. Even though real-time 3-D graphics were out
- of the question, JPL eventually got what it paid for by blending vector and
- raster technologies.
- References
- Adams, Lee. 1986. High-Performance CAD Graphics in C. Blue Ridge Summit, PA:
- Windcrest/Tab.
- Microsoft Corp. 1991. Microsoft Windows Multimedia Authoring and Tools Guide.
- Redmond, WA: Microsoft Press.
- Microsoft Corp. 1991. Microsoft Windows Multimedia Programmer's Reference.
- Redmond, WA: Microsoft Press.
- Microsoft Corp. 1991. Microsoft Windows Multimedia Programmer's Workbook.
- Redmond, WA: Microsoft Press.
- Park, Chan S. 1985. Interactive Microcomputer Graphics. Reading, MA:
- Addison-Wesley Publishing Company.
- Petzold, Charles. 1990. Programming Windows. Redmond, WA: Microsoft Press.
- Wilton, Richard. 1987. Programmer's Guide to PC & PS/2 Video Systems. Redmond,
- WA: Microsoft Press.
- Figure 1 The Viewing Transformation
- Figure 2 Wire-Frame and Solid Models
-
-
- Listing 1 3d.c -- creates a General Dynamics F16 Falcon aircraft
- #include "windows.h"
- #include <math.h>
- #include <stdlib.h>
-
- //******************************************************************
- //Title: 3D.C
- //Author: Thomas W. Olsen
- //Version: 1.0
- //Compiler: Microsoft C/C++ 7.0
- // rc /r 3d. rc
- // cl /c /AL /Gsw /W3 /Oas /Zpe /Zi /FPi 3d.c
- // link /CO /NOD 3d,,, libw llibcew win87em, 3d.def
- // rc 3d.exe
- //*****************************************************************
- //********************** Various Constants *************************
- #define CENTER 0
- #define PI 3.141593
- #define RADIANS(a) (a * (PI / 180.0))
- #define DEGREES(a) (a * (180.0 / PI))
- #define NO_OF_VERTICES 66
- #define NO_OF_SURFACES 45
- #define NO_OF_SURFACE_VERTICES 220
- #define VIEW_RATIO (40 / 10)
- #define MIN_DEGREES 0
- #define MAX_DEGREES 359
- #define ROTATE_DEGREES 5
- //********************* Structure Definitions **********************
- typedef struct tagPOINT3D
- {
- double x;
- double y;
- double z;
- } POINT3D;
-
- typedef struct tagVERTEX
- {
- POINT3D world; // World Coordinates
- POINT3D eye; // Eye Coordinates
- POINT screen; // Screen Coordinates
- } VERTEX;
-
- typedef struct tagSURFACE
- {
- int mapIndex; // Index Into the Surface Map Array
- int noOfVertices; // No of Vertices Forming the Surface
- int depthIndex; // Used in Determining Depth of Surface
- int brushColor;
- } SURFACE;
-
- typedef struct tagOBJECT
- {
- VERTEX vertex [NO_OF_VERTICES];
- SURFACE info[NO._OF_SURFACES];
- int map[NO_OF_SURFACE_VERTICES];
- } OBJECT;
- //************************* Static Data ***************************
- OBJECT f16 =
- {
-
- { // Vertex Array
- { 0.00, 0.00, 0.00 }, //Center
- { 0.00, -17.00, 9.0 }, { 0.00, -13.00, 9.00 }, //Tail
- { 0.00, -14.50, 3.00 }, { 0.00, -8.00, 3.00 },
- { 0.00, -14.50, 2.00 }, { 0.00, -5.00, 2.00 },
- { -0.50, -16.00, 1.00 }, { -1.00, -16.00, 0.00 }, //Exhaust
- { -0.50, -16.00, -0.50 }, { 0.00, -16.00, -1.00 },
- { 0.50, -16.00, -0.50 }, { 1.00, -16.00, 0.00},
- { 0.50, -16.00, 1.00 },
- { -1.00, -13.00, 2.00 }, { -2.00, -13.00, 0.00 }, //Fuse
- { -1.50, -13.00, -1.50 }, { 0.00, -13.00, -2.00 },
- { 1.50, -13.00, -1.50 }, { 2.00, -13.00, 0.00 },
- { 1.00, -13.00, 2.00 }, { -1.00, 7.00, 2.00 },
- { -2.00, 7.00, 0.00 }, { -1.50, 7.00, -1.50 },
- { 0.00, 7.00, -2.00 }, { 1.50, 7.00, -1.50 },
- { 2.00, 7.00, 0.00 }, { 1.00, 7.00, 2.00 },
- { 2.00, -8.00, 0.00 }, { 11.00, -8.00, 0.00 }, //Wings
- { 11.00,-5.00,0.00 }, { 5.00, 0.00, 0.00 },
- { 2.00, 7.00, 0.00 }, { -2.00, -8.00, 0.00 },
- { -11.00, -8.00, 0.00 }, { -11.00, -5.00, 0.00 },
- { -5.00, 0.00, 0.00 }, { -2.00, 7.00, 0.00 },
- { 0.50, 2.00, 2.00 }, { -0.50, 2.00, 2.00 }, //Cockpit
- { 1.00, 5.00, 2.00 }, { 0.50, 5.00, 3.50 },
- { -0.50, 5.00, 3.50 }, { -1.00, 5.00, 2.00 },
- { 1.00, 8.00, 2.00 }, { 0.50, 8.00, 3.50 },
- { -0.50, 8.00, 3.50 }, { -1.00, 8.00, 2.00 ),
- { 0.50, 11.00, 2.00 }, { -0.50, 11.00, 2.00 },
- { 0.00, 7.00, -1.00 }, { -1.00, 11.00, 2.00 }, //Subfuse
- { -2.00, 11.00, 0.00 }, { 0.00, 11.00, -1.00 },
- { 2.00, 11.00, 0.00 }, { 1.00, 11.00, 2.00 },
- { 0.00, 11.00, -1.00 }, { 0.00, 17.00, 0.00 }, //Nose
- { -2.00, -16.00, 0.00 }, { -8.00, -16.00, 0.00 }, //Elevators
- { -8.00, -14.00, 0.00 }, { -2.00, -10.00, 0.00 },
- { 2.00, -16.00, 0.00 }, { 8.00, -16.00, 0.00 },
- { 8.00, -14.00, 0.00 }, { 2.00, -10.00, 0.00 }
- },
- { // Surface Info ... (Points to Surface Map)
- { 0, 5, 60, DKGRAY_BRUSH }, { 5, 5, 64, DKGRAY_BRUSH }, //Elevators
- { 10, 5, 60, DKGRAY_BRUSH }, { 15, 5, 64, DKGRAY_BRUSH },
- { 20, 5, 1, GRAY_BRUSH }, { 25, 5, 4, LTGRAY_BRUSH }, //Tail
- { 30, 5, 1, GRAY_BRUSH }, { 35, 5, 4, LTGRAY_BRUSH },
- { 40, 5, 14, BLACK_BRUSH }, ( 45, 5, 15, DKGRAY_BRUSH }, //Exhaust
- { 50, 5, 16, BLACK_BRUSH }, ( 55, 5, 17, BLACK_BRUSH },
- { 60, 5, 18, DKGRAY_BRUSH }, { 65, 5, 19, BLACK_BRUSH },
- { 70, 5, 20, DKGRAY_BRUSH },
- { 75, 5, 22, DKGRAY_BRUSH }, { 80, 5, 23, GRAY_BRUSH }, //Fuse
- { 85, 5, 24, DKGRAY_BRUSH }, { 90, 5, 25, DKGRAY_BRUSH },
- { 95, 5, 26, GRAY_BRUSH }, { 100, 5, 27, DKGRAY_BRUSH },
- { 105, 5, 20, GRAY_BRUSH, },
- { 110, 6, 30, DKGRAY_BRUSH }, ( 116, 6, 35, DKGRAY_BRUSH }, //Wings
- { 122, 6, 30, DKGRAY_BRUSH }, { 128, 6, 35, DKGRAY_BRUSH },
- { 134, 5, 21, LTGRAY_BRUSH }, { 139, 5, 22, LTGRAY_BRUSH }, //Subfuse
- { 144, 5, 50, LTGRAY_BRUSH }, { 149, 5, 26, LTGRAY_BRUSH },
- { 154, 5, 27, LTGRAY_BRUSH },
- { 159, 4, 55, GRAY_BRUSH }, { 163, 4, 54, GRAY_BRUSH }, //Nose
- { 167, 4, 56, GRAY_BRUSH }, { 171, 4, 52, GRAY_BRUSH },
- { 175, 4, 51, LTGRAY_BRUSH },
- { 179, 5, 41, WHITE_BRUSH }, { 184, 5, 45, WHITE_BRUSH }, //Cockpit
- { 189, 5, 46, WHITE_BRUSH }, { 194, 4, 41, WHITE_BRUSH },
-
- { 198, 4, 42, WHITE_BRUSH }, { 202, 5, 41, WHITE_BRUSH },
- { 207, 5, 46, WHITE_BRUSH }, { 212, 4, 45, WHITE_BRUSH },
- { 216, 4, 46, WHITE_BRUSH }
- },
- { // Surface Map ... (Points to Vertex Array)
- 58, 61, 60, 59, 58, 62, 63, 64, 65, 62, 58, 59, 60, 61, 58, // Elevators
- 62, 65, 64, 63, 62,
- 1, 3, 4, 2, 1, 3, 5, 6, 4, 3, 1, 2, 4, 3, 1, // Tail
- 3, 4, 6, 5, 3,
- 14, 15, 8, 7, 14, 15, 16, 9, 8, 15, 16, 17, 10, 9, 16, // Exhaust
- 17, 18, 11, 10, 17, 18, 19, 12, 11, 18, 19, 20, 13, 12, 19,
- 20, 14, 7, 13, 20,
- 14, 21, 22, 15, 14, 15, 22, 23, 16, 15, 16, 23, 24, 17, 16, // Fuse
- 17, 24, 25, 18, 17, 18, 25, 26, 19, 18, 19, 26, 27, 20, 19,
- 20, 27, 21, 14, 20,
- 28, 29, 30, 31, 32, 28, 33, 37, 36, 35, 34, 33, 28, 32, 31, // Wings
- 30, 29, 28, 33, 34, 35, 36, 37, 33,
- 21, 51, 52, 22, 21, 22, 52, 53, 50, 22, 50, 53, 54, 26, 50, // Subfuse
- 26, 54, 55, 27, 26, 27, 55, 51, 21, 27,
- 55, 54, 57, 55, 54, 56, 57, 54, 56, 52, 57, 56, 52, 51, 57, 52, // Nose
- 51, 55, 57, 51,
- 41, 42, 39, 38, 41, 45, 46, 42, 41, 45, 48, 49, 46, 45, 48, // Cockpit
- 40, 41, 38, 40, 42, 43, 39, 42, 44, 45, 41, 40, 44,
- 46, 47, 43, 42, 46, 48, 45, 44, 48, 49, 47, 46, 49
- }
- };
-
- BOOL wireFrame = FALSE;
- double distance = 75, thetaDegrees = 90, phiDegrees = 60;
- //************************** Static Data ***************************
- LONG FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
- lParam);
- void DrawObject(HWND hWnd, HDC hDC, OBJECT *object );
- int __cdecl compareProc(const void *elem1, const void *elem2 );
- //************************** Program Begin *************************
- int PASCAL WinMain(HANDLE hInst, HANDLE hPrevInst, LPSTR lpCmdLine, int
- numCmdShow )
- {
- MSG msg;
- HWND hWnd;
- WNDCLASS wc;
- //************************ Setup Window ************************
- wc.style = (UINT) NULL;
- wc.lpfnWndProc = WindowProc;
- wc.cbClsExtra = 0;
- wc.cbWndExtra = 0;
- wc.hInstance = hInst;
- wc.hIcon = LoadIcon( NULL, IDI_APPLICATION);
- wc.hCursor = LoadCursor( NULL, IDC_ARROW);
- wc.hbrBackground = GetStockObject(BLACK_BRUSH);
- wc.lpszMenuName = (LPSTR) "Menu";
- wc.lpszClassName = (LPSTR) "3DClass";
-
- if (!RegisterClass(&wc))
- return(FALSE);
-
- hWnd = CreateWindow( "3DClass", "3D Modeling Example",
- WS_OVERLAPPEDWINDOW WS_SCROLL WS_HSCROLL,
- CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
- CW_USEDEFAULT, NULL, NULL, hInst, NULL );
- if (!hWnd)
-
- return (FALSE);
-
- SetScrollRange( hWnd, SB_HORZ, MIN_DEGREES, MAX_DEGREES, TRUE );
- SetScrollRange( hWnd, SB_VERT, MIN_DEGREES, MAX_DEGREES, TRUE );
- SetScrollPos( hWnd, SB_HORZ, (int) thetaDegrees, TRUE );
- SetScrollPos( hWnd, SB_VERT, (int) phiDegrees, TRUE );
-
- ShowWindow( hWnd, numCmdShow );
-
- while (GetMessage(&msg, NULL, NULL, NULL)) /* Typical Mossage Loop */
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
-
- return (msg.wParam);
- }
-
- LONG FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
- lParam)
- {
- PAINTSTRUCT paint;
- HDC hDC;
- HMENU hMenu;
- int vPos, hPos;
-
- switch (message)
- {
- case WM_COMMAND:
- hMenu = GetMenu( hWnd );
- CheckMenuItem( hMenu, wParam, MF_CHECKED);
-
- if (wParam == 1)
- {
- CheckMenuItem( hMenu, 2, MF_UNCHECKED);
- wireFrame = TRUE;
- }
- else
- {
- CheckMenuItem( hMenu, 1, MF_UNCHECKED);
- wireFrame = FALSE;
- }
- InvalidateRect( hWnd, NULL, TRUE );
- break;
-
- case WM_DESTROY:
- PostQuitMessage( NULL );
- break;
-
- case WM_HSCROLL:
- if (wParam == SB_THUMBTRACK)
- break;
-
- hPos = GetScrollPos( hWnd, SB_HORZ);
-
- switch( wParam )
- {
- case SB_TOP:
- hPos = MIN_DEGREES;
- break;
-
- case SB_BOTTOM:
- hPos = MAX_DEGREES;
- break;
- case SB_LINEUP:
- case SB_PAGEUP:
- hPos -= ROTATE_DEGREES;
- break;
- case SB_PAGEDOWN:
- case SB_LINEDOWN:
- hPos += ROTATE_DEGREES;
- break;
- case SB_THUMBPOSITION:
- hPos = LOWORD(lParam);
- break;
- }
-
- if (hPos < MIN_DEGREES)
- hPos = MAX_DEGREES;
- else
- if (hPos > MAX_DEGREES)
- hPos = MIN_DEGREES;
-
- SetScrollPos( hWnd, SB_HORZ, hPos, TRUE );
- thetaDegrees = (double) hPos;
- InvalidateRect( hWnd, NULL, TRUE );
- break;
-
- case WM_VSCROLL:
- if (wParam == SB_THUMBTRACK)
- break;
-
- vPos = GetScrollPos( hWnd, SB_VERT );
-
- switch( wParam )
- {
- case SB_TOP:
- vPos = MIN_DEGREES;
- break;
- case SB_BOTTOM:
- vPos = MAX_DEGREES;
- break;
- case SB_LINEUP:
- case SB-PAGEUP:
- vPos -= ROTATE_DEGREES;
- break;
- case SB_PAGEDOWN:
- case SB_LINEDOWN:
- vPos += ROTATE_DEGREES;
- break;
- case SB_THUMBPOSITION:
- vPos = LOWORD(lParam);
- break;
- }
-
- if (vPos < MIN_DEGREES)
- vPos = MAX_DEGREES;
- else
- if (vPos > MAX_DEGREES)
- vPos = MIN_DEGREES;
-
-
- SetScrollPos( hWnd, SB_VERT, vPos, TRUE );
- phiDegrees = (double) vPos;
- InvalidateRect( hWnd, NULL, TRUE );
- break;
-
- case WM_SIZE:
- InvalidateRect( hWnd, NULL, TRUE );
- break;
-
- case WM_PAINT:
- hDC = BeginPaint( hWnd, &paint );
- DrawObject(hWnd, hDC, &f16);
- ReleaseDC( hWnd, hDC );
- EndPaint( hWnd, &paint );
- break;
-
- default:
- return (DefWindowProc(hWnd, message, wParam, lParam));
- }
- }
-
- void DrawObject(HWND hWnd, HDC hDC, OBJECT *object)
- {
- double sinTheta, cosTheta, sinPhi, cosPhi;
- double s1, s2, s3;
- POINT3D *v1, *v2, *v3;
- POINT center;
- RECT rect;
- POINT points[10];
- HBITMAP hBitmap, hOldBitmap;
- HRGN hRgn;
- HDC hMemDC;
- int surface, vertex, mapIndex, vertexIndex, loop;
-
- GetClientRect( hWnd, &rect); // Determine Size of Client Area
- center.x = (rect.right / 2); // Calculate X-Centerpoint
- center.y = (rect.bottom / 2); // Calculate Y-Centerpoint
-
- hRgn = CreateRectRgn( rect.left, rect.top, rect.right, rect.bottom );
- hMemDC = CreateCompatibleDC( hDC );
- hBitmap = CreateCompatibleBitmap( hDC, rect.right, rect.bottom);
- hOldBitmap = SelectObject( hMemDC, hBitmap );
- //**********************************************************************
- //* Precalculate SIN(x) and COS(x) *
- //**********************************************************************
- cosTheta = cos( RADIANS(thetaDegrees) );
- sinTheta = sin( RADIANS(thetaDegrees) );
- cosPhi = cos( RADIANS(phiDegrees) );
- sinPhi = sin( RADIANS(phiDegrees) );
- //******************************************************************
- //* Calculate Eye and Screen Coordinates *
- //******************************************************************
- for (loop = 1; loop < NO_OF_VERTICES; loop++)
- {
- object->vertex[loop].eye.x =
- (-object->vertex[loop].world.x * sinTheta) +
- (object->vertex[loop].world.y * cosTheta);
- object->vertex[loop].eye.y =
-
- (-object->vertex[loop].world.x * cosTheta * cosPhi) -
- (object->vertex[loop].world.y * sinTheta * cosPhi) +
- (object->vertex[loop].world.z * sinPhi);
- object->vertex[loop].eye.z =
- (-object->vertex[loop].world.x * sinPhi * cosTheta) -
- (object->vertex[loop].world.y * sinTheta * sinPhi) -
- (object->vertex[loop].world.z * cosPhi) + distance;
-
- object->vertex[loop].screen.x = (int)
- (VIEW_RATIO * (object->vertex[loop].eye.x / object->vertex[loop].eye.z) *
- center.y + center.x);
- object->vertex[loop].screen.y = (int)
- (-VIEW_RATIO * (object->vertex[loop].eye.y / object->vertex[loop].eye.z) *
- center.y + center.y);
- }
- //******************************************************************
- //* Draw Object *
- //******************************************************************
- FillRgn( hMemDC, hRgn, GetStockObject(BLACK_BRUSH));
- SelectObject( hMemDC, GetStockObject(wireFrame == TRUE ? WHITE_PEN:BLACK_PEN)
- );
-
- if (wireFrame == FALSE)
- qsort( object->info, NO_OF_SURFACES, sizeof(SURFACE), compareProc );
-
- for (surface = 0; surface < NO_OF_SURFACES; surface++)
- {
- mapIndex = object->info[surface].mapIndex;
-
- if (wireFrame == FALSE) // No Hidden Surface Removal For Wire Frame
- {
- v1 = &object->vertex[ object->map[ mapIndex ] ].eye; // Setup pointers to
- three
- v2 = &object->vertex[ object->map[ mapIndex+1 ] ].eye; // surface vertices
- v3 = &object->vertex[ object->map[ mapIndex+2 ] ].eye;
-
- s1 = v1->x * (v2->y * v3->z - v3->y * v2->z); s1 = (-1) * s1;
- s2 = v2->x * (v3->y * v1->z - v1->y * v3->z); // Perform dot product on
- surface
- s3 = v3->x * (v1->y * v2->z - v2->y * v1->z); // vectors to find normal vector
- }
-
- if (wireFrame == TRUE s1 - s2 - s3 <= 0.0)
- {
- for (vertex = 0; vertex < object->info[surface].noOfVertices; vertex++,
- mapIndex++)
- {
- vertexIndex = object->map[mapIndex];
- points[vertex].x = object->vertex[vertexIndex].screen.x;
- points[vertex].y = object->vertex[vertexIndex].screen.y;
- }
-
- if (wireFrame == TRUE)
- Polyline( hMemDC, &points[0], vertex;
- else
- {
- SelectObject( hMemDC, GetStockObject(object->info[surface].brushColor) );
- Polygon( hMemDC, &points[0], vertex);
- }
- }
- }
- BitBlt( hDC, 0, 0, rect.right, rect.bottom, hMemDC, 0, 0, SRCCOPY);
-
- SelectObject( hMemDC, hOldBitmap );
- DeleteObject( hBitmap );
-
- DeleteObject( hRgn );
- DeleteDC( hMemDC );
- }
-
- int _cdecl compareProc( const void *elem1, const void *elem2 )
- {
- if( f16.vertex[((SURFACE *) elem1)->depthIndex].eye.z >
- f16.vertex[((SURFACE *) elem2)->depthIndex].eye.z)
- return -1;
- if( f16.vertex[((SURFACE *) elem1)->depthIndex].eye.z <
- f16.vertex[((SURFACE *) elem2)->depthIndex].eye.z)
- return 1;
- else
- return 0;
- }
- /* End of File*/
-
-
- Listing 2 3d.def
- NAME 3D
-
- DESCRIPTION '3D Modeling Example'
-
- EXETYPE WINDOWS
- STUB 'WINSTUB.EXE'
-
- CODE PRELOAD MOVEABLE
- DATA PRELOAD MOVEABLE MULTIPLE
-
- HEAPSIZE 2048
- STACKSIZE 4096
-
- EXPORTS
- WindowProc @1
-
- Listing 3 3d.rc
- #include "windows.h"
-
- Menu MENU
- {
- POPUP "&Draw"
- {
- MENUITEM "&Wire Frame", 1
- MENUITEM "&Solid", 2, CHECKED
- }
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pointer Power in C and C++, Part 2
-
-
- Christopher Skelly
-
-
- Christopher Skelly has been a teacher of C and C++ for the past ten years,
- first for Plum Hall Inc., and then for his own company, Insight Resource Inc.
- Insight Resource also developed the best-selling help utility, "KO-PILOT for
- WordPerfect," which Brit Hume called "the best add-in ever written." Chris has
- served on both the C and C++ ANSI committees, and was the Technical Chairman
- for this year's "CPlusC++" and "C++ in Action" conferences, presented by
- Boston University. He writes regularly for the C User's Journal and the C++
- Journal, and can be reached at Insight Resource Inc., 914-631-5032, or at
- 71005.771@compuserve.com.
-
-
- This article extends and continues the techniques presented last month in Part
- 1 of "Pointer Power in C and C++." At the end of this article I will repeat
- and then solve the pointer puzzle presented in Part 1. Just in case you don't
- happen to have last month's issue immediately available, here are the eight
- Key Facts from Part 1:
- 1. A pointer is a variable whose contents is an address.
- 2. A pointer always "knows" the type of thing it addresses. It can be properly
- used only to access something of the correct type.
- 3. Pointer values are address/type pairs, just like pointer variables.
- However, pointer values are not storable lvalues.
- 4. Every pointer has three fundamental attributes. These attributes are the
- location, the contents, and the indirect value of the pointer.
- 5. The three attributes of a pointer represent three distinct address levels.
- These address levels can also be called levels of indirection.
- 6. Pointer space is organized into a series of planes or levels. Every pointer
- expression can be assigned to one of these planes. The plane of a pointer
- expression is a measure of how much potential for indirection there is in that
- pointer expression.
- 7. The name of an array usually behaves as if the array name were a pointer
- value.
- 8. The name of an array, in almost every context, evaluates to the address of
- the array's own "zeroth" element.
- Armed with these Key Facts, you are ready to learn the next set of techniques,
- a game informally called Pointer Dominos.
-
-
- Pointer Dominoes
-
-
- The key to mastering pointers is to learn to play Pointer Dominos. The game of
- pointer dominos is simply the game of using operators in expressions involving
- pointers. Each of the allowable operators does something very specific, and
- the operators are always to be played, or really evaluated, in a very precise
- order. If you know exactly what each operator does, and if you know how to
- determine the order of evaluation, you can play pointer dominos.
- Key Fact #9 -- Only a small number of operations are ever performed on
- pointers. If you know exactly what each operation does, and what the right
- order to apply the operations is, you can understand and create any pointer
- expression in C.
- Each pointer has three attributes and each attribute is at a different level
- of indirection. You know that certain operators actually change the level of
- indirection of an expression using a pointer. Specifically, you have already
- seen that & takes you up one level and * takes us down a level when applied to
- a pointer in an expression. Note that in declarations the * builds in one
- level of indirection, as does the []. The rules of pointer dominos apply only
- to expressions, not to declarations.
- int i = 0; /* i is declared at level 0 */
- int *p; /* p is declared at level 1 */
- p = &i; /* &i is level 1, so is p */
- x = *p; /* * on p takes us down from level 1
- to level 0 */
- This leads to the first two rules of Pointer Dominos. When used in pointer
- expressions:
- & takes the type of the expression up one level of indirection.
- * takes the type of the expression down one level of indirection.
- Several other operators affect the overall level of indirection of an
- expression. But all the operators do one of a small number of things. They
- take us up, down, or sometimes even sideways on the Ladder. Fortunately, each
- allowable operation has a precise, well-defined meaning. Only six moves appear
- in expressions involving pointers. These moves form our next key fact.
- Key Fact #10 -- The six moves of pointer dominos are:
- 1. Go up one level of indirection using &
- 2. Go down one level of indirection using *
- 3. Go down one level of indirection using []
- 4. Increase an address using + or ++
- 5. Decrease an address using - or --n
- 6. Change the type of the pointer's window on memory with a cast.
- Structures and their members are not included here, but are easy to add to the
- fundamentals of the model.
- Each move corresponds to one or two C language operators. The first move to
- consider is the &, the unary address-of operator. & always lifts you up one
- level of indirection. Generating the address of something is the equivalent of
- moving up to the next plane in pointer space. As I've said, this process is
- known as referencing, and involves a relative move up to the next higher
- address plane.
- Two operators move you down one level of indirection. First the *, or unary
- indirection operator, means go down to the plane immediately below the plane
- you start on. If x lives on plane 5, *x is an expression that lives on plane
- 4. Most typically, if p lives on plane 1, *p is a non-pointer living on plane
- 0. This move is called dereferencing, and it is a relative move. *x is one
- level below x. You can't say anything about what level this actually is, until
- you know what level x itself resides on.
- The [] operator is also a dereferencing operator. a[n] lives on the plane
- below a, where a is any address expression. An important principle of pointer
- dominos is the fact that both * and [] bring an expression down one level of
- indirection from what they are applied to. The formula which relates how * and
- [] are related is the important formula:
- a[n] == *(a + n)
- This master formula in C and C++ shows that a subscript is syntactically
- equivalent to dereferencing an offset from a pointer. The a in the formula
- represents any address. The address may come from a pointer, or from an array
- name, or from a casted expression. It doesn't matter. The subscript can always
- be applied to an address, just like the *, and the relation between the two
- forms comes from this formula.
- The fourth and fifth moves involve operations of addition and substraction,
- including +, ++, -, and --. These operators produce no change in level of
- indirection. They move you sideways on the same plane, either toward higher or
- lower memory and always by a scaled offset. p + 1 is the address of one object
- higher in memory than the object p points to. p - 2 is the address of an
- object two objects below the object addressed by p. p, p + 1, and p - 2,
- however, all exist on the same plane and have the same level of indirection.
- The final pointer domino move is the cast. Casting a pointer usually produces
- no change on the level of indirection. Casting a pointer to int to a pointer
- to double, for example, does not change the level of the pointer. What does
- change is the size and format of the "window on memory" that this pointer
- accesses. A pointer to char accesses one byte of integer data. A pointer to
- double accesses eight bytes of floating-point formatted data. There are
- special cases, however, where a cast does affect the level of indirection of
- an expression. Consider:
- char *p = malloc(1000);
- char **p2;
- p2 = (char **)p;
- Here the cast, (char **), does indeed produce a level change from level one up
- to level two. The real rule is this: the level of an expression with a cast is
- the level of indirection of the cast. Casts are the wild-cards of pointer
- dominos. Any expression can be cast to have some new level and type. The
- declaration inside the cast determines the level of the newly-casted
- expression.
- Summarizing the rules of pointer dominos in terms of operators, you have:
- & -- move up one level
-
- * -- move down one level
- [n] -- move down one level, with an offset of n elements
- + ++ -- add a scaled offset on the same level
- - -- -- subtract a scaled offset on the same level
- (type) -- change the size, format, and possible level of the expression to
- that of the type in the cast
- This set of six rules forms the guts of the game. All you need now is one
- additional rule, which tells you which order to apply the moves when more than
- one operator are present in the same expression.
- For instance, in the expression:
- *++p
- two operations, * (indirection) and ++ (pre-increment) are being applied to
- the pointer p. Which operation should you do first, the increment or the
- indirection? The resulting value will be very different depending on what you
- decide. If you did the * first, you would take the indirect value of p, and
- then increment that indirect value. In fact, this is just the opposite of what
- you are really supposed to do.
- The rules of precedence state that unary operators, like both pre-increment ++
- and *, group right to left. This means that the ++ binds with p before the *
- is even considered. You must increment the pointer and then take the indirect
- value. The point here is that the rules of precedence always determine the
- proper order of evaluation. This is our last key fact.
- Key Fact #11 -- If more than one operator is applied in an expression, apply
- the operators in order of precedence.
- A quick glance at the precedence table reveals that primary operators include
- both [] and (), while the * and & are weaker-binding unary operators.
- Furthermore, primary operators group left to right and unaries group right to
- left. In effect, this means that you will deal with the primary [] and ()
- first in left to right order, and then handle the unary *s or &s in right to
- left order.
- For example, the expression
- *p[n]
- contains two operators, one primary [] and one unary *. The [] binds first
- followed by the *, so the interpretation is "locate the array element p[n],
- then dereference this element."
- A more complex expression may have lots of operators to consider:
- *(char *)p2[n][m]
- Here both subscripts bind first, in left to right order. Then comes the cast,
- (char *), which changes the type of the value in p2[n][m] to be a pointer to
- char. Finally, the * on the left dereferences this casted pointer.
- Showing each step in order:
- 1. p2--starting with p2
- 2. p2[n]--access the nth element offset from p2
- 3. p2[n] [m]--access the mth element offset from p2[n]
- 4. (char *)p2[n][m]--cast to the type pointer to char
- 5. *(char *)p2[n][m]--dereference the resulting char pointer
- The rules are simple. Apply each pointer move in the proper order of
- precedence. Keep track of levels as you go. Now you are thinking just like the
- C compiler!
-
-
- Solving the Puzzle
-
-
- It's time to solve the puzzle presented at the beginning of this article.
- Though the puzzle has inordinately complex expressions, the rules of pointer
- dominos will make short work of the task. Listing 1 contains the puzzle again.
- What kind of data structures are you working with in this puzzle? Figure 1
- contains a picture of the data.
- As you can see in Figure 1, ap is an array of pointers to chars, each pointer
- aimed at one of five character strings. ap is a level two object. Why? First,
- because ap is an array, it has intrinsically one level of indirection. But the
- elements of ap are all pointers, each holding their own level one address. So
- ap evaluates to the address of a pointer, hence ap is a level two expression.
- app is similarly a level three expression. An array of level two pointers
- evaluates to the address of a level two pointer, hence app lives on level
- three. ppp is a level 3 pointer, and pppp is a level four pointer. The
- relationships between the pointers are illustrated in Figure 1.
- Here is the first expression to be printed:
- printf("%.*s", 2, *--**pppp);
- Do the easy part first. The %.*s format specifier means to fill in the * with
- the first argument in the argument list following the format string. So you
- are really asking for %.2s, that is, print the first two characters of the
- string *--**pppp. How do you unravel *--**pppp? With the rules of pointer
- dominos!
- Start at the identifier, pppp, and apply the operators in order of precedence.
- Both * and -- are unary operators so they group right to left, as follows:
- 1. pppp--first the identifier pppp
- 2. *pppp--right-most * dereference pppp
- 3. **pppp--second right-most * dereference *pppp
- 4. --**pppp--unary -- pre-decrement the result
- 5. *--**pppp left-most * dereference again
- Reading off the quoted strings gives a comprehensible formula for solving this
- part of the puzzle.
- To find the answer, start at the top of the diagram of the puzzle's data, at
- pppp, and move down two levels, following the arrows. You should be at app[0],
- the zeroth element in the app array of char ** pointers. app[0] holds the
- address of ap[4], the last element in the array of char * pointers. But now,
- the rules say you must apply the unary -- operator to app[0]. Instead of
- pointing at ap[4], app[0] will now hold the address of ap[3]! This change, by
- the way, persists, and changes the diagram slightly from that shown in Figure
- 1. After decrementing app[O], you apply the final dereference or *, and arrive
- at the contents of ap[3]. This is what you will print with the first
- expression. Actually, the program prints only the first two characters of the
- string PORTABLE. So PO appears on the output.
- What does the second expression print?
- printf("%.*s", 3, *(++*pppp[0] - 4));
- This complex expression again uses the %.*s mechanism to pick up the 3 as the
- number of chars to be printed. In effect, you will print three chars from the
- address given by the complex expression *(++*pppp [0] - 4)).
- This one breaks down as follows:
- 1. pppp--start at pppp
- 2. pppp[0]--dereference to access [0] th element
- 3. *pppp[0]--dereference pppp[0]
- 4. ++*pppp[0]--pre-increment the result
- 5. ++*pppp[0] - 4--subtract 4
- 6. *(++*pppp[0] - 4)--dereference again
- Handling each operator one step at a time gives you the solution.
- The only new trick here is the translation between [] and *. Remember the
- all-important a[n] == *(a + n) formula and you'll zip through the steps.
- Accessing the zeroth element of pppp is the same as dereferencing pppp. a[0]
- is always the same object as *a. So the subscript [0] and the right-most *
- bring you down two levels, just as before. Only now you are told to increment
- the result, namely app[0]. So app[0] now pops back right back to where it
- started in the first place, namely to point to ap[4], the string TOWER!.
- Now what? Now, you have to subtract 4 from this pointer. This is where it can
- get tough. But remember, app is an array of char ** pointers, so app[0] acts
- like a pointer to a pointer to a char. Decrementing means subtracting the
- space for four char * pointers. So app[0] - 4 points to ap[0], the very first
- string in the puzzle, INTEGER. You have to print three characters from this
- string. So INT appears on the display right after the PO. The screen says
- POINT.
-
- The third expression prints the whole of the expression:
- ++*--*++pppp[0] + 5
- This whopper breaks down according to the precedence rules as follows:
- 1. pppp
- 2. pppp[0]
- 3. ++pppp[0]
- 4. *++pppp[0]
- 5. -- *++pppp[0]
- 6. *--*++pppp[0]
- 7. ++*--*++pppp[0]
- 8. ++*--*++pppp[0] + 5
- Here's the explanation.
- Again [0] means move down one level, to ppp. Now increment ppp, so ppp points
- to ppp[1], not ppp[0] anymore. Dereference with * means move down to app[1].
- Now decrement app[1], so app[1] points to ap[2] from now on, rather than
- ap[3]. Dereferencing with * brings you down to ap[2]. Now increment ap[2], so
- the pointer to char, ap[2], points to the E rather than the D of DEBUGGER.
- Finally, add 5. Since you are now down at level one, the contents of ap[2],
- adding 5 means add the size of five chars to the pointer. Hence you are
- finally left pointing at the second E in DEBUGGER. Printing the resulting
- string, along with a space as the puzzle requires, gives us ER.
- The screen now says POINTER.
- Only two more. By the time you're done you'll never forget how to do this! The
- fourth printf statement looks like this:
- printf("%.*s", 2, *++pppp[0][3] + 3);
- You are asked to print two characters from the expression:
- *++pppp[0][3] + 3
- Here's the break down of the steps.
- 1. pppp
- 2. pppp[0]
- 3. pppp[0][3]
- 4. ++pppp[0][3]
- 5. *++pppp[0][3]
- 6. *++pppp[0][3] + 3
- Now just read through this break down, supplying the interpretation for each
- step.
- [0] again moves down to ppp. Adding a [3] to ppp means two things; go down to
- the next level, and offset by three elements. So while pppp[0][0] is app[1]
- (remember, you incremented ppp in the last step), pppp[0][3] is app[4]! Now
- increment this result, app[4], so app[4] points at ap[1], rather than ap[0].
- Dereferencing with the * brings you down to the contents of ap[1]. Adding 3,
- skips the first three characters of PROPORTION so you print the PO substring
- from the middle of PROPORTION.
- Now the screen reads POINTER PO.
- Time for the last one! See if you can get this one without an explanation. If
- you get to WER!, you're right. Remember to consider the changes that -- and ++
- created in the earlier expressions. Here's how.
- printf("%s\n", (*pppp + 2)[-2][2] + 2);
- 1. pppp
- 2. *pppp
- 3. *pppp + 2
- 4. (*pppp + 2)[-2]
- 5. (*pppp + 2)[-2][2]
- 6. (*pppp + 2)[-2][2] + 2
- Dereference pppp to ppp, now still pointing at app[1]. Adding 2 creates a
- pointer value pointing at app[3]. What does the [-2] subscript do? First it
- moves you down to the level of app[3], but the -2 means two objects lower in
- memory, so you move down to app[1], not app[3]. app[1] is still pointing at
- ap[2], where the -- in the third expression left app[1]. Applying the [2]
- subscript to this value of app[1] moves us down and over to ap[4], two
- pointers offset from ap[2]. The last + 2 skips over the first two chars in
- TOWER!, so you see the last part of the puzzle, WER! on the display.
- The screen says POINTER POWER! and you had better believe it!
- You may want to work the steps of this puzzle over several times, perhaps
- drawing some intermediate diagrams as the pointers change. If you can work
- this puzzle correctly, you have indeed acquired pointer power as a long-term
- resource for your future C and C++ programs!
- Remembering to carefully distinguish the Three Attributes, following the
- levels on the Ladder of Indirection, and applying the rules of Pointer Dominos
- will get you to the solution every time. Furthermore, understanding complex
- pointer behavior will give you the confidence to create your own sophisticated
- data-handling mechanisms, when and where appropriate. The examples in the
- puzzle, of course, are not designed to be "good code." They are designed to
- show that this most powerful part of C is indeed governed by straight-forward
- rules which can always be applied to understand and work with complex pointer
- declarations and expressions in C.
- Now it's on to C++, where void isn't quite so void anymore, where references
- are not pointers, but sometimes act like them, and where pointers to members
- are not even addresses in the standard C sense at all!
- Figure 1
-
- Listing 1 What does this program print?
- #include <stdio.h>
-
- char *ap[] = {
- "INTEGER",
- "PROPORTION",
- "DEBUGGER",
- "PORTABLE",
- "TOWER!"
- };
-
- char **app[] = { ap + 4, ap + 3, ap + 2, ap + 1, ap };
-
- char ***ppp = app;
-
- char ****pppp = &ppp;
-
-
- void main()
- {
- printf("%.*s", 2, *--**pppp);
-
- printf("%.*s", 3, *(++*pppp[0] - 4));
-
- printf("%s " , ++*--*++pppp[0] + 5);
-
- printf("%.*s" , 2, *++pppp[0][3] + 3);
-
- printf("%s\n", (*pppp + 2) [-2][2] + 2);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- C++, Coroutines, and Simulation
-
-
- Trond Akerbaek
-
-
- Trond Akerbaek is teaching at a college in Halden, Norway. Current interests
- include computer communications, neural networks, and fuzzy systems. He can be
- contacted at Ostfold DH; N-1757 Halden; Norway; e-mail:tronda@dhhalden.no.
-
-
-
-
- Introduction
-
-
- Object-oriented programming is not a new concept. The Simula67 Language,
- developed in the late sixties, contains all the basic concepts found in modern
- object-oriented languages such as C++. An important part of Simula not found
- in C++ is the support for quasi-parallel processes and simulation. Very few
- programmers ever use parallel functions or coroutines in their programs, even
- if it will lead to more elegant designs. There are several reasons: few
- languages have ample support, coroutines may otherwise be cumbersome to
- implement, and it is possible to solve most problems without.
- C programmers may use setjmp and longjmp and functions based on these to
- implement quasi-parallell routines. With C++ and some assembly instructions it
- is possible to create a set of functions which makes coroutines easier to
- comprehend and use. Using coroutines, implementing simple tools for simulation
- modelled after the Simula67 tools, is a fairly easy matter.
- In this article I will give a brief introduction to coroutines and describe an
- implementation in Borland C++ 2.0, with a single assembly function in Turbo
- Assembler. Based on the coroutine concept, the article will discuss simulation
- and how to implement it in C++.
-
-
- Quasi-Parallel Functions and Coroutines
-
-
- Due to the single-processor constraint, computer programming has traditionally
- been sequentially-oriented with a single thread of operations. Multitasking
- operating systems give us the opportunity to create programs consisting of
- several sequential processes operating in parallel on a timesharing basis. The
- operating system controls the task switching, with little or no influence from
- the programmer.
- The object-oriented approach is still basically sequential, even if it makes
- it easier to describe real processes which are interacting and operating in
- parallel. Parallel processes cannot directly be modelled in a sequential
- language. The process descriptions are running on a single processor, and must
- switch back and forth, imitating real parallel behavior, hence the expression
- quasi-parallel.
- It would be another situation if the model were running on a multiprocessor
- machine, with one processor for each process. In that case the model would be
- described in a language designed for such environments.
-
-
- The Coroutine Extensions
-
-
- In order to support the coroutine concept I have created a few extensions to
- C++:
- A base-class coroutine for all classes describing coroutines.
- A virtual function main in coroutine subclasses containing the actions of the
- coroutine.
- The primitive resume(coroutine*) which freezes the current coroutines actions,
- and transfers control to the indicated coroutine, resuming its actions at the
- freesing point.
- A new coroutine instance is initially frozen at the start of its main
- function.
- A primitive detach is available, which freezes the current coroutine actions
- and transfers control to the main program.
- A coroutine is terminated when its main function is terminated normally (not
- frozen by resume or detach). Resuming a terminated object leads to a runtime
- error.
- Automatic variables declared in a class method and alllocated on the stack are
- not availablee outside the couroutine instance. A variable declared in the
- main function of the program, is not available within the other coroutines.
- The main program is in fact a coroutine with the main function defining its
- actions.
- There must be no register variables, because registers will not be preserved
- changing coroutines.
-
-
- Implementation
-
-
- The implementation consists of the coroutine class, the two primitives resume
- and detach, and a few functions for internal use. Each coroutine, as the main
- program, has its own stack. Switching between coroutines is done by switching
- stacks. Whenever a coroutine is activated, the activating coroutine's stack is
- saved in memory, and the activated coroutine's previously stored stack is
- copied to the stack segment.
- Listing 1 contains the definitions. The variables of the coroutine class are
- used for keeping track of the passive stack. stkSegment and stkOffset are the
- segment and offset address of the area where the stack is stored, while
- stkSize holds the size of the used part of the stack.
- Listing 2 shows the implementation. There are three internal pointers in the
- implementation file: PREVIOUS referencing the coroutine to switch from,
- CURRENT referencing the new and active coroutine, and MAINC referencing the
- main program.
- When a new process is created as a coroutine subclass instance, the stack of
- the new process is initialized by coroutine::coroutine(). Space for the
- initial stack is allocated on the heap, and the addresses and the size are
- stored in the base class variables. This stack is initialized, so that on
- activation, the stack is copied from the heap, and control is passed to the
- superMain function.
- The trick is to store the address of the starting function (startProcess) on
- the stack as a return address. When the stack-switching function terminates,
- control returns to this address as if it was calling the function. This
- function in turn starts the superMain function, which calls the main function
- of the subclass. The superMain function also takes care of necessary
- terminating actions. A coroutine may be deleted. In that case the
- coroutine::~coroutine() method releases the stack area.
- The resume primitive switches control from the active coroutine to the new
- current specified as parameter to the primitve. At first, the size of the
- current stack is computed, and space for storage is allocated on the heap. The
- PREVIOUS and CURRENT variables are set, pointing to the two coroutines
- involved. The assembler function ctxtswc is callled to do the actual stack
- chnge. On return, the new coroutine is CURRENT and active, and the area for
- stack storoage is released. The detach primitive resumes MAINC, the main
- program.
- You find ctxtswc in Listing 3. The global variables CURRENT and PREVIOUS are
- imported by ctxtswc. Through these pointers, the stack storage area of the two
- coroutines involved, are accessible.
- ctxtswc works by copying the current system stack to the area referenced in
- the PREVIOUS coroutine instance. This copy of the stack now contains the
- return address from the ctxtswc call. Then the stack of the new CURRENT
- coroutine is copied from the storage area to the system stack area, and the SP
- register is reset. This stack now contains the return address from the ctxtswc
- call, last time it was called by the coroutine. Therefore, when ctxtswc
- returns, program execution continues where the resumed coroutine was stopped.
- This version is written for the compact memory model. Minor changes in ctxtswc
- and some of the C++ functions are necessary for other memory models.
-
-
- Simulation
-
-
-
- With the tools described in the previous sections it is possible to model
- interacting real-world processes in a quasi-parallel program. In such a model
- you can experiment. By changing the number of processes, the behavior of each,
- and the input data you can test the model to gain insight into how the real
- system would perform under different circumstances.
- Simulating real processes in a computer model is often cheaper than doing real
- life experiments. In some cases it is not even possible to experiment with the
- real system. Consider the case of a nuclear reactor. In order to study the
- behavior of the reactor operators in stress situations, e.g. when serious
- problems occur, it would be necessary to induce problems in the reactor. We
- cannot do that, for obvious reasons. Instead a control room connected to a
- computer model, simulating the reactor, is used in experiments and training.
- Real systems are often very complex. When creating a model, it is necessary to
- include those features that are crucial to the model's operation, and avoid
- insignificant details. Otherwise the simulation results may be of no value.
- The time aspect of simulation, which is very important in most real systems,
- is not handled within the coroutine concept. In the next sections, I will
- introduce a set of tools for modelling processes in simulated time with C++.
-
-
- An Example
-
-
- This is a hypothetical system consisting of a number of physicians working
- together in a clinic receiving patients. The system is simplified in order to
- keep the example reasonably small in size.
- The physicians receive patients from 8 A.M. until 4 P.M. Then the physicians
- work until no more patients are waiting. The patients arrive one at a time. On
- arrival the patient goes to the waiting room. If there is a physician in the
- lunchroom, he is summoned at once. Otherwise the patient waits for his turn.
- When a patient leaves the clinic, the physician will see the next patient. If
- nobody is waiting, he will go to the lunchroom for a coffee break.
- The physicians, observing that most of the day the waiting room is full, want
- to know if another physician should be invited to join the group. They are
- losing business because the patients must wait too long, but they are not sure
- if this might lead to an increasing number of coffee breaks.
- The model of this system consists of a clinic object, a number of physician
- objects, and the patients. For each of these objects, there is a set of
- actions controlling the objects and their interactions. As the objects operate
- in simulated time, their classes are derived from the class process, a
- subclass of the class coroutine, which contains the information necessary to
- support the time concept. Instead of the resume and detach primitives, a small
- set of more advanced primitives will be used. These are based on resume and
- detach, but are more suitable for simulations in time.
- Most simulation models depend on some kind of input data that specifies when
- the significant events occur. In this case, the important events are the
- arrivals of patients, and the duration of a consultation. It is possible to
- collect real data and use the information directly as input to the simulation
- model. Another solution is to analyze the input data and find the events'
- probability distributions. Input data may then be generated using random
- generators. The physicians have observed the arrival pattern of patients and
- found that patients arrive according to a Poisson distribution, and they have
- found the expected number of patients each minute. They have also found that
- the duration of each consultation is uniformly distributed over an interval.
- A simulation model should always be validated by comparing the results of a
- simulation with real input data to real system results. If there are
- discrepancies, the model must be modified.
- The implementation of the clinic system is found in Listing 4. The
- implementation consist of three kinds of processes, defined by the classes
- clinic, physician, and patient derived from the process base class. Each class
- has a constructor, and a main function describing the process actions. There
- is a main program which creates the process instances, starts the simulation
- and presents the results. This example just finds the average waiting time of
- the patients. Two functions are used to generate input to the model. The
- treatmentPeriod function returns a randomly-chosen value indicating the
- duration of a consultation, while periodBeforeNextArrival returns the
- randomly-chosen period between two arrivals.
- The clinic class references two FIFO queues, chain lunchRoom and waitingRoom,
- where the physicians and patients are kept while waiting. In addition, there
- are variables used for collecting statistical data. The physician and patient
- classes have a pointer to the clinic, and variables for statistical data.
- The physician and patient constructors set the clinic pointer. The clinic
- constructor creates the physician processes and schedules them to run at once.
- The constructors also initialize statistical variables.
- The clinic main function describes the clinic actions. The main task is
- generating new patients. Its main loop will run until simulated time
- (currentTime) reaches the clinic's closing time. In the loop a new patient is
- created, sent to the waiting room, and activated. If there is a physician in
- the lunchRoom she is summoned and activated. Then the clinic process
- reschedules itself and waits until the next patient arrives, with the hold
- primitive.
- The physician process is an eternal loop. Each time the physician process
- checks if the waitingRoom is empty. If so, she goes to the lunchRoom and has a
- break by calling the passivate primitive. This primitive will suspend the
- process until some other process activates it. If there are patients in the
- waitingRoom, the physician gets the next patient, activates him and suspends
- herself with the hold primitive while helping the patient. When the
- consultation ends, she will see the patient out, activating him, and repeat
- the loop.
- The patient process is simple. It is activated three times, first by the
- clinic process when the patient is sent to the waitingRoom, then by the
- physician on leaving the waitingRoom, and last by the physician when the
- consultation is over. Each time, the patient process records the time of the
- event for statistical purposes before passivating itself. (When the patient
- main function terminates, there is an implicit passivate call.)
- The main program of the system first initializes the random generator
- functions and the process library by calling initProcesses. Then it suspends
- itself until opening hours when a clinic process is created and activated. The
- main program again suspends itself for a long period, until the clinic is
- closed and all patients have left. At last statistical data are processed and
- displayed.
- In this model, each process acts on its own, performing actions. Between
- events, the processes are suspended waiting to be restarted, either
- automatically as with the hold primitive, or by another process with the
- activate primitive. It is much easier to understand the sequence of events for
- each process and to describe each process separately as in this example, than
- to describe the whole system in one piece. Besides, you can modify the model
- to include other objects such as secretaries, surgeons, several patient types,
- and other action sequences.
- After validating the model, you can simulate and investigate different
- scenarios by changing the number of physicians, the arrival pattern of
- patients, consultation time etc. It is easy to include statements to collect
- other types of statistical data, such as the number of minutes spent in the
- lunchroom, and the distribution of patient waiting times.
-
-
- The Process Extensions
-
-
- I created the following extensions supporting processes:
- Each process is defined as a subclass of the process base class.
- A function initProcesses initializes the process library.
- A virtual function main in descendants of the process class contains the
- actions of the process.
- activate(process*,float) schedules the indicated process to run at the
- specified point of time. If it is already scheduled to run, it is rescheduled.
- If the current process is activated, it is suspended and rescheduled.
- hold(float) suspends and reschedules the current process to run after the
- indicated period of time.
- passivate suspends the current process without rescheduling.
- currentTime returns current simulated time.
- Whenever the current process is suspended, simulated time is increased to the
- time of the next scheduled process, and this process is restarted. If no
- process is scheduled to run, a runtime error occurs. In this way simulated
- time grows from 0 in varying steps as controls are switched from process to
- process.
- In addition, a few primitives not used in this example, are available
- cancel(process *) deschedules the process indicated. Cancelling the current
- process is the same as passivate.
- currentProcess returns a pointer to the current process object.
- mainProcess returns a pointer to the main program process object. The main
- program in itself is a process, and can be treated as such.
- resume(coroutine*) and detach may be used with processes as the process class
- is derived from the coroutine class. Avoid this, it might lead to unexpected
- activation sequences.
-
-
- Implementation
-
-
- In Listing 5 you find the process class definition. The process class is a
- coroutine subclass with a single time variable which contains scheduled time
- to run. It is initially set by the process constructor.
- Listing 6 contains the implementation of the process library. The data
- structure of the process module is a priority queue SOS (The Sequence Set)
- where the scheduled processes are kept sorted in ascending order on the time
- variable. The current executing process is always the first object in the SOS.
- There is a process* MAINP which is used for referencing the main program as a
- process. CURRENT and MAINC are imported from the coroutine module. The
- initProcesses function initializes the data structure. A process object
- referencing the main program is created and inserted at the head of the SQS.
- MAINC and CURRENT used for referencing the main program and the current
- coroutine, are set to reference this main program process.
- The four scheduling primitives activate, hold, passivate, and cancel are
- implemented as functions manipulating the SQS. activate inserts a process in
- the priority queue, hold removes and reinserts the current process, cancel
- removes any process, and passivate removes the current process. Whenever the
- current process is suspended, control is transferred to the next.
- The three information primitives mainProcess, currentProcess, and currentTime
- just return state information: which is the main program process, which is
- currently executing, and what is the current simulated time.
- The internal superMain function is called from startProcess to manage the
- user-defined main function. On termination, the process is removed from the
- SQS and cannot be activated again.
-
-
- Some Comments on the Code listings
-
-
-
- As shown, the implementations of the process primitives are very simple, once
- the coroutine primitives are in place. If the need arise, you can create other
- scheduling primitives of your own design.
- The code shown in the listings has been compiled and tested using Borland's
- Turbo C++ 2.0 and Turbo Assembler 2.5, compact memory model. It is easy to
- modify the system for other memory models. I have removed some include
- statements referring to system functions and the description of the priority
- type class chain. You may implement this yourself, otherwise a complete system
- is available from the author on request.
- References
- Birtwistle, Dahl, Myhrhaug, Nygaard: "SIMULA BEGIN," Auerbach, Phil., 1973.
- (contains an introduction to the SIMULA programming language)
-
- Listing 1 The base class coroutine definition
- // COR.HPP
- typedef unsigned int word;
-
- class coroutine {
- friend void startProcess(void);
- friend void resume(coroutine*);
- word stkSegment,stkOffset,stkSize;
- virtual void main() {}
- virtual void superMain() {}
- public:
- coroutine(void);
- ~coroutine(void);
- };
- void resume(coroutine*);
- void detach(void);
-
- // End of File
-
-
- Listing 2 The base-class coroutine implementation
- // COR.CPP
- #include "cor.hpp"
- #include ...
-
- extern void ctxtswc(void);
- coroutine *MAINC=new coroutine();
- coroutine *PREVIOUS,*CURRENT=MAINC;
-
- coroutine::coroutine() {
- word *sp,*stkBase;
- stkSize= 2 * sizeof(word);
- stkBase=(word *) farmalloc(stkSize);
- stkSegment=FP_SEG(stkBase);
- stkOffset=FP_OFF(stkBase);
- sp=stkBase + 2;
- *--sp=(word) startProcess;}
-
- coroutine::~coroutine() {
- delete(MK_FP(stkSegment,stkOffset));}
-
- void coroutine::superMain(void) {
- main();
- resume(MAINC);
- FATAL("terminated coroutine resumed");}
-
- void startProcess(void) {
- CURRENT->superMain();}
-
- void resume(coroutine* rc)
- { word sp,*stkBase;
- if ((CURRENT != NULL) && (rc != CURRENT) &&
- (rc != NULL)) {
- sp=_SP;
-
- CURRENT->stkSize= _stklen - sp + 4;
- stkBase=
- (word*)farmalloc(CURRENT->stkSize);
- CURRENT->stkSegment=FP_SEG(stkBase);
- CURRENT->stkOffset=FP_OFF(stkBase);
- PREVIOUS=CURRENT;
- CURRENT=rc;
- ctxtswc();
- delete(MK_FP(CURRENT->stkSegment,
- CURRENT->stkOffset));
- }
- }
-
- void detach(void) {
- resume(MAINC);}
-
- // End of File
-
-
- Listing 3 The assembler routine for changing stacks
- ; ctxtswc will do a contextswitch by changing stacks.
- ; the stack in use will be stored on the heap, and the
- ; new stack which is previously stored on the heap,
- ; will be loaded.
- .MODEL COMPACT
- .CODE
- EXTRN _stklen:word
- EXTRN _PREVIOUS:dword
- EXTRN _CURRENT:dword
- PUBLIC @ctxtswc$qv
- @ctxtswc$qv PROC NEAR
- push bp ; save bp
- mov cx,_stklen ; put nmbr of bytes
- sub cx,sp ; in cx
- les bx,dword ptr DGROUP:_PREVIOUS
- mov word ptr es:[bx+4],cx ; save sz of usedstk
- mov di,word ptr es:[bx+2] ; set adr for stk
- mov es,word ptr es:[bx] ; copy in di and es
- mov ax,ds ; save ds
- mov bx,ss ; set adr of stk in
- mov ds,bx ; ds
- mov si,sp ; and si
- rep movsb ; do copy
- mov ds,ax ; reset ds
- les bx,dword ptr DGROUP:_CURRENT
- mov cx,word ptr es:[bx+4] ; get sz of stk copy
- mov ax,ds ; save ds
- mov sp,_stklen
- sub sp,cx ; set stkPtr
- mov si,word ptr es:[bx+2] ; set adr of stored
- mov ds,word ptr es:[bx] ; stk in si and ds
- mov di,sp ; set dest for copy
- mov dx,ss ; di=stkPtr
- mov es,dx ; and es=stkSeg
- rep movsb ; do copy
- mov ds,ax ; reset ds
- pop bp ; reset bp
- ret
- @ctxtswc$qv ENDP
-
- END
- ; End of File
-
-
- Listing 4 Simulation of a medical clinic
- #include "sim.hpp"
- #include ...
- #define nmbrOfPhysicians 4
- #define openingHours 480.0
- #define openPeriod 480.0
- #define consultationLow 5.0
- #define consultationHigh 20.0
- #define arrivalsExpected 0.25
-
- float treatmentPeriod(void)
- { return(fUniform(consultationLow,consultationHigh));
- }
-
- float periodBeforeNextArrival(void)
- { return(fNegexp(arrivalsExpected)); }
-
- class clinic : public process
- {public:chain lunchRoom,waitingRoom;
- float closingTime,totalWaitingTime;
- int totalNmbrOfPatients;
- clinic(int nrOfPhysicians,float Period);
- void main(void);
- };
-
- class physician : public process
- { clinic* theClinic;
- public: physician(clinic* cl) {theClinic=cl;}
- void main(void);
- };
-
- class patient : public process
- { clinic *theClinic;
- public: float startWaitingAt,startTreatmentAt,
- finishedAt;
- patient(clinic* cl) {theClinic=cl;}
- void main(void);
- };
-
- clinic::clinic(int nrOfPhysicians, float Period)
- { int i;
- totalWaitingTime=0.0;
- totalNmbrOfPatients=0;
- closingTime=currentTime() + Period;
- for (i=0;i<nrOfPhysicians;i++)
- activate(new physician(this),currentTime());
- }
-
- void clinic::main(void)
- { patient* ptnt;
- physician* phsn;
- hold(periodBeforeNextArrival());
- while (currentTime() < closingTime)
- { ptnt=new patient(this);
- totalNmbrOfPatients++;
-
- waitingRoom.append(ptnt);
- activate(ptnt,currentTime());
- if (!lunchRoom.empty())
- { phsn=(physician*)lunchRoom.
- getfirst();
- activate(phsn,currentTime());
- }
- hold(periodBeforeNextArrival());
- }
- }
-
- void physician::main(void)
- { patient *ptnt;
- while(1)
- { if (theClinic->waitingRoom.empty())
- { theClinic->lunchRoom.append(this);
- passivate();
- }
- else
- { ptnt=(patient*)theClinic->waitingRoom.
- getfirst();
- activate(ptnt,currentTime());
- hold(treatmentPeriod());
- activate(ptnt,currentTime());
- }
- }
- }
-
- void patient::main(void)
- {
- startWaitingAt=currentTime();
- passivate();
- startTreatmentAt=currentTime();
- passivate();
- finishedAt=currentTime();
- theClinic->totalWaitingTime+=
- (startTreatmentAt-startWaitingAt);
- }
-
- void main(void)
- { clinic *clnc;
- initRandom();
- initProcesses();
- hold(openingHours - currentTime());
- activate(clnc=new clinic(
- nmbrOfPhysicians,openPeriod),
- currentTime());
- hold(10000.0);
- cout << "Total number of patients ="
- << clnc->totalNmbrOfPatients << end1;
- cout << "Average waiting time ="
- << clnc->totalWaitingTime/
- clnc->totalNmbrOfPatients << end1;
- }
-
- // End of File
-
-
- Listing 5 The class process definition
-
- // SIM.HPP
- #include "cor.hpp"
-
- class process : private coroutine
- {
- friend void startProcess(void);
- friend float currentTime(void);
- friend void hold(float interval);
- friend void passivate(void);
- friend void activate(process *p,float time);
- friend void cancel (process *p);
- float time;
- virtual void superMain(void);
- virtual void main(void) {}
- public: process (void) {time=0.0;}
- ~process (void) {}
- };
-
- void initProcesses(void);
- void activate(process *p,float time);
- void hold(float interval);
- void passivate(void);
- void cancel (process *p);
- process* mainProcess(void);
- process* currentProcess (void);
- float currentTime(void);
-
- // End of File
-
-
- Listing 6 A library implementation for the clinic simulation
- // SIM.CPP
- #include "sim.hpp"
- #include ...
-
- extern coroutine* CURRENT;
- extern coroutine* MAINC;
- process* MAINP=NULL;
- chain SQS;
-
- void initProcesses(void)
- { if (MAINP == NULL)
- { MAINP=newprocess();
- SQS.put(MAINP,0);
- delete MAINC;
- MAINC=CURRENT=(coroutine*)MAINP;
- }
- }
-
- process* mainProcess(void)
- { return(MAINP); }
-
- process* currentProcess(void)
- { return((process *) CURRENT); }
-
- float currentTime(void)
- { return( ((process*)CURRENT)->time); }
-
- void hold(float interval)
-
- { if (interval > 0)
- { SQS.get(CURRENT);
- ((process *)CURRENT)->time+=interval;
- SQS.put(CURRENT,
- ((process *)CURRENT)->time);
- resume((coroutine*)SQS.first());
- }
- }
-
- void passivate(void)
- { SQS.get(CURRENT);
- if (SQS.first() == NULL)
- FATAL("Sequence Set is empty");
- resume((coroutine *)SQS.first());
- }
-
- void activate(process *p,float time)
- { if (p == CURRENT)
- hold(time - currentTime());
- else
- { SQS.get(p);
- p->time= ((time > currentTime())?
- time : currentTime());
- SQS.put(p,p->time);
- }
- }
-
- void cancel(process *p)
- { SQS.get(p);
- if (p == CURRENT)
- { if (SQS.first() == NULL)
- FATAL("Sequence Set is empty");
- resume((coroutine *)SQS.first());
- }
- }
-
- void process::superMain(void)
- { main();
- passivate();
- FATAL("terminated process activated");}
-
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- Time Formatting Functions
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of The C Users Journal. He is convenor of the
- ISO C standards committee, WG14, and active on the C++ committee, WG21. His
- latest books are The Standard C Library, published by Prentice-Hall, and ANSI
- and ISO Standard C (with Jim Brodie), published by Microsoft Press. You can
- reach him at pjp@plauger.com.
-
-
- This is the last of three installments on the functions declared in <time.h>.
- (See "The Header <time.h>," CUJ January 1993 and "Time Conversion Functions ,"
- CUJ February 1993.) I conclude by describing the functions that convert
- encoded times to human-readable text strings.
- Standard C provides an extraordinary ability to capture times and dates. It
- also lets you convert freely between scalar and component forms. That lets you
- do arithmetic on components such as days or months, leaving to the library the
- hard work of correcting for calendar irregularities. The Standard C library
- even lets you switch between local and universal (UTC or GMT) time zones,
- correcting as needed for Daylight Savings Time. (All that assumes that the
- library has some way of learning time zone information from the environment,
- of course.)
- The icing on the cake is the ability to convert times to human-readable form.
- C has long had the functions ctime and asctime to perform this useful service.
- Committee X3J11 added the more general function strftime. It lets you pick the
- time and date components you wish to include. It also adapts to the current
- locale. Thus Standard C now encourages programs that print time information in
- the most useful form for each culture. (Again, that assumes that the library
- has a nontrivial implementation of the locale machinery.)
- The topic for this month is the remaining functions declared in
- <time.h>--those that convert times to text strings. They are not as hairy as
- the conversion functions I described last month, but they have their own
- complexities.
-
-
- What the C Standard Says
-
-
-
-
- 7.12.3 Time conversion functions
-
-
- Except for the strftime function, these functions return values in one of two
- static objects: a broken-down time structure and an array of char. Execution
- of any of the functions may overwrite the information returned in either of
- these objects by any of the other functions. The implementation shall behave
- as if no other library functions call these functions.
-
-
- 7.12.3.1 The asctime function
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- char *asctime
- (const struct tm *timeptr);
-
-
- Description
-
-
- The asctime function converts the broken-down time in the structure pointed to
- by timeptr into a string in the form
- Sun Sep 16 01:03:52 1973\n\0
- using the equivalent of the following algorithm.
- char *asctime
- (const struct tm *timeptr)
- {
- static const char wday_name[7][3] = {
- "Sun", "Mon", "Tue", "Wed",
- "Thu", "Fri", "Sat"
- };
-
- static const char mon_name[12][3] = {
- "Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
- };
- static char result[26];
-
- sprintf(result,
- "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
- wday_name[timeptr->tm_wday],
- mon_name[timeptr->tm_mon],
- timeptr->tm_mday, timeptr->tm_hour,
- timeptr->tm_min, timeptr->tm_sec,
- 1900 + timeptr->tm_year);
- return result;
- }
-
-
- Returns
-
-
- The asctime function returns a pointer to the string.
-
-
- 7.12.3.2 The ctime function
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- char *ctime(const time_t *timer);
-
-
- Description
-
-
- The ctime function converts the calendar time pointed to by timer to local
- time in the form of a string. It is equivalent to
- asctime (localtime(timer))
-
-
- Returns
-
-
- The ctime function returns the pointer returned by the asctime function with
- that broken-down time as argument. Forward references: the localtime function
- (7.12.3.4).
- ....
-
-
- 7.12.3.5 The strftime function
-
-
-
-
- Synopsis
-
-
- #include <time.h>
- size_t strftime(char *s,
-
- size_t maxsize,
- const char *format,
- const struct tm *timeptr);
-
-
- Description
-
-
- The strftime function places characters into the array pointed to by s as
- controlled by the string pointed to by format. The format shall be a multibyte
- character sequence, beginning and ending in its initial shift state. The
- format consists of zero or more conversion specifiers and ordinary multibyte
- characters. A conversion specifier consists of a % character followed by a
- character that determines the behavior of the conversion specifier. All
- ordinary multibyte characters (including the terminating null character) are
- copied unchanged into the array. If copying takes place between objects that
- overlap, the behavior is undefined. No more than maxsize characters are placed
- into the array. Each conversion specifier is replaced by appropriate
- characters as described in the following list. The appropriate characters are
- determined by the LC_TIME category of the current locale and by the values
- contained in the structure pointed to by timeptr.
- "%a" is replaced by the locale's abbreviated weekday name.
- "%A" is replaced by the locale's full weekday name.
- "%b" is replaced by the locale's abbreviated month name.
- "%B" is replaced by the locale's full month name.
- "%c" is replaced by the locale's appropriate date and time representation.
- "%d" is replaced by the day of the month as a decimal number (01-31).
- "%H" is replaced by the hour (24-hour clock) as a decimal number (00-23).
- "%I" is replaced by the hour (12-hour clock) as a decimal number (01-12).
- "%j" is replaced by the day of the year as a decimal number (001-366).
- "%m is replaced by the month as a decimal number (01-12).
- "%M" is replaced by the minute as a decimal number (00-59).
- "%p" is replaced by the locale's equivalent of the AM/PM designations
- associated with a 12-hour clock.
- "%S" is replaced by the second as a decimal number (00-61).
- "%U" is replaced by the week number of the year (the first Sunday as the first
- day of week 1) as a decimal number (00-53).
- "%w" is replaced by the weekday as a decimal number (0-6), where Sunday is 0.
- "%W" is replaced by the week number of the year (the first Monday as the first
- day of week 1) as a decimal number (00-53).
- "%x" is replaced by the locale's appropriate date representation.
- "%X" is replaced by the locale's appropriate time representation.
- "%y" is replaced by the year without century as a decimal number (00-99).
- "%Y" is replaced by the year with century as a decimal number.
- "%Z" is replaced by the time zone name or abbreviation, or by no characters if
- no time zone is determinable.
- "%%" is replaced by %.
- If a conversion specifier is not one of the above, the behavior is undefined.
-
-
- Returns
-
-
- If the total number of resulting characters including the terminating null
- character is not more than maxsize, the strftime function returns the number
- of characters placed into the array pointed to by s not including the
- terminating null character. Otherwise, zero is returned and the contents of
- the array are indeterminate.
-
-
- The Function strftime
-
-
- The one complicated function declared in <time.h> (from the outside, at least)
- is strftime. You use it to generate a text representation of a time and date
- from a struct tm under control of a format string. In this sense, it is
- modeled after the print functions declared in <stdio. h>. It differs in two
- important ways:
- strftime does not accept a variable argument list. It obtains all time and
- date information from one argument.
- The behavior of strftime can vary considerably among locales. The locale
- category LC_TIME can, for example, specify that the text form of all dates
- follow the conventions of the French culture.
- For example, the code fragment:
- char buf[100];
-
- strftime(buf, sizeof buf,
- "%A, %x", localtime(&t0));
- might store in buf any of:
- Sunday, 02 Dec 1979
- dimanche, le 2 decembre 1979
- Weekday 0, 02/12/79
- If your goal is to display times and dates in accordance with local custom,
- then strftime gives you just the flexibility you need. You can even write
- multi-byte-character sequences between the conversion specifiers. That lets
- you convert dates to Kanji and other large character sets.
- Here are the conversion specifiers defined for strftime. I follow each with an
- example of the text it produces. The examples are all from P.J. Plauger and
- Jim Brodie, ANSI and ISO Standard C, Microsoft Press, 1992. All assume the "C"
- locale and the date and time Sunday, 2 December 1979 at 06:55:15 AM EST:
- %a -- the abbreviated weekday name (Sun)
- %A -- the full weekday name (Sunday)
-
- %b -- the abbreviated month name (Dec)
- %B -- the full month name (December)
- %c -- the date and time (Dec 2 06:55:15 1979)
- %d -- the day of the month (02)
- %H -- the hour of the 24-hour day (06)
- %I -- the hour of the 12-hour day (06)
- %j -- the day of the year, from 001 (335)
- %m -- the month of the year, from 01 (12)
- %M -- the minutes after the hour (55)
- %p -- the AM/PM indicator (AM)
- %S -- the seconds after the minute (15)
- %U -- the Sunday week of the year, from 00 (48)
- %w -- the day of the week, from 0 for Sunday (0)
- %W -- the Monday week of the year, from 00 (47)
- %x -- the date (Dec 2 1979)
- %X -- the time (06:55:15)
- %y -- the year of the century, from 00 (79)
- %Y -- the year (1979)
- %Z -- the time zone name, if any (EST)
- %% -- the per cent character (%)
-
-
- Using the Formatting Functions
-
-
- Note that the two functions that return a value of type pointer to char return
- a pointer to a static data object. Thus, a call to one of these functions can
- alter the value stored on behalf of an earlier call to another (or the same)
- function. Be careful to copy the value stored in one of these shared data
- objects if you need the value beyond a conflicting function call.
- asctime--(The asc comes from ASCII, which is now a misnomer.) Use this
- function to generate the text form of the date represented by the argument
- (which points to a broken-down time). The function returns a pointer to a
- null-terminated string that looks like "Sun Dec 2 06:55:15 1979\n". This is
- equivalent to calling strftime with the format "%a %c\n" in the "C" locale.
- Call asctime if you want the English-language form regardless of the current
- locale. Call strftime if you want a form that changes with locale. See the
- warning about shared data objects, above.
- ctime--ctime(pt) is equivalent to the expression asctime(localtime(pt)). You
- use it to convert a calendar time directly to a text form that is independent
- of the current locale. See the warning about shared data objects, above.
- strftime--This function generates a null-terminated text string containing the
- time and date information that you specify. You write a format string argument
- to specify a mixture of literal text and converted time and date information.
- You specify a broken-down time to supply the encoded time and date
- information. The category LC_TIME in the current locale determines the
- behavior of each conversion.
-
-
- Implementing the Conversion Functions
-
-
- These functions convert encoded times to text strings in various ways. All
- depend, in the end, on the internal function _Strftime to do the actual
- conversion. What varies is the choice of locale. The function asctime (and, by
- extension, the function ctime) convert times by a fixed format, following the
- conventions of the "C" locale regardless of the current state of the locale
- category LC_TIME. The function strftime, on the other hand, lets you specify a
- format that directs the conversion of a broken-down time. It follows the
- conventions of the current locale. Thus, one of the arguments to _Strftime
- specifies the locale-specific time information (of type_Tinfo) to use.
- Listing 1 shows the file asctime.c. It defines the function asctime that
- formats a broken-down time the same way irrespective of the current locale.
- The file also defines the data object _Times that specifies the
- locale-specific time information. And it defines the internal data object
- ctinfo, which replicates the time information for the "C" locale.
- Listing 2 shows the file ctime.c. The function ctime simply calls localtime,
- then asctime, to convert its time_t argument. Thus, it always follows the
- conventions of the "C" locale.
- Listing 3 shows the file strftime.c. The function strftime calls _Strftime,
- using the locale-specific time information stored in_Times. Thus, its behavior
- changes with locale.
- Listing 4 shows the file xstrftim.c. It defines the internal function
- _Strftime that does all the work of formatting time information. _Strftime
- uses the macro PUT, defined at the top of the file xstrftim.c, to deliver
- characters. The macro encapsulates the logic needed to copy generated
- characters, count them, and limit the number delivered.
- The internal function _Mbtowc, declared in <stdlib.h>, parses the format as a
- multibyte string using state memory of type _Mbstate that you provide on each
- call.
- Listing 5 shows the file xgentime.c It defines the function _Gentime that
- performs the actual conversions for _Strftime. The function _Gentime consists
- primarily of a large switch statement that processes each conversion
- separately.
- Each conversion determines a pointer p to a sequence of characters that gives
- the result of the conversion. It also stores a signed integer count at *pn. A
- positive count instructs _Strftime to generate the designated sequence of
- characters.
- One source of generated characters is the function _Get_time, which selects a
- field from one of the strings in the locale-specific time information. Another
- is the internal function getval, also defined in the file xgentime.c, which
- generates decimal integers. getval stores characters in the accumulator
- provided by _Strftime.
- Note that _Gentime includes a nonstandard addition. The conversion specifier
- %D converts the day of the month with a leading space in place of a leading 0.
- That's what asctime insists on.
- _Gentime returns a negative count to instruct _Strftime to "push down" a
- format string for a locale-specific conversion. Three conversions change with
- locale: %c, %x, and %X. (The conversion %x, for example, becomes the format
- string "%b %d %Y" in the "C" locale.) You express these conversions as format
- strings that invoke the other conversions. Note that the function_Strftime
- supports only one level of format stacking.
- The other internal function in the file xgentime.c is wkyr. It counts weeks
- from the start of the year for a given day of the year. The week can begin on
- Sunday (wstart is 0) or Monday (wstart is 1). The peculiar logic avoids
- negative arguments for the modulus and divide operators.
-
-
- Conclusion
-
-
- For over two and a half years, I have used this column for a single
- purpose--to provide a guided tour of the Standard C library. Along the way, I
- got out a book of the same name. About half these installments have been
- excerpts from the completed book. With this installment, I bring to a close
- that guided tour.
- Starting next month, I'll pick up on other standards activities in the C
- community. That's more than you might think. When I started this column, I was
- a member of one ANSI standards committee. Now I find myself attending meetings
- for two ANSI committees and three ISO committees. Don't think I do it because
- it's unmitigated fun. I just think standards activities are too important
- these days to ignore. I'll do my best to keep you informed too.
- This article is excerpted in part from P.J. Plauger, The Standard C Library,
- (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
-
- Listing 1 asctime.c
- /* asctime function */
- #include "xtime.h"
-
-
- /* static data */
- static const char ampm[] = {":AM:PM"};
- static const char days[] = {
- ":Sun:Sunday:Mon:Monday:Tue:Tuesday:Wed:Wednesday"
- ":Thu:Thursday:Fri:Friday:Sat:Saturday"};
- static const char fmts[] = {
- "%b %D %H:%M:%S %Y%b %D %Y%H:%M:%S"};
- static const char isdst[] = {""};
- static const char mons[] = {
- ":Jan:January:Feb:February:Mar:March"
- ":Apr:April:May:May:Jun:June"
- ":Jul:July:Aug:August:Sep:September"
- ":Oct:October:Nov:November:Dec:December"};
- static const char zone[] =
- {""}; /* adapt by default */
- static_Tinfo ctinfo = {ampm, days, fmts, isdst, mons, zone};
- _Tinfo_Times = {ampm, days, fmts, isdst, mons, zone};
-
- char *(asctime)(const struct tm *t)
- { /* format time as
- "Day Mon dd hh:mm:ss yyyy\n" */
- static char tbuf[] =
- "Day Mon dd hh:mm:ss yyyy\n";
-
- _Strftime(tbuf, sizeof (tbuf), "%a %c\n",
- t, &ctinfo);
- return (tbuf);
- }
-
- /* End of File */
-
-
- Listing 2 ctime.c
- /* ctime function */
- #include <time.h>
-
- char *(ctime)(const time_t *tod)
- { /* convert calendar time to local text */
- return (asctime(localtime(tod)));
- }
-
- /* End of File */
-
-
- Listing 3 strftime.c
- /* strftime function */
- #include "xtime.h"
-
- size_t (strftime)(char *s, size_t n,
- const char *fmt, const struct tm *t)
- { /* format time to string */
- return (_Strftime(s, n, fmt, t, &_Times));
- }
-
- /* End of File */
-
-
- Listing 4 xstrftim.c
- /* _Strftime function */
-
- #include <stdlib.h>
- #include <string.h>
- #include "xtime.h"
-
- /* macros */
- #define PUT(s, na) (void)(nput = (na), \
- 0 < nput && (nchar += nput) <= bufsize ? \
- (memcpy(buf, s, nput), buf += nput) : 0)
-
- size_t_Strftime(char *buf, size_t bufsize, const char *fmt,
- const struct tm *t, _Tinfo *tin)
- { /* format time information */
- const char *fmtsav, *s;
- size_t len, lensav, nput;
- size_t nchar = 0;
-
- for (s = fmt, len = strlen(fmt), fmtsav = NULL; ; fmt = s)
- { /* parse format string */
- int n;
- wchar_t wc;
- _Mbsave state = {0};
-
- while (0 < (n = _Mbtowc(&wc, s, len, &state)))
- { /* scan for '%' or '\0' */
- s += n, len -= n;
- if (wc == '%')
- break;
- }
- if (fmt < s) /* copy any literal text */
- PUT(fmt, s - fmt - (0 < n ? 1 : 0));
- if (0 < n)
- { /* do the conversion */
- char ac[20];
- int m;
- const char *p = _Gentime(t, tin, s++, &m, ac);
-
- --len;
- if (0 <= m)
- PUT(p, m);
- else if (fmtsav == NULL)
- fmtsav = s, s = p, lensav = len,
- len = -m;
- }
- if (0 == len && fmtsav == NULL n < 0)
- { /* format end or bad multibyte char */
- PUT("", 1); /* null termination */
- return (nchar <= bufsize ? nchar - 1 : 0);
- }
- else if (0 == len)
- s = fmtsav, fmtsav = NULL, len = lensav;
- }
- }
-
- /* End of File */
-
-
- Listing 5 xgentime.c
- /* _Gentime function */
- #include "xtime.h"
-
-
- /* macros */
- #define SUNDAY 0 /* codes for tm_wday */
- #define MONDAY 1
-
- static char *getval(char *s, int val, int n)
- { /* convert a decimal value */
- if (val < 0)
- val = 0;
- for (s += n, *s = '\0'; 0 <= --n; val /= 10)
- *--s = val % 10 + '0';
- return (s);
- }
-
- static int wkyr(int wstart, int wday, int yday)
- { /* find week of year */
- wday = (wday + 7 - wstart) % 7;
- return (yday - wday + 12) / 7 - 1;
- }
-
- const char *_Gentime(const struct tm *t,
- _Tinfo *tin, const char *s, int *pn, char *ac)
- { /* format a time field */
- const char *p;
-
- switch (*s++)
- { /* switch on conversion specifier */
- case 'a': /* put short weekday name */
- p = _Gettime(tin->_Days, t->tm_wday << 1, pn);
- break;
- case 'A': /* put full weekday name */
- p = _Gettime(tin->_Days, (t->tm_wday << 1) + 1, pn);
- break;
- case 'b': /* put short month name */
- p = _Gettime(tin->_Months, t->tm_mon << 1, pn);
- break;
- case 'B': /* put full month name */
- p = _Gettime(tin->_Months, (t->tm_mon << 1) + 1, pn);
- break;
- case 'c': /* put date and time */
- p = _Gettime(tin->_Formats, 0, pn),
- *pn = -*pn;
- break;
- case 'd': /* put day of month, from 01 */
- p = getval(ac, t->tm_mday, *pn = 2);
- break;
- case 'D': /* put day of month, from 1 */
- p = getval(ac, t->tm_mday, *pn = 2);
- if (ac[0] == '0')
- ac [0] = ' ';
- break;
- case 'H': /* put hour of 24-hour day */
- p = getval(ac, t->tm_hour, *pn = 2);
- break;
- case 'I': /* put hour of 12-hour day */
- p = getval(ac, t->tm_hour % 12, *pn = 2);
- break;
- case 'j': /* put day of year, from 001 */
- p = getval(ac, t->tm_yday + 1, *pn = 3);
-
- break;
- case 'm': /* put month of year, from 01 */
- p = getval(ac, t->tm_mon + 1, *pn = 2);
- break;
- case 'M': /* put minutes after the hour */
- p = getval(ac, t->tm_min, *pn = 2);
- break;
- case 'p': /* put AM/PM */
- p = _Gettime(tin->_Ampm, 12 <= t->tm_hour, pn);
- break;
- case 'S': /* put seconds after the minute */
- p = getval(ac, t->tm_sec, *pn = 2);
- break;
- case 'U': /* put Sunday week of the year */
- p = getval(ac,
- wkyr(SUNDAY, t->tm_wday, t->tm_yday),
- *pn = 2);
- break;
- case 'w': /* put day of week, from Sunday */
- p = getval(ac, t->tm_wday, *pn = 1);
- break;
- case 'W': /* put Monday week of the year */
- p = getval(ac,
- wkyr(MONDAY, t->tm_wday, t->tm_yday),
- *pn = 2);
- break;
- case 'x': /* put date */
- p =_Gettime(tin->_Formats, 1, pn),
- *pn = -*pn;
- break;
- case 'X': /* put time */
- p = _Gettime(tin->_Formats, 2, pn),
- *pn = -*pn;
- break;
- case 'y': /* put year of the century */
- p = getval(ac, t->tm_year % 100, *pn = 2);
- break;
- case 'Y': /* put year */
- p = getval(ac, t->tm_year + 1900, *pn = 4);
- break;
- case 'Z': /* put time zone name */
- if (tin->_Tzone[0] == '\0')
- tin->_Tzone = _Getzone(); /* adapt zone */
- p = _Gettime(tin->_Tzone, 0 < t->tm_isdst, pn);
- break;
- case '%': /* put "%" */
- p = "%", *pn = 1;
- break;
- default: /* unknown field, print it */
- p = s - 1, *pn = 2;
- }
- return (p);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Function Return Values
-
-
-
-
- Kenneth Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C+ +
- language courses for corporations. He is the author of C Language for
- Programmers and All On C, and was a member on the ANSI C committee. He also
- does custom C programming for communications, graphics, image databases, and
- hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707.
- You may fax questions for Ken to (919) 489-5239. Ken also receives email at
- kpugh@dukemvs.ac.duke.edu (Internet) and on Compuserve 70125,1142.
-
-
- Q
- First of all I want to thank you for a wonderful magazine and informative
- answers to a lot of good programming questions. I have two easy ones for you.
- Why do most of the Standard C library string functions (strcpy, strcat) return
- a char * when they already modify the string through formal parameters? I have
- seen:
- s2 = strcpy(s2, s1);
- a lot less than I have seen:
- strcpy(s2, s1);
- I remember your saying something about return values screwing up the stack if
- they are not captured, and this seems like a major offender.
- The second question is geared towards C++. I have never seen a constructor or
- destructor declared with a return value. Are we assuming they return void or
- int, either of which would be better style to specifically declare?
- Thanks in advance for your time and useful information, and keep up the great
- column.
- Andrew Tucker
- Seattle Pacific University
- A
- Let's take your questions in order. I can't ever remember saying anything
- about not using the return value. It usually gets passed back in a register
- and if you do not use it, the value simply disappears when the register is
- reused.
- One reason for the char * return value is to allow the functions to be nested.
- You could specify a series of operations as:
- strcpy(a,"");
- strcat(a, b);
- strcat(a, c);
- or as:
- strcat(strcat(strcpy (a,""),
- b), c);
- I cannot say I particularly prefer the latter. Of course, if you were using
- C++ and a String class, the point becomes moot. String classes overload the
- operator + to mean concatenation. The assignment operator is also usually
- overloaded to mean string copying. So the set of operations can be expressed
- as:
- a = "" + b + c;
- or
- a = b + c;
- Functions such as fgets also return a character pointer, probably for the same
- reason. Although this is consistent with the str__ functions, it is
- inconsistent with the return type of most of the other file functions.
- As for your second question, constructors and destructors do not return any
- value. They are implicitly called in declarations, so there is no opportunity
- for them to return a value. You might call this a void return value, but that
- implies to me that the function actually is called normally.
- For example, suppose you coded:
- #include "string.hpp"
- a_function()
- {
- String a_string;
- ...
- }
- The constructor String(void) is implicitly called when a_string is declared.
- At the terminating brace (when a_string goes out of scope), the destructor
- ~String(void) is implicitly called.
- The lack of return value relates to the problem of what to do if there is an
- error in the constructor or destruct. For example, the initialization values
- might be out of range. You cannot return an error code. You have several
- choices. They include aborting the program, issuing a message alternative to
- the standard error output, or simply substituting in valid values. Just to be
- complete, let me note that a constructor can be called explicitly as a normal
- function. For example, if you had a string = string ("abc"), the constructor
- string (char*) is called and creates an object of class string. However, there
- is still no way to return an error code. A destructor can also be called
- explicitly, but the use for that is fiarly obscure. (KP)
-
-
- Check Digits
-
-
- This is in partial response to your answer to a question by Brendan O'Haire in
- the November 1992 issue of The C Users Journal. I came across this in reading
- sometime early in my career, 1958-1964 perhaps. If you would like proofs of
- the mathematical assertions, I will supply them on request. I remember the
- source where I read this claimed it was a technique widely-used in the
- business world. I cannot argue for or against that nor can I cite the original
- source.
- This procedure inserts a check "digit" into a number in such a way that all
- single transpositions of adjacent digits can be detected. It can actually
- detect single transposition at odd distance, but the adjacent transposition is
- the more probable keying error. The mathematical algorithm used to compute the
- check digit is actually an algorithm for computing the remainder upon division
- by 11.
- I'll use an example. Suppose the number to be encoded is 34781 and you wish to
- make the check digit the third digit from the right. Let Y represent the check
- digit. The new number will have the form 347Y81. Form a sum alternately adding
- and subtracting the digits in the number:
-
- 1 - 8 + Y - 7 + 4 - 3 = -13 + Y
- If the numeric part of this, -13 in this case, is not between -10 and 10
- repeat the process:
- -13 + Y -> -(3 - 1) + Y = -2 + Y
- At this point pick a single digit for Y so the sum is either zero or eleven. Y
- is 2 in this case. It is possible Y might be 10 in some cases. The Roman
- numeral X is used as the "digit" in this case. The original number with the
- check digit inserted is now 347281. When the alternate addition and
- subtraction is performed:
- 1 - 8 + 2 - 7 + 4 - 3 = -11 -> -(1 - 1) = 0
- then the ultimate value should be zero. If two digits are transposed, 342781
- for example, the algorithm produces -1 for the example. A non-zero value
- indicates the number is not correct.
- The mathematical argument depends on the fact that any integer, in decimal
- notation, can be represented as a sum of two terms, A+B, where A is the
- alternating sum and difference of the digits and B is an exact multiple of 11.
- This latter fact is derived from two assertions which can be proved by
- mathematical induction.
- (10 ** (2k + 1)) + 1 and (10**2k) - 1, k = 0, 1, 2,...
- are each divisible by 11. All the terms in B have a factor of one or the other
- of these forms.
- Walter Beck
- I read with some interest the letter from Brendan O'Haire in your column in
- the November CUJ regarding check digits. I'm no mathematician nor a theorist,
- so I don't know the reasons why these things are done the way they are.
- However, I have recently run into a couple of algorithms for calculation of
- check digits in real-world programs, which differ significantly from the
- simple algorithm you gave in your response.
- The first one I tripped over was in doing some database conversion for a
- client. Part of the data were bank routing numbers, used for Electronic Funds
- Transfers (these numbers are, apparently, assigned by the Federal Reserve
- Bank). They are eight-digit numbers with a ninth digit appended, which is the
- check digit. The algorithm I was given to use for calculating these numbers is
- shown in the code in Listing 1.
- Also, I am currently involved in attempting to understand the specifications
- from HL-7. (HL-7 is a standard for data messages to be used in Health Care
- settings.) Some of the data types specified in the standard are short text
- strings with a check digit calculated either as mod 10 or mod 11. After some
- digging I was given the following description of the algorithm:
- Assume you have an identifier equal to 12345. Take the odd digit positions,
- counting from the right, i.e., 531, multiply this number by 2 to get 1062.
- Take the even digit positions, starting from the right, i.e., 42, append these
- to the 1062 to get 421062. Add all of these six digits together to get 15.
- Subtract this number from the next highest multiple of 10, i.e., 20 - 15 to
- get 5. The Mod10 check digit is 5. The Mod10 check digit for 401 is 0, for
- 9999, it's 4, for 9999999, it's 7.
- Listing 2 is my (possibly simplistic, and certainly not terribly efficient)
- implementation of this algorithm.
- I always enjoy reading your column. Keep up the good work!
- Fred Smith
- Thank you both for your contribution. I had an encoding course about twenty
- years ago, but the algorithms I recall were mainly for correction of binary
- values. They help in detecting and/or correcting binary digit errors (1 for 0
- or 0 for 1). The parity bit on memory bytes is an example of a single error
- detection code. It could not detect a transposition error (switching two
- binary digits). Cyclic redundancy codes (CRC) can be used to correct multiple
- errors, such as might be found on magnetic disks.
- A single check digit for a decimal number can be used to detect single
- transposition errors or a single digit error. Since the digit is usually
- included as part of the number, it would be interesting to see if any of these
- algorithms fail if the check digit is transposed with a normal digit. When I
- used the check digit, it was set off as a separate character with a hyphen, so
- that mistake was less likely. Too bad telephone numbers don't include a check
- digit. The old rotary switches couldn't handle them, but computerized switches
- should be able to. I wonder if the saving in the amount of time spent dialing
- wrong numbers would compensate for the time spent dialing an extra digit. (KP)
-
-
- gotos
-
-
- In response to the letter from Raymond Lutz in the November CUJ I would like
- to point out the following useful feature of goto. I use gotos to consolidate
- the error-handling code near the bottom of the routine. This can be
- particularly useful when the normal path also includes most (or all) of the
- error-handling code as well (as in the case of cleanup code). See Listing 3
- for an example.
- If I had used return instead of goto I would have had three copies of the free
- code for buf1, two copies of the free code for buf2, and four different exit
- points in the routine. This way I only have one copy of the free code and one
- exit point which makes maintaining the code easier.
- In brief, I believe that gotos, just like handguns, can be misused/abused but
- that is not a reason to avoid them altogether. You just have to make sure you
- use the right tool for the right job.
- James Brown
- Orem, UT
- I agree with your sentiments regarding gotos, as I have stated before in this
- column. Your particular example of memory allocations is of particular
- interest. A major program that I am helping out with has rather large dynamic
- memory requirements. It also has the need for exiting gracefully if memory
- runs out. The compiler vendor's allocation algorithms have created some
- interesting problems in this application. Memory was getting fragmented more
- than expected, so some tracing capabilities were needed.
- Instead of using malloc and free, I made up two wrapper functions called
- memory_allocate(unsigned int size, char * name) and memory_free(void *address,
- char * name). The name parameter is a string that describes the variable that
- is being allocated or freed.
- It is used for debugging and error reporting purposes. I can't give you the
- exact code, as it is for a proprietary project, but the pseudo-code looks
- something like Figure 1. (KP)
- Figure 1 Pseudocode for memory-tracing capability used for debugging and error
- reporting
- static char *names_allocated[MAX_ALLOCATIONS];
- static unsigned int sizes_allocated[MAX_ALLOCATIONS];
- static unsigned long addresses_allocated[MAX_ALLOCATIONS];
-
- void *memory_allocate(unsigned int size, char *name)
- {
- // Call malloc with size
- // if okay, then store name, size, address in arrays
- // return address
- // else, print error message with name
- // optionally, print all names, sizes, addresses
- }
-
- void memory_free(void *address, char *name)
- {
- // If address == NULL
- // print error message with name
- // else find address in array
- // if not found
- // print error message with name
- // else
- // clear array elements
- }
-
- Listing 1 Algorithm for calculating bank routing numbers
- #define NUMVAL(x) (x - '0')
-
- #define TONUM(x) NUMVAL(x)
- #define TODIGIT(x) (x + '0')
- static char trconst[] = {'3', '7', '1', '3',
- '7', '1', '3', '7'};
- char calc_check_digit (char * trnum)
- {
- int sum, val;
- int i;
- for ( sum = i = 0 ; i < 8 ; i++)
- {
- sum += TONUM (trnum[i]) * TONUM (trconst[i]);
- }
- val = 10 - (sum % 10);
- if (val == 10)
- val = 0;
- return (TODIGIT (val));
- }
-
- /* End of File */
-
-
- Listing 2 Algorithm for calculating HL-7 standard check digits
- /*
- * This implementation of the above algorithm has some distinct
- * limitations, although I believe they are not a serious problem.
- * This is intended for processing short strings of digits (10 or so),
- * not arbitrarily long digit strings. Each of the substrings that are
- * broken out by the code below are assumed to be a value that can
- * be assigned to a long when converted to an integral type. This
- * limitation requires that the input string not be more than about 20
- * characters in length, which is more than adequate for the intended
- * application. I'm assuming that a long is at least 32 bits.
- * Further, it assumes a character set where the digits' collating
- * sequence mimics that of ASCII, so that things like:
- * x = val - '0';
- * will evaluate to an integer value of 3 in the case where val == '3'.
- */
-
- #include <stdio.h>
- #define MOD10 10
- #define MOD11 11
- /*
- * -------------- - check_digit() -------- - *
- * Calculates a check digit according to the algorithm given above.
- * NOTE
- * that there are no checks for array bounds overflow. Beware.
- */
- check_digit (string, mod)
- char * string;
- int mod;
- {
- char tmpbuf[20];
- int i, j, sum;
- unsigned long odd, even;
- /* extract the odd digits */
- j = strlen (string);
- for ( j-, i = 0 ; j >= 0 ; j -= 2, i++ )
- {
- tmpbuf[i] = string[j];
-
- }
- tmpbuf[i] = '\0';
- odd = atol (tmpbuf);
- /* extract the even digits */
- j = strlen (string);
- for ( j -= 2, i = 0 ; j >= 0 ; j -= 2, i++ )
- {
- tmpbuf[i] = string[j];
- }
- tmpbuf[i] = '\0';
- even = atol (tmpbuf);
- /* now make the composite string & sum the digits */
- sprintf (tmpbuf, "%lu%lu", even, 2L * odd);
- i = strlen (tmpbuf);
- for ( sum = j = 0 ; j < i ; j++)
- {
- sum += tmpbuf[j] - '0';
- }
- /* now do the mod10 or mod11 operation */
- i = (mod - (sum % mod));
- if (i == mod)
- i = 0;
- return (i);
- }
- #ifdef TEST
- main ()
- {
- char buf[30];
- while (1)
- {
- gets (buf);
- if (strlen (buf) == 0)
- break;
- printf ("string: %s, mod10 checkdigit: %d,"
- "mod11 checkdigit %d:n",
- buf, check_digit (buf, MOD10),
- check_digit (buf, MOD11));
- }
- }
- #endif /* TEST */
-
- /* End of File */
-
-
- Listing 3 Code using goto to consolidate error-handling code
- int SomeFunc(void)
- {
- char *buf1, *buf2, *buf3;
- int ccode;
- if ((buf1 = malloc(1024)) == NULL)
- {
- ccode = -1;
- goto End1;
- }
- if ((buf2 = malloc(1024)) == NULL)
- {
- ccode = -1;
- goto End2;
- }
-
- if ((buf3 = malloc(1024)) == NULL)
- {
- ccode = -1;
- goto End3;
- }
- // More code here
- End3:
- free(buf3);
- End2:
- free(buf2);
- End1:
- free(buf1);
- return(ccode);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Code Capsule
-
-
- A C++ Date Class, Part 2
-
-
-
-
- Chuck Allison
-
-
- Chuck Allison is a software architect for the Family History Department of
- Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake City. He
- has a B.S. and M.S. in mathematics, has been programming since 1975, and has
- been teaching and developing in C since 1984. His current interest
- object-oriented technology and education. He is a member of X3J16, the ANSI
- C++ Standards Committee. Chuck can be reached on the Internet at
- allison@decus.org, or at (801) 240-4510.
-
-
- In last month's capsule I presented the beginnings of a simple date class. In
- addition to providing a member function to calculate the interval between two
- dates, this class illustrated the following features of C++:
- inline functions
- references
- constructors
- controlled access to private data members
- In this month's installment I will add relational operators, input/output
- operations, and the capability to get the current date, while demonstrating
- these features:
- operator overloading
- streams
- friend functions
- static members
- When using dates you often need to determine if one precedes another. I will
- add the member function
- int compare(const Date& d2)
- const;
- to the date class (see Listing 1).
- Date::compare behaves like strcmp--it returns a negative integer if the
- current object (*this) precedes d2, 0 if both represent the same date, and a
- positive integer otherwise (see Listing 2 for the implementation and Listing 3
- for a sample program). For those of you familiar with qsort from the Standard
- C library, you can use Date::compare to sort dates just like you use strcmp to
- sort strings. Here is a compare function to pass to qsort:
- #include "date.h"
- int datecmp(const void *p1, const void *p2)
- {
- const Date
- *d1p = (const Date *) p1,
- *d2p = (const Date *) p2;
- return d1p->compare(*d2p);
- }
- (Next month's CODE CAPSULE will cover qsort).
-
-
- Operator Overloading
-
-
- Most of the time, it is more convenient to have relational operators, for
- example
- if (d1 < d2)
- // do something appropriate..
- Adding a less-than operator is trivial using Date::compare--just insert the
- following in-line member function into the class definition:
- int operator<(const Date& d2)
- const
- {return compare(d2) < 0);
- The compiler translates each occurrence of an expression such as
- d1 < d2
- into the function call
- d1.operator<(d2)
- Listing 4 has the class definition with all six relational operators and the
- updated sample program is in Listing 5.
- Since the functionality of Date::interval is like a subtraction (it gives us
- the difference between two dates), it would seem natural to rename it as
- Date::operator-. Before doing this, take a closer look at the semantics of the
- statement
- a = b - c;
-
- No matter the type of the variables, the following should hold:
- a is a distinct object created by the subtraction, and b - c == - (c - b)
- I will use the convention that a "positive" Date object has all positive data
- members, and conversely for a "negative" date (mixed signs are not allowed).
- In Listing 7, I have replaced Date::interval with Date::operator- (const
- Date&), which affixes the proper signs to the data members and returns a
- newly-constructed Date object. The new class definition in Listing 6 also
- contains a unary minus operator, which also has the name Date::operator-, but
- takes no arguments. The compiler will transform the statements
- d1 - d2;
- -d1;
- respectively into
- d1.operator-(d2); // Calls Date::operator-(const Date&)
- d1.operator-(); // Calls Date::operator-()
- A sample program using these new member functions is in Listing 8.
-
-
- Stream I/O
-
-
- One thing remains before I can say that a Date object has the look and feel of
- a built-in type--input/output support. C++ supplies stream objects that handle
- I/O for standard types. For example, the output from the program
- #include <iostream.h>
-
- main()
- {
- int i;
- cout << "Enter an integer: ";
- cin >> i;
- cout << "You typed " << i << endl;
- return 0;
- }
- will be something like
- Enter an integer: 5
- You typed 5
- cout is an output stream (class ostreom) supplied by the C++ streams library
- and cin is an input stream (class istream), which are associated by default
- with standard output and standard input, respectively. When the compiler sees
- the expression
- cout << "Enter an integer: "
- it replaces it with
- cout.operator<<("Enter an integer: ")
- which calls the member function ostream::operator<<(const char *). Likewise,
- the expression
- cout << i
- where i is an integer calls the function
- ostream::-operator<<(int).
- endl is a special stream directive (called a manipulator) which outputs a
- newline and flushes the output buffer. Output items can be chained together:
- cout << "You typed " << i
- because ostream::operator<< returns a reference to the stream itself. The
- above statement becomes
- (cout.operator<<("You typed ")).operator<<(i)
- To accommodate output of Date objects, then, you will need a global function
- that sends the printed representation to a given output stream, and then
- returns a reference to that stream:
- ostream& operator<<(ostream& os, const Date& d)
- {
- os << d.get_month() << '/'
- << d.get_day() << '/'
- << d.get_year();
- return os;
- }
- This of course can't be a member function, since the stream (not the object
- being output) always appears to the left of the stream insertion operator.
-
-
- Friends
-
-
- For efficiency, it is customary to give operator<< access to the private data
- members of an object. (Most implementations of a class provide the associated
- I/O operators too, so it seems safe to break the encapsulation boundary in
- this case.) To bypass the restriction on access to private members, you need
- to declare operator<< to be a friend of the Date class by adding the following
- statement to the class definition:
- friend ostream& operator<<(ostream&, const Date&);
- This declaration appears in the new class definition in Listing 9, along with
- the corresponding friend declaration for the input function, operator>>. The
- implementation for these functions is in Listing 10, and Listing 11 has a
- sample program.
-
-
-
- Static Members
-
-
- A class in C++ defines a scope. This is why the function Date::compare would
- not conflict with a global function named compare (even if the arguments and
- return value were of the same type). Now consider the array dtab[] in the
- implementation file. dtab's static storage class makes it private to the file,
- but it really belongs to the class, not to the file. If I chose to spread the
- Date member functions across multiple files, I would be forced to group those
- that needed access to dtab together in the same file.
- A better solution is to make dtab a static member of the Date class. Static
- members belong to the whole class, not to a single object. This means that
- only one copy of dtab exists, and is shared by all objects of the class.
- Making the function isleap static allows you to call it without an associated
- object, i.e., you just need to say
- isleap(y);
- instead of
- d.isleap(y);
- To make isleap available to any caller, place it in the public section of the
- class definition, and call it like this:
- Date::isleap(y);
- As a finishing touch, I will redefine the default constructor to initialize
- its object with the current date. The final class definition, implementation
- and sample program are in Listing 12 - Listing 14 respectively.
-
-
- Summary
-
-
- In these last two installments I've tried to illustrate how C++ supports data
- abstraction--the creation of user-defined types. Constructors cause objects to
- be initialized automatically when you declare them. You can protect class
- members from inadvertent access by making them private. Overloading common
- operators makes your objects look and feel like built-in types--a plus for
- readability and maintenance.
-
- Listing 1 Introduces a function to compare dates
- // date4.h
-
- class Date
- {
- int month;
- int day;
- int year;
-
- public:
- // Constructors
- Date()
- {month = day = year= 0;}
- Date(int m, int d, int y)
- {month = m; day = d; year = y;}
-
- // Accessor Functions
- int get_month() const
- {return month;}
- int get_day() const
- {return day;}
- int get_year() const
- {return year;}
-
- Date * interval(const Date&) const;
- int compare(const Date&) const;
- };
-
- // End of File
-
-
- Listing 2 Implements the interval and compare member functions
- // date4.cpp
-
- #include "date4.h"
-
- inline int isleap(int y)
- {return y%4 == 0 && y%100 != 0 y%400 == 0;}
-
- static int dtab[2][13] =
- {
-
- {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
- };
-
- Date * Date::interval(const Date& d2) const
- {
- static Date result;
- int months, days, years, prev_month;
-
- // Compute the interval - assume d1 precedes d2
- years = d2.year - year;
- months = d2.month - month;
- days = d2.day - day;
-
- // Do obvious corrections (days before months!)
- //
- // This is a loop in case the previous month is
- // February, and days < -28.
- prev_month = d2.month - 1;
- while (days < 0)
- {
- // Borrow from the previous month
- if (prev_month == 0)
- prev_month = 12;
- -months;
- days += dtab[isleap(d2.year)][prev_month-];
- }
-
- if (months < 0)
- {
- // Borrow from the previous year
- -years;
- months += 12;
- }
-
- // Prepare output
- result.month = months;
- result.day = days;
- result.year = years;
- return &result;
- }
-
- int Date::compare(const Date& d2) const
- {
- int months, days, years, order;
-
- years = year - d2.year;
- months = month - d2.month;
- days = day - d2.day;
-
- // return <0, 0, or >0, like strcmp()
- if (years == 0 && months == 0 && days == 0)
- return 0;
- else if (years == 0 && months == 0)
- return days;
- else if (years == 0)
- return months;
- else
- return years;
-
- }
- // End of File
-
-
- Listing 3 Tests the compare member function
- // tdate4.cpp
-
- #include <stdio.h>
- #include "date4.h"
-
- void compare_dates(const Date& d1, const Date& d2)
- {
- int compval = d1.compare(d2);
- char *compstr - (compval < 0) ? "precedes" :
- ((compval > 0) ? "follows" : "equals"};
-
- printf("%d/%d/%d %s %d/%d/%d\n",
- d1.get_month(),d1.get_day(0),d1.get_year(),
- compstr,
- d2.get_month(),d2.get_day(),d2.get_year());
- }
- main()
- {
- Date d1(1,1,1970);
- compare dates(d1,Date(10,1,1951));
- compare_dates{d1,Date(1,1,1970));
- compare_dates(d1,Date(12,31,1992));
- return 0;
- }
-
- /* OUTPUT
-
- 1/1/1970 follows 10/1/1951
- 1/1/1970 equals 1/1/1970
- 1/1/1970 precedes 12/31/1992
- */
-
- // End of File
-
-
- Listing 4 Defines relational operators for the Date class
- // date5.h
-
- class Date
- {
- int month;
- int day;
- int year;
-
- public:
- // Constructors
- Date()
- {month = day = year = 0;}
- Date(int m, int d, int y)
- {month = m; day = d; year = y;}
-
- // Accessor Functions
- int get_month() const
- {return month;}
-
- int get_day() const
- {return day;}
- int get_year() const
- {return year;}
-
- Date * interval(const Date&) const;
- int compare(const Date&) const;
-
- // Relational operators
- int operator<(const Date& d2) const
- {return compare(d2) < 0;}
- int operator<=(const Date& d2) const
- {return compare(d2) <= 0;}
- int operator>(const Date& d2) const
- {return compare(d2) > 0;}
- int operator>=(const Date& d2) const
- {return compare(d2) >= 0;}
- int operator!=(const Date& d2) const
- {return compare(d2) != 0;}
- int operator!=(const Date& d2) const
- {return compare(d2) !=0;}
- };
-
- // End of File
-
-
- Listing 5 Uses the Date relational operators
- // tdate5.cpp
-
- #include <stdio.h>
- #include <stdlib.h>
- #include "date5.h"
-
- void compare_dates(const Date& d1, const Date& d2)
- {
- char *compstr = (d1 < d2) ? "precedes" :
- ((d1 > d2) ? "follows" : "equals");
-
- printf("%d/%d/%d %s %d/%d/%d\n",
- d1.get_month(),d1.get_day(),d1.get_year(),
- compstr,
- d2.get_month(),d2.get_day(),d2.get_year());
- }
-
- main()
- {
- Date d1(1,1,1970);
- compare_dates(d1,Date(10,1,1951));
- compare_dates(d1,Date(1,1,1970));
- compare_dates(d1,Date(12,31,1992));
- return 0;
- }
-
- /* OUTPUT
-
- 1/1/1970 follows 10/1/1951
- 1/1/1970 equals 1/1/1970
- 1/1/1970 precedes 12/31/1992
- */
-
-
- // End of File
-
-
- Listing 6 Adds binary and unary minus to the Date class
- // date6.h
-
- class Date
- {
- int month;
- int day;
- int year;
-
- public:
- // Constructors
- Date()
- {month = day = year = 0;}
- Date(int m, int d, int y)
- {month = m; day = d; year = y;}
-
- // Accessor Functions
- int get_month() const
- {return month;}
- int get_day() const
- {return day;}
- int get_year() const
- {return year;}
-
- Date operator-(const Date& d2) const;
- Date& operator-()
- {month = -month; day = -day; year = -year;
- return *this;}
-
- int compare(const Date&) const;
-
- // Relational operators
- int operator<(const Date& d2) const
- {return compare(d2) < 0;}
- int operator<=(const Date& d2) const
- {return compare(d2) <= 0;}
- int operator>(const Date& d2) const
- {return compare(d2) > 0;}
- int operator>=(const Date& d2) const
- {return compare(d2) >= 0;}
- int operator==(const Date& d2) const
- {return compare(d2) == 0;}
- int operator!=(const Date& d2) const
- {return compare(d2) != 0;}
- };
-
- // End of File
-
-
- Listing 7 Implements the binary minus operator
- // date6.cpp
- #include <assert.h>
- #include "date6.h"
-
- inline int isleap(int y)
-
- {return y%4 == 0 && y%100 != 0 y%400 == 0;}
-
- static int dtab[2][13] =
- {
- {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
- };
-
- Date Date::operator-(const Date& d2) const
- {
- int months, days, years, prev_month, order;
- const Date * first, * last;
-
- // Must know which date is first
- if (compare(d2) <= 0)
- {
- // this <= d2
- order = -1;
- first = this;
- last = &d2;
- }
- else
- {
- order = 1;
- first = &d2;
- last = this;
- }
-
- // Compute the interval; first <= last
- years = last->year - first->year;
- months = last->month - first->month;
- days = last->day - first->day;
- assert(years >= 0 && months >= 0 && days >= 0);
-
- // Do obvious corrections (days before months!)
- // This is a loop in case the previous month is
- // February, and days < -28.
- prev_month = last->month - 1;
- while (days < 0)
- {
- // Borrow from the previous month
- if (prev_month == 0)
- prev_month = 12;
- --months;
- days += dtab[isleap(last->year)][prey_month--];
- }
-
- if {months < 0)
- {
- // Borrow from the previous year
- --years;
- months += 12;
- }
-
- // Return a date object with the interval
- if (order == -1)
- return Date(-months,-days,-years);
- else
- return Date(months, days, years);
-
- }
-
- int Date::compare(const Date& d2) const
- {
- // same as in Listing 2
- }
- // End of File
-
-
- Listing 8 Subtracts two dates
- // tdate6.cpp:
-
- #include <stdio.h>
- #include "date6.h"
-
- main()
- {
- Date d1(1,1,1970), d2(12,8,1992);
- Date result = d1 - d2;
- printf("years: %d, months: %d, days: %d\n",
- result.get_year(),
- result.get_month(),
- result.get_day());
- result = d2 - d1;
- printf("years: %d, months: %d, days: %d\n",
- result.get_year(),
- result.get_month(),
- result.get_day());
- int test = d1 - d2 == -(d2 - d1);
- printf("d1 - d2 == -(d2 - d1)? %s\n",
- test ? "yes" : "no");
- return 0;
- }
-
- /* OUTPUT
-
- years: -22, months: -11, days: -7
- years: 22, months: 11, days: 7
- d1 - d2 == -(d2 - d1)? yes
- */
-
- // End of File
-
-
- Listing 9 Adds stream I/O to the Date class
- // date7.h
-
- class ostream;
-
- class Date
- {
- int month;
- int day;
- int year;
-
- public:
- // Constructors
- Date()
- {month = day = year = 0;}
-
- Date(int m, int d, int y)
- {month = m; day = d; year = y;}
-
- // Accessor Functions
- int get_month() const
- {return month;}
- int get_day() const
- {return day;}
- int get_year() const
- {return year;}
-
- Date operator-(const Date& d2) const;
- Date& operator-()
- {month= -month; day = -day; year = -year;
- return *this;}
-
- int compare(const Date&) const;
-
- // Relational operators
- int operator<(const Date& d2) const
- {return compare(d2) < 0;}
- int operator<=(const Date& d2) const
- {return compare(d2) <= 0;)
- int operator>(const Date& d2) const
- {return compare(d2) > 0;}
- int operator>=(const Date& d2) const
- {return compare(d2) >= 0;}
- int operator==(const Date& d2) const
- {return compare(d2) == 0;}
- int operator!=(const Date& d2) const
- {return compare(d2) != 0)
-
- // I/O operators
- friend ostream& operator<<(ostream&, const Date&);
- friend istream& operator>>(istream&, Date&);
- };
-
- // End of File
-
-
- Listing 10 Implements Date stream I/O functions
- #include <iostream.h>
- #include "date7.h"
-
- ostream& operator<<(ostream& os, const Date& d)
- {
- os << d.month << '/' << d.day << '/' << d.year;
- return os;
- }
-
- istream& operator>>(istream& is, Date& d)
- {
- char slash;
- is >> d.month >> slash >> d.day >> slash >> d.year;
- return is;
- }
-
- // End of File
-
-
-
- Listing 11 Illustrates stream I/O of Date objects
- // tdate7.cpp:
-
- #include <iostream.h>
- #include "date7.h"
-
- main()
- {
- Date d1, d2;
- cout << "Enter a date: ";
- cin >> d1;
- cout << "Enter another date: ";
- cin >> d2;
- cout << "d1 - d2 = "<< d1 - d2 << endl;
- cout << "d2 - d1 = "<< d2 - d1 << endl;
- return 0;
- }
-
- /* OUTPUT
-
- Enter a date: 10/1/1951
- Enter another date: 5/1/1954
- d1 - d2 = -7/0/-2
- d2 - d1 = 7/0/2
- */
-
- // End of File
-
-
- Listing 12 Defines static members
- // date8.h
-
- // Forward declarations
- class istream;
- class ostream;
-
- class Date
- {
- int month;
- int day;
- int year;
-
- static int dtab[2][13];
-
- public:
- // Constructors
- Date(); // Get today's date (see .cpp file)
- Date(int m, int d, int y)
- {month = m; day = d; year = y;}
-
- // Accessor Functions
- int get_month() const
- {return month;}
- int get_day() const
- {return day;}
- int get_year() const
- {return year;}
-
-
- Date operator-(const Date& d2) const;
- Date& operator-()
- {month = -month; day = -day; year = -year;
- return *this;}
-
- int compare(const Date&) const;
-
- // Relational operators
- int operator<(const Date& d2) const
- {return compare{d2) < 0;}
- int operator<=(const Date& d2) const
- {return compare(d2) <= 0;}
- int operator>(const Date& d2) const
- {return compare(d2) > 0;}
- int operator>=(const Date& d2) const
- {return compare(d2) >= 0;}
- int operator==(const Date& d2) const
- {return compare(d2) == 0;}
- int operator!=(const Date& d2) const
- {return compare(d2) != 0;}
-
- // Stream I/O operators
- friend ostream& operator<<(ostream&, const Date&);
- friend istream& operator>>(istream&, Date&);
-
- static int isleap(int y)
- {return y%4 == 0 && y%100 != 0 y%400 == 0;}
- };
-
- // End of File
-
-
- Listing 13 Final implementation of the Date class
- // date8.cpp
-
- #include <iostream.h>
- #include <time.h>
- #include <assert.h>
- #include "date8.h"
-
- // Must initialize statics outside the class definition
- int Date::dtab[2][13] =
- {
- {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
- {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
- };
-
- Date Date::operator-(const Date& d2) const
- {
- int months, days, years, prev_month, order;
- const Date * first, * last;
-
- // Must know which date is first
- if (compare(d2) <= 0)
- {
- // this <= d2
- order= -1;
- first = this;
- last = &d2;
-
- }
- else
- {
- order = 1;
- first = &d2;
- last = this;
- }
-
- // Compute the interval; first <= last
- years = last->year - first->year;
- months = last->month - first->month;
- days = last->day - first->day;
- assert(years >= 0 && months >= 0 && days >= 0);
-
- // Do obvious corrections (days before months!)
- //
- // This is a loop in case the previous month is
- // February, and days < -28.
- prev_month = last->month - 1;
- while (days < 0)
- {
- // Borrow from the previous month
- if (prev_month == 0)
- prev_month = 12;
- -months;
- days += dtab[isleap(last->year)][prev_month-];
- }
-
- if (months < 0)
- {
- // Borrow from the previous year
- -years;
- months += 12;
- }
-
- // Return a date object with the interval
- if (order == 1)
- return Date(-months,-days,-years);
- else
- return Date(months,days,years);
- }
-
- int Date::compare(const Date& d2) const
- {
- int months, days, years, order;
-
- years = year - d2.year;
- months = month - d2.month;
- days = day - d2.day;
-
- // return <0, 0, or >0, like strcmp()
- if (years == 0 && months == 0 && days == 0)
- return 0;
- else if (years == 0 && months == 0)
- return days;
- else if (years == 0)
- return months;
- else
- return years;
-
- }
-
- ostream& operator<<(ostream& os, const Date& d)
- {
- os << d.month << '/' << d.day << '/' << d.year;
- return os;
- }
-
- istream& operator>>(istream& is, Date& d)
- {
- char slash;
- is >> d.month >> slash >> d.day >> slash >> d.year;
- return is;
- }
-
- Date::Date()
- (
- // Get today's date
- time_t tval = time(0);
- struct tm *tmp= localtime(&tval);
-
- month = tmp->tm_mon+1;
- day = tmp->tm_mday;
- year = tmp->tm_year + 1900;
- }
- // End of File
-
-
- Listing 14 Gets today's date
- // tdate8.cpp:
-
- #include <iostream.h>
- #include "date8.h"
-
- main()
- {
- Date today, d2;
- cout << "Today's date is "<< today << endl;
- cout << "Enter another date: ";
- cin >> d2;
- cout << "today - d2 = "<< today - d2 << endl;
- cout << "d2 - today = "<< d2 - today << endl;
- return 0;
- }
-
- /* OUTPUT
- Today's date is 12/12/1992
- Enter another date: 1/1/1970
- today - d2 = 11/11/22
- d2 - today = -11/-11/-22
- */
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- Inheritance, Part 1
-
-
-
-
- Dan Saks
-
-
- Dan Saks is the founder and principal of Saks & Associates, which offers
- consulting and training in C++ and C. He is secretary of the ANSI and ISO C++
- committees, and contributing editor for the Windows/DOS Developer's Journal.
- Dan is coauthor of C++ Programming Guidelines, and codeveloper of the Plum
- Hall Validation Suite for C++ (both with Thomas Plum). You can reach him at
- 393 Leander Dr., Springfield OH, 45504,4906, by phone at (513)324-3601, or
- electronically at dsaks@wittenberg.edu.
-
-
- Although they don't always agree on the exact meaning of the terms, most
- people who know something about object-oriented programming agree that it
- employs at least three techniques:
- Data abstraction
- Inheritance
- Polymorphism
- Thus far in my column, I've only covered C++ features that support data
- abstraction--namely, classes, access specifiers, constructors and destructors,
- and features such as references and operator overloading that help you build
- more intuitive abstractions. In this article, I'll introduce inheritance.
- Some of you may be wondering why I've waited so long to deal with inheritance.
- I think inheritance is useful, but not nearly as useful as classes and access
- specifiers. Many programmers overrate its value and consequently misuse it. I
- use inheritance sparingly in my own work.
- The other reason I've postponed discussing inheritance is that inheritance is
- a technique for defining new classes from existing classes. As such,
- understanding inheritance requires an understanding of all of those features I
- listed above. Now that I've covered them, I'll take on inheritance.
-
-
- The Basics
-
-
- Inheritance is a technique for creating a new class, called the derived class,
- from an existing class, called the base class. The derived class definition
- looks like any other base class definition, except for the presence of a base
- class specifier appearing immediately after the derived class name. For
- example, the definition
- class D : public B
- {
- //...
- };
- includes the base class specifier : public B, so that class D is derived from
- (a previously defined) base class B. The keyword public after the colon (:)
- indicates that B is a public base class of D, or conversely, that D is
- publicly-derived from B. Base classes can also be private or protected. For
- the moment, I will only consider public base classes.
- The derived class inherits nearly all the members (data and functions) of the
- base class even though the derived class definition doesn't even mention the
- inherited members. You add more members to a derived class by simply declaring
- them inside the class body. For example, if you define class B as
- class B
- {
- public:
- int f();
- void g(int);
- private:
- int i, j;
- };
- then
- class D : public B
- {
- public:
- double h(double);
- private:
- double x;
- };
- defines class D inheriting the members of B aing a public member function
- h(double) and a private data member x. The inherited public members--functions
- f() and g(int)--become public members of D. The inherited private
- members--data members i and j--become part of class D, but remain private to
- the B part of D. A D member function, like h(double), can only access i and j
- via public members (and friends, if any) of B. You may be surprised by this
- restriction, but if derived classes could directly access private base class
- members, then anyone could violate the encapsulation of a class by simply
- deriving another class from it.
- A derived class can be a base class for further derived classes. For example,
- class F : public D
- {
- public:
- char *p;
- };
- derives class F from D, adding a public data member p to F.
-
-
-
- Is-A Relationships
-
-
- Inheritance provides a simple, explicit notation for creating specialized
- versions of broader classes. Inheritance defines an Is-A relationship (some
- people prefer Is-A-Kind-Of for added clarity): an object of a derived class is
- an object of the base class. A derived class object has everything that a base
- class object has, and usually more. It never has less.
- For example, Stroustrup (1991) introduces inheritance by sketching an example
- dealing with employees and their managers. Class employee defines the
- representation for each employee, with data such as name, age, and salary.
- Since a manager is an employee (with additional powers and responsibilities),
- Stroustrup derives class manager from class employee:
- class manager : public employee
- {
- // additional members that
- // distinguish managers from
- // other employees
- };
- Since a manager is an employee, any function f with a formal parameter of type
- employee or employee & will accept an actual argument of type manager. For
- example, given
- void f(employee &e);
- manager m;
- the call f(m) binds the formal parameter e to actual argument m. Inside f, e
- appears to refer to an employee, which it does. It just happens that in this
- particular call, e refers to an employee that is also a manager. But f can't
- tell that e actually refers to a manager; it can only access the employee part
- of the object.
- As a general rule, whenever class B is a public base of class D, you can:
- 1. Convert a D * to a B * (a pointer to a derived object to a pointer to a
- base object)
- 2. Convert a D & to a B & (a reference to a derived object to a reference to a
- base object)
- 3. Initialize a B & to refer to a D
- These conversions are transitive. That is, if class D is a public base of
- class F, then you can convert an F * to either a D *or a B *, or an F & to
- either a D & or a B &. For example, given
- class B {...};
- class D : public B {...};
- class F : public D {...};
- B b;
- D d;
- F f;
- then all of the following operations are valid:
- B *pb = &d; // ok, a D* is a B*
- D *pd = &f; // ok, an F* is a D*
- pb = &f; // ok, an F* is a D*,
- // which is a B*
- D &rd = f; // ok, an F is a D
- B &rb1 = rd; // ok, a D is a B
- B &rb2 = f; // ok, an F is a D,
- // which is a B
- Although an object of a derived class is an object of its base class, the
- opposite is not true. Thus, you cannot convert a pointer (or reference) to a
- base object to a pointer (or reference) to a derived object, unless you use a
- cast. For example, given the immediately preceding declarations, then
- pd = pb; // error, a B* is
- // not a D*
- pd = (D *)pb; // ok, but suspect
- F &f = b; // error, a B is
- // not an F
- In general, C++ also lets you initialize a base class object with a derived
- class object, as in
- D d;
- //...
- B b(d);
- or simply assign a derived class object to a base class object, as in
- b = (d);
- (I detailed the differences between initialization and assignment in
- "Initialization vs. Assignment," CUJ, September 1992.) Both the initialization
- and the assignment copy only the inherited B members from d to b.
- The ARM (Ellis & Stroustrup 1990) doesn't specifically permit conversion from
- a derived class object to a base class object, but it falls out from the
- reference conversions. When you write
- B b(d);
- C++ translates this to a call to B's copy constructor, typically declared as
- B::B(const B &br);
- The constructor call binds formal parameter br (a reference to a base object)
- to actual argument d (a derived class object), as permitted by in rule 3
- previously mentioned. Similarly, when you write the assignment
- b = d;
- C++ translates this to a call to B's assignment operator:
- B &B::operator=(const B &br);
- Again, calling this assignment operator binds br to d, employing the same
- reference conversion.
-
-
-
- How It Works
-
-
- Some insight into how C++ implements inheritance may help you understand and
- remember the conversion rules a bit better. Although C++ imposes some
- restrictions on the storage layout for derived class objects, it doesn't
- require that an implementation use any particular layout strategy.
- Figure 1 shows a simple base class B and a typical storage layout for an
- object of class B. Notice that only the data fields occupy storage in the
- object. C++ resolves B's member function calls at translation time, so it need
- not store any information about the member functions in the object.
- Figure 2 shows a typical layout for a class D derived from class B in Figure
- 1. The B sub-object (the B part) occupies the beginning (the lowest addresses)
- of a D object. Thus, converting a pointer (or reference) to a D into a pointer
- (or reference) to a B doesn't require any generated code; it's strictly a
- compile-time transformation.
- Calling a function,
- void f(B &br);
- with an actual argument d of type D binds br to the B part of d. Typical
- generated code simply assigns the address of d to the pointer that implements
- br. No pointer arithmetic or indirection is needed.
- The body of the function can't tell whether br is bound to a B or to an object
- of a class derived from B. But, since all classes derived from B have at least
- everything that B has, f can safely access all members of br. That there might
- be more members beyond the fringes of B is not a concern.
- This model also illustrates why you generally can't convert in the opposite
- direction, that is, from pointer (or reference) to base to pointer (or
- reference) to derived. For example, calling a function,
- void g(D &dr);
- with an actual argument b of type B attempts to bind dr to b. But, referring
- to Figure 1 and Figure 2, a B object doesn't have an x member, so accessing
- dr.x inside g would reach beyond the end of b into uncharted territory. Thus,
- converting from base to derived violates the type safety rules in C++. You
- can't make the call without casting the actual argument b to type D.
-
-
- Overriding
-
-
- A derived class can redefine a function inherited from a base case, as shown
- in Listing 1. Here, the base class B defines two functions, f and g. The
- derived class D inherits both functions from B, but then replaces g with a
- definition of its own. This replacement is called overriding.
- Figure 3 shows the output from the program in Listing 1. Calling d.f calls the
- f inherited from B. The translator need not generate any new code for D's f;
- it can simply invoke the code already generated for B's f, passing the address
- of d's B sub-object as the value of this. However, since D's g overrides the g
- inherited from B, calling d.g calls a different function than calling b.g.
- When a derived class overrides an inherited public function, that function is
- hidden, but not completely inaccessible. The derived class can still access
- the hidden member by using the scope resolution operator, ::, and explicitly
- qualifying the member name with the base class name, as shown in Listing 2. In
- this example, derived class D overrides inherited functions and g and h. The
- call to B::g inside D's g calls B's g, as shown in the program output in
- Figure 4. Without the qualifier B::, a call to g inside D's g would be a
- recursive call to D's g.
- The body of D::h in Listing 2 shows another technique for calling an
- overridden member function using a cast. Remember that, inside the body of a
- member function, a call to a member function like h is actually a call to
- this->h. In a D member function, the type of is D const *. By casting this to
- B * (which is a valid conversion from pointer to derived to pointer to base)
- inside D::h, I forced the translator to look for h in the scope of B, and
- bypass looking in the scope of B. It works, but I recommend avoiding the cast
- and using the scope resolution operator as I did in D::g.
- A derived class can override inherited data members as well as function
- members. For example, consider
- class B
- {
- public:
- int n;
- // ...
- };
-
- class D : public B
- {
- public:
- long n;
- void f();
- // ...
- };
- An object d of class D has storage for both an int n and a long n. Inside
- D::f, an unqualified reference to n refers to D::n (the long); B::n refers to
- the inherited int n.
- A derived class cannot delete inherited members.
-
-
- An Example
-
-
- In my last two articles I described and implemented several versions of class
- float_array, an array of float for which you can set the number of elements at
- runtime. (See "Dynamic Arrays," CUJ, November 1992, and "The Function
- operator[]", CUJ, January 1993.) Listing 3 shows the class definition for the
- climactic version, which grows automatically to keep subscript references in
- bounds.
- float_arrays, like all other arrays in C and C++, have the lowest subscript
- fixed at zero. This is less than ideal for some applications. Programming
- languages like Pascal, and its descendents Modula-2 and Ada, let you declare
- arrays with low bounds other than zero.
- In C++, you can fill the need by creating a class that I'll call float_vector,
- for which you specify not the number of elements but the low and high bounds
- of the subscript. For example,
- float_vector fv(1, 10);
- declares fv as an array of float whose subscript range is 1 to 10, inclusive.
- float_array already embodies much of the functionality for float_vector, so
- let's consider implementing float_vector by deriving it from float_array.
- Listing 4 shows the class definition for float_vector. float_vector adds a new
- private data member, _low, that records the vector's low bound. A float_vector
- need not store the high bound as a data member because it can determine the
- high bound from the low bound and length (inherited from float_array).
- float_vector defines a constructor, float_vector(int lo, int hi), that builds
- a vector with subscripts from lo to hi, inclusive. It also adds two query
- functions, low and high, that return the values of the current low and high
- subscripts, respectively.
- The float_vector constructor is extremely terse, so I defined it as an inline
- function in the header, as
- inline float_vector::float_vector(int lo, int hi)
- : _low(lo), float_array(hi - lo + 1)
- { }
- The constructor's member-initializers do all the work. The first initializer,
- _low(lo), fills in the private data member _low. The second initializer,
- float_array(hi - lo + 1), invokes the base class constructor to initialize the
- inherited data members array and len.hi - lo + 1 is the number of elements in
- a float_vector whose subscript range is from lo to hi.
- Remember, inherited private members are not directly accessible in the derived
- class. The derived class must use the public interface provided by the base
- class, which in this case, is a public constructor. Unlike member initializers
- that I've used in the past, the leading identifier in the initializer
- float_array(hi - lo + 1) is the name of a type, not the name of a data member.
- There's no named member for the float_array sub-object in the float_vector, so
- you must refer to it using its type name.
- float_vector overrides both inherited operator[] functions with new
- implementations that work when the low subscript bound is nonzero. Notice that
- the formal parameters for both the const and non-const
- float_vector::operator[] are int, and not size_t, as they are in class
- float_array, because a float_vector's low bound may be negative.
-
- Listing 5 contains the non-inline float_vector member functions, namely, both
- forms of operator[]. The function bodies are identical; they both rely on the
- inherited (overridden) versions of operator[] to do most of the work. The
- expression i - low shifts the subscript i into a subscript range whose low
- bound is zero. The statement
- return float_array::operator[] (i - low);
- calls the inherited operator[] to select the desired element from the
- inherited float_array sub-object, and extend the array if necessary.
- Listing 6 shows a test program for float_vectors. Notice that the display
- function (brought over from my previous two articles) still accepts a second
- argument of type const float_array &. I did not change it to const
- float_vector &. I also left a float_array in the test program to show that the
- display function accepts arguments of both the base and derived types.
- A sample output from the program appears in Figure 5. The abnormal termination
- is intentional. I planted a subscripting error just to show that the
- assertions work.
-
-
- Food for Thought
-
-
- I was forced to make a simplifying assumption in my float_vector class,
- namely, that you can only extend the high bound of a vector. The low bound
- must remain fixed. That's why both float_vector::operator[] functions include
- the assertion
- assert(i >= low());
- The problem is that my design for float_arrays did not anticipate that I might
- want to extend the arrays in both directions. I'm not sure that I want to
- extend the low bound, but given this design, it's out of the question.
- Inheritance is often advertised as a wonderful technique for reusing existing
- code. But the reuse doesn't always work out the way you want. The reality is
- that you must decide for each class that you build whether you intend to use
- it as a base class for further derivation, and if so, how you or others might
- wish to use it.
- My design also raises another question. I said earlier that public inheritance
- defines Is-A relationships. You might reasonably ask if float_vector really is
- a float_array, or if I just used a convenient implementation trick.
- I'll ponder this and other questions in the next part of this series.
- References
- Ellis, Margaret A. and Bjarne Stroustrup. 1990. The Annotated C++ Reference
- Manual. Reading, MA: Addison-Wesley.
- Stroustrup, Bjarne. 1991. The C++ Programming Language, 2nd. ed. Reading, MA:
- Addison-Wesley.
- Figure 1 Base class B and typical storage layout for derived class objects
- Figure 2 Typical layout for a class D drived from B
- Figure 3 Output from Listing 1
- B::f()
- B::g()
- B::f()
- D::g()
- Figure 4 Output from Listing 2
- B::f()
- B::g()
- B::h()
- B::f()
- B::g()
- ... called from D::g()
- B::h()
- ... called from D::h()
- Figure 5 Sample output from the program in Listing 6
- low? 3
- high? 6
- fa = { 3 4 5 6 }
- fb = { 3 4 5 6 }
- fb = { 9 4 5 6 }
- fb = { 9 16 5 6 }
- fb = { 9 16 25 6 }
- fb = { 9 16 25 36 }
- fb = { 9 16 25 36 49 }
- fb = { 9 16 25 36 49 64 }
- fb = { 9 16 25 36 49 64 81 }
- fb = { 9 16 25 36 49 64 81 100 }
- fb.low() = 3
- fb.high() = 10
- fc = { 3 4 5 6 }
- fc = { 3 4 5 123 }
- fc = Assertion failed: i >= low, file fv1.cpp, line 19
- Abnormal program termination
-
- Listing 1 Overriding an inherited function
- #include <iostream.h>
-
- class B
-
- {
- public:
- void f();
- void g();
- };
-
- void B::f() { cout << "B::f()\n"; }
-
- void B::g() { cout << "B::g()\n"; }
-
- class D : public B
- {
- public:
- void g();
- };
-
- void D::g() { cout << "D::g()\n"; }
-
- int main()
- {
- B b;
- b.f();
- b.g();
- D d;
- d.f();
- d.g();
- return 0;
- }
-
- // End of File
-
-
- Listing 2 Calling an overridden inherited function
- #include <iostream.h>
-
- class B
- {
- public:
- void f();
- void g();
- void h();
- };
-
- void B::f() { cout << "B::f()\n"; }
-
- void B::g() { cout << "B::g()\n"; }
-
- void B::h() { cout << "B::h()\n"; }
-
- class D : public B
- {
- public:
- void g();
- void h();
- };
-
- void D::g()
- {
- B::g();
-
- cout << "... called from D::g()\n";
- }
-
- void D::h()
- {
- ((B *)(this))->h();
- cout << "... called from D::h()\n";
- }
-
- int main()
- {
- B b;
- b.f();
- b.g();
- b.h();
- D d;
- d.f();
- d.g();
- d.h();
- return 0;
- }
-
- // End of File
-
-
- Listing 3 Class definition for float_array
- // fa1.h - a dynamic array of float using a subscripting
- // object
-
- #include <iostream.h>
-
- class fa_index
- {
- friend class float_array;
- public:
- fa_index &operator=(float f);
- operator float();
- private:
- fa_index(float_array *f, size_t i);
- float_array *fa;
- size_t ix;
- };
-
- class float_array
- {
- friend class fa_index;
- public:
- float_array(size_t n = 0);
- float_array(const float_array &fa);
- -float_array();
- float_array &operator=(const float_array &fa);
- float operator[](size_t i) const;
- fa_index operator[](size_t i);
- inline size_t length() const;
- private:
- void extend(size_t i);
- float *array;
- size_t len;
- };
-
-
- ostream &operator<<(ostream &os, const float_array &fa);
-
- inline size_t float_array::length() const
- {
- return len;
- }
-
- // End of File
-
-
- Listing 4 Class definition for float_vector
- // fv1.h - a dynamic vector of float (with a possibly
- // non-zero low-bound) using a subscripting object
-
- #include <iostream.h>
- #include "fa1.h"
-
- class float_vector : public float_array
- {
- public:
- float_vector(int lo = 0, int hi = 0);
- float operator[](int i) const;
- fa_index operator[](int i);
- int low() const;
- int high() const;
- private:
- int _low;
- };
- inline float_vector::float_vector(int lo, int hi)
- : low(lo), float_array(hi - lo + 1)
- { }
-
- inline float_vector::low() const
- {
- return_low;
- }
-
- inline float_vector::high() const
- {
- return_low + length() - 1;
- }
-
- // End of File
-
-
- Listing 5 Non-inline float_vector member functions.
- // fv1.cp- a dynamic vector of float (with a possibly
- // non-zero low-bound) using a subscripting object
-
- #include "fv1.h"
- #include <assert. h>
-
- float float_vector::operator[](int i) const
- {
- assert(i >= low());
- return float_array::operator[](i - low());
- }
-
-
- fa_index float_vector::operator[] (int i)
- {
- assert(i >= low());
- return float_array::operator[](i - low());
- }
-
- // End of File
-
-
- Listing 6 Test program for float_vector
- // tv1.cpp - a test program for float_vectors and
- // float_arrays
-
- #include <iostream.h>
- #include "fv1.h"
-
- void display(const char *s, const float_array &fa)
- {
- cout << s << " = " << fa << endl;
- }
- int main()
- {
- int i, low, high;
- cout << "low? ";
- cin >> low;
- cout << "high? ";
- cin >> high;
-
- float_vector fa(low, high);
- for (i = fa.low(); i <= fa.high(); ++i)
- fa[i] = i;
- display("fa", fa);
- float_vector fb = fa;
- display("fb", fb);
- for (i = low; i < low + 2 * fa.length(); ++i)
- {
- fb[i] = i * i;
- display("fb", fb);
- }
- cout << "fb.low() = " << fb.low() << '\n';
- cout << "fb.high() = " << fb.high() << '\n';
- float_array fc = fa;
- display("fc", fc);
- i = fc.length();
- fc[i - 1] = 123;
- display("fc", fc);
- cout << "fa[" << low - 1 << "] = ";
- cout << fa[low - 1] << '\n';
- return 0;
- }
-
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- C-Clearly
-
-
- William Smith
-
-
- William Smith is the engineering manager at Montana Software, a sorftware
- development company specializing in custom applications for MS-DOS and
- Windows. You may contact him by mail at P.O. Box 663, Bozeman, MT 59771-0663.
-
-
- C is a free format language. It opens the door to an infinite variation of
- appearances based on the programmers personal preference. Nearly every
- programmer has opinions, sometimes very strong, of how C code should be
- formatted. Arguments over curly brace placement, indentation, white space, and
- comment placement abound. Over time, programmers first become comfortable with
- a certain style, then attached and eventually religiously committed.
- Style, especially the appearance aspect of style is a matter of personal
- taste. You can radically change the appearance of C without changing what the
- code does. Depending on the placement of indentation and white space you can
- make the code very readable or nearly unreadable. Obviously the goal is to use
- appearance to enhance the visibility of programming structures such as
- functions, blocks, loops, etc. Unfortunately what is readable and visible to
- one programmer is obfuscated to another.
- C-Clearly by V Communications is a C & C++ source code formatting utility that
- claims to be able to give you complete control over code appearance. It is a
- "you can have it any way you like it" utility. C-Clearly attempts to be the
- ultimate code beautifier/formatter.
- If true, it ends the argument on what appearance is appropriate. Every
- programmer can have it his or her own way. In a work group situation,
- programmers can use the style they are comfortable with and later reformat
- their code to the standards specified by the group. Alternatively, a
- programmer can take someone else's code and reformat it into the style that he
- or she is most comfortable with and efficient at reading. Sounds good in
- theory, but there are limitations.
- First of all, style is not just where the curly braces go. Style consists of
- both structure and appearance. Structure involves programming constructs,
- variable naming, and the type of techniques to execute a certain process.
- C-Clearly cannot do anything about this aspect of coding style. What it can do
- is alter the appearance of code. And even this has its limits.
-
-
- What C-Clearly Does
-
-
- There are infinite combinations of appearance options for C and C++ code.
- Instead of a long list of command-line options or an input screen containing a
- myriad of check boxes and radio buttons, C-Clearly's user interface for
- selecting a specific combination of appearance options is a template file. The
- template file is a recipe of pseudo-C commands that set the formatting
- definitions.
- C-Clearly comes with six template files, three each for C and C++. Listing 1,
- knr. ccl, is the template file for K&R-style C code. You can create your own
- template file by starting with one of the delivered template files and editing
- it. You have to be careful when editing a template file. You can only modify
- the white space in the template file.
- In addition to the this restriction, the fact that some constructs are defined
- in more than one place creates another restriction. The constructs that are
- defined in more than one place must be consistent or C-Clearly will warn you
- when you have conflicting appearance constraints.
- C-Clearly supports both a command-line and an interactive user interface. The
- command-line interface is a subset of the interactive user interface. You
- cannot specify any style formatting options on the command line. You must use
- the interactive user interface to specify formatting options and then save
- them as the default.
- C-Clearly stores the default settings directly in the executable file. This
- means it writes to and modifies the executable file. Most Anti-Virus software
- will detect this. So you will have to tell your Anti-Virus program to ignore
- this situation.
- Besides the template file there are four categories of additional appearance
- choices. The first category involves margins, tabs, and nesting lines. Nesting
- lines involve adding lines to show the connection between the beginning and
- end of logical blocks.
- The second category involves comments. You can select how to align and group
- comments. C-Clearly will make attempts to beautify comments, but I have never
- found what it does to my liking. I usually turn most of the comment formatting
- options off.
- The third category applies to white space. You can tell C-Clearly to retrain
- existing blank lines depending upon their placement.
- The fourth category is for hardcopy output only. You can send the output
- directly to a printer or to a file that is ready to print. When generating
- hardcopy output, C-Clearly can add line numbers, printer escape codes, and
- additional information such as headers, footers, and code metrics information.
- By inserting printer escape codes around different code constructs you can
- select printing styles or fonts. For example, you can print all comments in
- italics. There are no printer drivers so you will have to determine the escape
- codes for the printer you are using.
- The combination of code templates, an interactive user interface for
- additional style selection, and a terse commandline interface works well for
- C-Clearly. I find it quite easy to use.
- The user manual is perfect bound and about 55 pages in length. Chapter 2
- covers template files. It is only a couple of pages in length, but if you are
- going to create your own template file it is must reading.
- This all sounds good until you have to start formatting some code. Even though
- C-Clearly has a lot going for it, it has some severe limitations. As soon as
- you are faced with the chore of formatting files, you will most certainly bump
- up against C-Clearly's unfortunate limitations.
-
-
- What C-Clearly Doesn't Do
-
-
- C-Clearly requires syntactically correct source code. This in itself is not
- that big a drawback. If your code complies it is probably syntactically
- correct. Well, not always, according to C-Clearly. C-Clearly does not like
- some C constructs that are rigorously correct in syntax and compile just fine.
- The ability of C-Clearly to have so much control and flexibility in formatting
- code means that it has to parse and understand code nearly to the same level
- as a compiler. Unfortunately, it chokes on many complex but entirely valid C
- constructs. When C-Clearly finds a syntax it does not like, it quits and makes
- no further attempt to format the file.
- This problem is a serious flaw in C-Clearly. It truly cripples an otherwise
- fine product. From experience, an important area of weakness that I have
- identified is in handling types and macros that deal with types.
- C-Clearly has trouble dealing with typedefs, structures, defines, and
- variables that happen to have the same name. This may not be a good idea to
- have the same name for all these different things, but it is valid. C-Clearly
- also has problems with macros that expand to structure member references. For
- example macros similar to the following have caused me trouble with C-Clearly.
- #define MEMBER StructInstance->Member
- The token pasting preprocessor operator, ##, can also cause C-Clearly to fail
- on perfectly correct code.
- Sometimes you can coax C-Clearly to still format a file by turning off
- formatting or syntax checking for the sections it can not handle. This has the
- drawback of requiring you to insert C-Clearly instructions in the form of
- specialized comments into your code. It may still not get some files to work.
- C-Clearly ignores the code that you have flagged and then expects the
- unflagged code to still be syntactically correct.
- Since C-Clearly has the bad habit of not liking certain code structures and
- flagging them as syntax errors, I have found it useful to monitor the
- program's return code from within a batch file. Although not documented in the
- users manual, C-Clearly returns 1 if it fails. You can access this in a batch
- file as the MS-DOS errorlevel. I use this technique to conditionally execute
- further processing on the source file after C-Clearly.
- I have not had success getting C-Clearly to consistently output tabs for
- indentation. Consequently, I process the file after C-Clearly to convert
- groups of leading spaces to tabs. The following is an MS-DOS batch file
- listing.
- CCL %1 OUTPUT.CCL
- IF ERRORLEVEL 1 GOTO :EXITERROR
- ENTAB OUTPUT.OUT 8
- COPY OUTPUT.OUT %1
- :EXITERROR
- ERASE OUTPUT.CCL
- I also add the comment, /* End of File */ to the end of every source file.
- Although C-Clearly can add comments to closing braces, it does not add
- comments (footers) to file output. You can add footers to hard-copy
- (printer-ready) output.
- I have an additional major complaint. Even with all of its flexibility, I
- cannot get C-Clearly to format code exactly the way I want. The problem is
- with long expressions that must be continued on multiple lines. With very long
- source lines, I like to find a convenient place to make a new line and
- continue the expression on the next line indented two levels from the indent
- level of the expression. Try as I might, I have not been able to get C-Clearly
- to do this exactly the way I want. I cannot even get it to leave continued
- lines alone. Since C-Clearly comes so close to giving you what you want and
- your expectations are high, this is especially annoying.
- The other complaints stem from the limitations of memory imposed by MS-DOS. I
- have started to run out of memory with long listings, especially code for
- Microsoft Windows that include WINDOWS.H. Since WINDOWS.H is more than 5,000
- lines in length, this is not surprising. As file length goes up, C-Clearly
- gets significantly slower. A protected-mode version may remedy these problems.
-
-
- User Support -- Upgrade Policy
-
-
-
- I started using C-Clearly when it first came out about three years ago. I
- expected a lot from the product. I was not shy in calling V Communications and
- expressing my desires for C-Clearly to do something that it presently could
- not do or pointing out limitations and bugs in the product. V Communications
- responded with a closely-spaced series of upgrades. Many of these upgrades
- came free of charge.
- This experience and the ease of access to the technical people involved with
- the product impressed me. V Communications seemed committed to a quality
- product and committed to user support.
- C-Clearly has evolved to a state that works for me. I have found work arounds
- for most of the limitations and rarely contact the company anymore. The last
- upgrade cost me $45.00.
-
-
- Conclusions
-
-
- C-Clearly ambitiously sets out to be the ultimate C and C++ source code
- reformatting utility. It comes close. It fails in two critical areas. The
- first is that I cannot get C-Clearly to format code exactly the way I want it.
- The other serious flaw is that it will not format some perfectly good code. It
- stops when it thinks it has found a syntax error or runs low on memory.
- If you are willing to live with these limitations, C-Clearly is a powerful
- source code maintenance tool. I have found it useful and use it frequently. I
- have also found it frustrating and nearly always do additional processing to
- files after C-Clearly is done with them.
- When I have to read someone else's code that is in a style I am not
- comfortable with, C-Clearly has come to my rescue many times. C-Clearly is not
- a replacement for good coding style rules such as choice of variable names,
- consistency, and commenting, but it sure helps put to rest some of the code
- appearance arguments.
-
-
- Note from the editor:
-
-
- V Communications has released version 2.1 of C-Clearly which addresses some of
- the limitations outlined in this review. For specific differences please
- contact V Communications.
- C-Clearly, Version 2.0
- V Communications, Inc.
- 4320 Stevens Creek Blvd., Suite 275
- San Jose, CA 95129
- Phone: 800-648-8266
- Price: $129.95
- Hardware Requirements: IBM PC Compatible, MS-DOS, and 512K RAM
-
- Listing 1 C-Clearly template file for K&R code for C code
- #include "lib.h"
- #define Macro( Param1, Param2 ) Param1 + Param2
-
- static int i, (far *pfi)();
-
- int v = 0;
- int ArrayName[XSize][YSize] = {{Init1, Init2}, {Init3}};
-
- enum EnumName {Enum=1, Enum2};
-
- struct StructName {
- int Field, Field;
- int Field, Width;
- };
-
- void ANSIProtoFunc1 ();
- void ANSIProtoFunc2 ();
-
- static int ANSIFunction (char Param[], int *PtrParam)
- {
- long int Variable;
- int Variable;
-
- if (Expression) {
- Statement;
- }
- else if (Expression) {
- Statement;
- }
- else {
- Statement;
-
- }
- if (Expression) {
- int LocalVariable;
- int LocalVariable;
-
- Statement;
- Statement;
- }
- else if (Expression) {
- int LocalVariable;
- int LocalVariable;
-
- Statement;
- Statement;
- }
- else {
- int LocalVariable;
- int LocalVariable;
-
- Statement;
- Statement;
- }
- Array [Index].Field = (int *)PostOp++ * ++PreOp;
- Struct->Field = -UnaryOp * sizeof Variable;
- if ((Value = FunctionName ()) == sizeof (Type))
- Statement;
- else if (Expression)
- Statement;
- else
- Statement;
- {
- Statement;
- }
- {
- int LocalVariable;
- int LocalVariable;
-
- Statement;
- Statement;
- }
- while (Expression)
- Statement;
- while (Expression) {
- Statement;
- }
- while (Expression) {
- int LocalVariable;
- int LocalVariable;
-
- Statement;
- Statement;
- }
- switch (Expression) {
- case Value: {
- Statement;
- break;
- }
- default:
- Statement;
-
- break;
- }
- return Expression;
- }
-
- void KnRFunction (Param1, Param2)
- int Param1;
- int Param2;
- {
- FunctionCall (Param, TestExpr ? ThenExpr : ElseExpr);
- goto Label;
- for (Expression; Expression; Expression, CommaExpression)
- Statement;
- for (Expression; Expression; Expression, CommaExpression) {
- Statement;
- }
- for (Expression; Expression; Expression, CommaExpression) {
- int LocalVariable;
- int LocalVariable;
-
- Statement;
- Statement;
- }
- do
- Statement;
- while (Expression);
- do {
- Statement;
- } while (Expression);
- do {
- int LocalVariable;
- int LocalVariable;
-
- Statement;
- Statement;
- } while (Expression);
- return (Expression);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The Art of Programming Embedded Systems
-
-
- Mark Gingrich
-
-
- Mark Gingrich has been employed in the medical device industry for the past
- nine years. Presently he serves as a software engineer for Baxter Healthcare
- Corporation's Novacor division. He can be reached at 355 Estabrook St., Apt.
- 403, San Leandro, CA 94577.
-
-
- The series of books by Donald Knuth called The Art of Computer Programming may
- be the archetype of its genre. So I approach recent epics leading with "The
- Art of" title with elevated expectation. The Art of Programming Embedded
- Systems is among the latest (though unrelated to Knuth's trilogy). Its author,
- Jack Ganssle, checks in with good credentials: a contributing editor for
- Embedded Systems Programming magazine, a designer and purveyor of in-circuit
- emulators, and a veteran practitioner of said "art."
- But why a book on coding embedded systems? Perhaps because the topic is so
- woefully treated in the engineering/computer science curricula. More often
- it's a trade learned on the job--the hard way--at no small expense to our
- employers. And many of us drift into this sea having cast off on the purely
- hardware or purely software oceans. The luckiest benefit from a mentor helping
- to steer around obstacles: how to coerce the compiler to accept writeable
- variables distinct from RAM; how to debug optimized code sans print
- statements; how to configure the emulator to trap bugs which occur only during
- neap tides on Groundhog Day; how to do this; how not to do that. Don't have a
- mentor as such? Well, then, this book may be a reasonable alternative.
- Only don't expect a tutorial from square zero. Gannsle's approach is more
- casual--rather like talking shop with colleagues. He assumes that you've
- already served your software apprenticeship; now your goal is to fill those
- gaps of wisdom which have postponed your transition to true embedded systems
- programming enlightenment. The parallel path to this state of being entails
- time-consuming and costly mistakes (the euphemism is called "experience").
- And experience is seldom acquired in each and every aspect of embedded design.
- For example, chief among my own gaps of wisdom is one in memory management
- techniques, having never employed bank switching on a project. Ganssle comes
- through in chapter six with a clear depiction of the camouflaged snake pits
- lurking in this area. Reading this chapter made it plainly apparent that I
- would have pathetically underestimated the time required to implement a
- bank-switching scheme.
- Likewise, a good introduction to real-time operating systems is found in
- chapter nine. Although not the be-all, end-all word on the subject, it's an
- appropriate diving-in point for the novice before swimming through the
- voluminous sales literature and spec sheets from the umpteen RTOS-to-go
- vendors. Of particular value is the small--but functional--real-time executive
- supplied in source listing form.
- Ever need a lone transcendental function in your system? Instead of calling
- the compiler's bloated, glacier-speed floating-point math library routine
- (which returns a result with three digits of precision more than you require),
- why not roll your own? Ganssle shows how--illustrated with C--in chapter
- seven.
- In addition, there are chapters on interrupt management; on signal smoothing
- and curve fitting (especially intriguing is the Savitsky and Golay technique);
- on software design which allows for civilized debugging; on designing to
- permit simplified production test--always guaranteed to endear you with the
- harried, under-appreciated manufacturing folk.
- And interspersed with the lucid, here's-the-way-it-is writing style are
- snippets of reality--flashbacks from Ganssle's eventful past:
- "It always seems that just before a demo everything falls apart. After a late
- night of removing the final bugs from microcontroller-based design, I
- unplugged the emulator and installed the computer chip. On power up the unit
- did nothing -- it was completely dead. Fortunately the code had a simple test
- routine that blinked an LED before any other initialization took place. Since
- the LED didn't blink, I knew immediately that the code was not starting and
- indeed found that a floating DMA request line was keeping the processor idle.
- The emulator's slightly different DC characteristics masked the problem during
- weeks of code development."
- Such anecdotal digressions in the prose are welcome. They add realism. And
- they underscore that the proffered advice is not rarefied academic theory;
- these are eyewitness war stories from the front.
- Occasionally, too, Ganssle opines on the softer issues of software
- development: programming style and professional improvement. And he confronts
- business issues so often avoided like the plague by the technical staff. This
- holistic approach is commendable. The still-too-pervasive image of
- "proglodytes" (wearing pocket protectors, of course) hacking away in the back
- room, oblivious to the rest of the world, has been a hindrance to our
- collective professional advancement. There is a bottom line, and Ganssle steps
- back to point out our role and responsibilities within the big picture.
- Reading widely is among our responsibilities, we're admonished. So Gannsle
- supplies an eclectic bibliography: from techy Intel application notes to Alvin
- Toffler's Powershift. (Though I would have preferred a more exhaustive
- reference section--pointers to the richest embedded systems lore. Indigenous
- software types, for instance, may need to "speak" electronic more
- proficiently; another "art of" book, The Art of Electronics, by Horowitz and
- Hill, is an appropriate text. Those of the hardware stripe would benefit from,
- say, Kernighan and Plauger's The Elements of Programming Style.) An appendix
- with recommended periodicals for the cognizant embedded programmer is also
- offered. (The C Users Journal makes the list; but somehow Dr. Dobb's Journal
- is omitted, a conspicuous oversight considering it is cited elsewhere in the
- book.)
- Be advised, however, that the "art" presented is not the state of the art.
- Embedded systems are described as they've existed over the past few years,
- with 4-, 8-, and 16-bit processors. There are no visits from the ghost of
- Embedded-Programming-Yet-To-Be. One must look elsewhere for coverage of fuzzy
- logic, neural nets, and DSP chips as embedded controllers.
- Mind you, I heartily recommend this book, but there are a few too many warts,
- most of which should have been removed with scrupulous copy editing. On page
- 152 the definitions of accuracy and precision are confused, as is the
- described behavior of the sine function, and the constant pi/2 is termed a
- "round" number. (Ironically, these blunders occur on a page with the subhead
- "Errors.") Elsewhere, the repeated misspelling of "kernel," the missing
- arrowhead in the state diagram in Figure 3.2, and the interchanged x-y
- coordinates in Figure 7.4 are annoying flaws. The state diagram in Figure 9.1
- is simple, but it could have been drawn without the confusion-adding crossed
- lines.
- Then there are the source listings. Yes, there's enough source in this book
- (but, alas, no companion disk) to satisfy your minimum daily requirement of
- real code: much of it in C, a few in sundry assembly languages, and one
- listing in Basic. But the art of software book publishing demands faithful
- reproduction of listings. Just a cursory scan caught a missing right
- parenthesis in the for loop on page 31; the phex routine on page 95 lost a
- curly bracket somewhere; page 96 contains a commented-out source line, which
- is somewhat disconcerting. These typos along with the schizophrenic
- indentation style hint of manually-typeset code listings--a dangerous
- practice. My overall impression: Academic Press skimped on (or rushed) the
- proofreading and the illustrations.
- These are nitpicking complaints. I'm being a bit harsh because such a valuable
- work deserves better handling. And pricey books with lofty titles justifiably
- receive more intense scrutiny. But I'll apply a more pragmatic rule of thumb:
- If a book's cost and the invested reading time is more than compensated by the
- added quality and productivity of my work, or to the improved quality of my
- company's product, it's an unequivocable bargain. Without question, The Art of
- Programming Embedded Systems hits this critical breakpoint.
- Title: The Art of Programming Embedded Systems
- Author: Jack G. Ganssle
- Publisher: Academic Press
- Price: $49.00
- ISBN: 0-12-274880-8
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- I'm back on the subject of standards again. (See the Editor's Forum, CUJ
- November 1992 and January 1993.) The most interesting news is that the ISO C
- standards committee WG14 voted out an amendment to the C language at its last
- meeting, back in December. It includes a (much modified) set of alternate
- spellings for all those operators and punctuators that use funny characters
- not widely available. It also includes lots more functions for manipulating
- the large character sets used by the Japanese, Chinese, and several other
- cultures.
- Future editions of my column, "Standard C," will discuss the technical details
- in greater depth. They are of interest mostly to people who write for
- international markets. My experience is that more and more of you will fall
- into that category as time goes by. On the subject of time, however, don't
- feel too rushed. The amendment still faces at least two votes within ISO SC22,
- the parent committee. Don't look for an official standard for many months to
- come.
- The next most interesting news is that SC22 has finally given us clear
- guidance for both interpreting and patching the C Standard. WG14 has begun by
- picking up all the ANSI Requests for Interpretation. They should finally see
- the light of day as an ISO Record of Response. WG14 will continue to ask
- X3J11, the original authors of the ANSI C Standard, for assistance in forming
- responses. But we can now use a more streamlined ISO channel for closing the
- loop.
- My job as Convenor of WG14 effectively makes me caretaker for the Standard C
- programming language. Besides convening WG14 meetings on a regular basis, I am
- now the keeper of what SC22 calls the Defect Report Log -- the formal requests
- for interpretation of (or correction to) the C Standard. By an administrative
- quirk, I can also personally expedite the filing of Defect Reports.
- Please don't take this admission as an invitation to send in all your random
- queries about the C Standard. I reserve the right not to sponsor any Defect
- Report that I choose. But if your organization needs a technical
- clarification, sending the request straight to me just might lob a month or
- two off of going through ANSI or another ISO member body.
- I did make good on my threat to resign all my posts within ANSI. That saves me
- a lot of money and a bit of time attending meetings. It costs me the right to
- vote on how C and C++ evolve, but what the heck. I hope to use the extra money
- and time to get more book writing done in 1993. That assumes, of course, that
- you don't all deluge me with interpretation requests.
- P.J. Plauger
- pjp@plauger.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- ParcPlace Introduces VisualWorks
-
-
- ParcPlace Systems, Inc., has introduced VisualWorks, an application
- development environment (ADE) for corporate developers creating graphical,
- client/server applications that are portable across PC, Macintosh, and UNIX
- platforms (Sun, IBM, HP, DEC, Sequent). VisualWorks includes a GUI builder,
- database access capabilities, and a reusable application framework.
- The GUI builder provides a point-and-click palette and canvas, with layout
- tools that include a menu builder, an icon painter, and a color tool.
- ChamelionView, a component of the GUI builder illustrates portability of a new
- GUI across various front-ends, including Windows, Motif, OS/2 Presentation
- Manager, Macintosh, and OPENLOOK. ChameleonView allows developers to preview
- the new interface in the native application look of these platforms.
- VisualWorks provides direct access to Oracle and Sybase databases. Through
- Information Builders, Inc.'s EDA/SQL gateway, more than 48 different databases
- can be accessed.
- VisualWorks is priced at $2,995 for Windows, OS/2, and Macintosh, and at
- $4,995 for UNIX. Database drivers cost $495 for Oracle or Sybase and $995 for
- EDA/SQL. Contact ParcPlace Systems, 999 E. Arques Avenue, Sunnyvale, CA 94086,
- (408) 481-9090; FAX: (408) 481-9095.
-
-
- Xionics Announces PowerTools and ImageSoft 2.0 for Image Processing
-
-
- Xionics, a developer of image acceleration technology, has announced
- PowerTools application programming interface for MS-Windows image processing,
- and ImageSoft 2.0, an update of their C library for image applications
- software. PowerTools consists of a Windows Dynamic Link Library (DLL) with ten
- high-level commands to control the aspects of monochrome document image
- processing. The PowerTools commands each operate by issuing a string of calls
- to several of the underlying C routines in Xionics ImageSoft Libraries.
- PowerTools Release 1.0 is available as object code, and is compatible with
- standard C compilers, such as Microsoft C and Turbo C.
- ImageSoft 2.0 encompasses over 80 routines for image processing: scanning,
- printing, compression/decompression, display, and enhancement. Xionics
- maintains a policy of "assuring complete backward compatibility" in its API.
- PowerTools is priced at $895, royalty-free. The price includes one year of
- technical support and software updates. Contact Xionics Inc., Two Corporation
- Way, Peabody, MA 01960, (508) 531-6666; FAX: (508) 531-6669.
-
-
- Micro Digital Introduces smx++
-
-
- Micro Digital has announced smx++, an Application Program Interface (API)
- which allows C++ programmers to access smx (simple multitasking executive)
- multi-tasking features in object form. smx++ supports Borland C++ v3.1 and
- Microsoft C++ v7.0.
- smx++ is a C++ class library consisting of nine base classes and seven derived
- classes. Redundant and seldom-used smx functionality has been omitted and
- orthogonality has been improved, in order to create a simpler API. A shallow
- class hierarchy was designed to preserve performance. smx++ runs on top of smx
- and direct smx C function calls can still be performed, and smxProbe still
- works the same. The new design made all smx objects (e.g., tasks, messages,
- semaphores, etc.) fully dynamic--deletable as well as createable.
- C++ developers can derive classes from the smx++ classes. For example, device
- drivers can be derived from the Bucket and Pipe classes, (which have been
- derived from the Software I/O Bus (SIObus) class).
- smx++ is priced at $2995 (including a royalty-free license), with source
- available for $1000, and smxProbe for $500. Contact Micro Digital, Inc., 6402
- Tulagi Street, Cypress, CA 90630-5630, (800) 366-2491 or (714) 373-6862; FAX:
- (714) 891-2363.
-
-
- Integrated Development Announces LibTools
-
-
- Integrated Development Corp. has announced LibTools, a set of programmer's
- tools for creating, managing, and exploring libraries of C, C++, Assembly,
- Xbase, and other Intel-, Microsoft-, and Borland-compatible object modules.
- LibTools provides extensive reporting and cross-referencing capabilities.
- LibTools can resolve public symbol conflicts, forecast overly-large
- executables, and verify module integrity. LibTools can show a complete list of
- the public and external references in a module, showing what will be linked in
- when you call a particular function. LibTools' LibComp utility can compare two
- libraries and produce a list of duplicate symbols, and LibTools can rename
- external and public symbols to resolve conflicts. LibTools also includes a
- Library Dump utility, which provides a detailed listing of the complete
- contents of a library. LibTools documentation includes tutorials, an
- introduction to library management, and tips on designing more granular
- libaries.
- Contact Integrated Development Corp., 190 Main Street, P.O. Box 592,
- Hampstead, NH 03841, (603) 329-5522 or (800) 333-3429; FAX: (603) 329-4842;
- CIS: 700441,2465.
-
-
- Archimedes Adds C Tools for Hitachi Microcontrollers
-
-
- Archimedes Software, Inc. has introduced C cross compilers and
- debuggers/simulators for the Hitachi H8/300 and H8/500 microcontroller
- families. The Archimedes C-Cross Compilers for the microcontrollers follow the
- ANSI C standard and support all the required libraries. The compilers provide
- several memory models (six of the H8/300 and 11 for the H8/500). Pre-defined
- in-line functions support interrupt handling. The compiler provides both
- single and double precision IEEE floating-point library functions. The
- compilers generate relocatable code and the Archimedes linker generates any of
- 32 different output formats for different emulators and PROM-programmers.
- The family of C-SPY High-Level Language Debuggers/Simulators for embedded
- applications support the Archimedes C-Cross compilers. C-SPY is avialble in
- simulator and emulator driver versions. The emulator versions support the
- MIME-700 in-circuit emulator from Pentica Systems, Inc. The H8/300 and H8/500
- Cross Compilers are hosted on PCs, HP 9000, Sun SPARCs, and DEC MicroVax and
- Vax platforms. The compilers are priced starting at $1295 for H8/300 on a PC.
- C-SPY is supported on PC compatibles, and priced at $1195 for the H8/300 and
- $1995 for the H8/500. Contact Archimedes Software, Inc., 2159 Union Street,
- San Francisco, CA 94123, (415) 567-4010; FAX: (415) 567-1318.
-
-
- Nu-Mega Announces BOUNDS-CHECKER 2.0, for MS-DOS Memory Protection
-
-
- Nu-Mega Technologies, Inc., has announced BOUNDS-CHECKER 2.0, an MS-DOS memory
- protection tool that provides real-time memory and heap protection.
- BOUNDS-CHECKER 2.0 can detect problems in a program's heap, stack, or data
- segment; handles array over-run detection; finds illegal memory accesses
- outside a program; and finds code overwrites automatically. BOUNDS-CHECKER
- adds a Smart Mode feature, with built-in heuristics to determine the
- legitimacy of a memory access, and avoid unnecessarily flagging legitimate
- out-of-bounds accesses (e.g., video memory, BIOS variables, etc.).
- BOUNDS-CHECKER 2.0 doesn't require anything to be linked-in or compiled, but
- can work directly with both Microsoft C 7.0 and Borland 3.1 with the VROOM
- overlay, as well as support memory managers such as QEMM. Contact Nu-Mega
- Technologies, Inc., P.O. Box 7780, Nashua, NH 03060-7780, (603) 889-2386; FAX:
- (603) 889-1135.
-
-
- StratosWare Releases MemCheck for the Macintosh
-
-
- StratosWare has introduced versions of its error detection and prevention
- product, MemCheck for the Macintosh, for the Think C and MPW C environments.
- MemCheck requires no source code changes. MemCheck detects memory overwrites
- and underwrites, memory leaks, heap corruption, and other memory errors.
- MemCheck operates transparently, appearing only to report errors with source
- file and line information. MemCheck for the Macintosh detects failure of
- memory allocation routines, failure of many resource operations, invalid
- operations on unlocked or purged handles, and inappropriate use of
- non-resource handles. One include file per source module is required to
- configure projects, and an automated configuration tool is included, MemCheck
- can be switched on or off at runtime, linked out via the production library,
- or compiled out with no source code changes. Contact StratosWare Corporation,
- 1756 Plymouth Road, Suite 1500, Ann Arbor, MI48105, (313) 996-2944 or (800)
- 933-3284; FAX: (313) 747-8519.
-
-
-
- Scientific Endeavors Announces GraphiC/Win Windows Graphics Library
-
-
- Scientific Endeavors has announced GraphiC/Win, a version of its C graphics
- library for Windows. The features of the MS-DOS version of GraphiC have been
- provided under Windows, allowing scientists to create graphics for
- publication. GraphiC/Win creates and manages its own window and resources, and
- adds features to take advantage of the Windows environment. GraphiC/Win will
- create Windows metafiles and bitmaps and copy both to the Windows clipboard.
- GraphiC/Win uses Windows video and printer drivers. Graphics are stored using
- the high-resolution Tektronix 4105 format; graphics can be exported in
- Postscript, GEM, Lotus PIC, HPGL, HPGL/2, and TIFF formats. GraphiC's routines
- come as source code. GraphiC/Win is priced at $495. Contact Scientific
- Endeavors Corporation, 508 North Kentucky Street, Kingston, TN 37763, (615)
- 376-4146 or (800) 998-1571; FAX: (615) 376-1571.
-
-
- Instrumentation Software Adds Stand-Alone Libraries
-
-
- National Instruments has announced LabWindows for MS-DOS Version 2.2, an
- instrumentation software package (for instrument control, data acquisition,
- analysis, and presentation) which now includes stand-alone libraries for the
- Borland C++ and Turbo C++ compilers and the Microsoft Visual Basic for DOS
- (VBDOS) compiler. Users can access the Borland compiler and linker from within
- the LabWindows programming environment, or they can add the LabWindows
- libraries to Borland's development environment. The LabWindows instrument
- driver library includes over 260 instrument drivers. Version 2.2 includes a
- float data type DSP Analysis Library, new cursor functions, DPMI memory
- manager, and utilties for MS-DOS file and directory commands from within
- LabWindows. LabWindows can control GPIB, VXI, and RS-232 instruments, and
- plug-in data acquisition cards. Contact National Instruments, 6504 Bridge
- Point Parkway, Austin, TX 78730-5039, (512) 794-0100 or (800) 433-3488; FAX:
- (512) 794-8411.
-
-
- StatSci Introduces S+INTERFACE Application Building Toolkit
-
-
- Statistical Sciences, Inc. (StatSci), has introduced S+INTERFACE, a toolkit
- designed to assist users of the S-PLUS data analysis software on UNIX
- workstations. S+INTERFACE can create custom menu interfaces to S-PLUS, and
- provides access for separate C applications to the over 1000 data analysis
- functions of S-PLUS. Under X11-based systems, S+INTERFACE provides a macro
- language that can be used to create a Motif-style user interface. S+INTERFACE
- provides access to S-PLUS functions from another application as if S-PLUS were
- a subroutine library. S-PLUS is initiated as a separate process and C
- functions calls are used for communication. Contact Statistical Sciences,
- Inc., 1700 Westlake Ave. N, Suite 500, Seattle, WA 98109, (206) 283-8802 or
- (800) 569-0123; FAX: (206) 283-8691; E-mail: mktg@statsci.com.
-
-
- RTIS Announces Distributed Application Builder
-
-
- The Real-Time Intelligent Systems (RTIS) Corporation has announced their DAB
- Distributed Application Builder Software. DAB provides network-wide data
- exchange between computer programs running on PC compatibles connected by LANs
- and serial links. An introductory DAB Kit includes C libraries compatible with
- Microsoft or Borland compilers, a users manual, and identification keys for
- two computers. DAB supports NetBIOS compatible LANs (e.g., Novel and
- Lantastic). DAB creates a multiprocessing environment, with MS-DOS running in
- the foreground and communications software running in the background. Contact
- The Real-Time Intelligent Systems Corporation, 30 Sever Street, Worcester, MA
- 01609, (508) 752-5567; FAX: (508) 752-5491.
-
-
- WCSC Releases COMM-DRV Version 12.0
-
-
- WCSC has released COMM-DRV Version 12.0, their serial communication
- development libraries and tools. COMM-DRV v12.0 supports Windows 3.x, MS-DOS,
- and DESQview. COMM-DRV supports dumb multiport cards, the ARNET SMARTPORT PLUS
- cards, and the Digiboard COMXi cards. COMM-DRV can be linked directly into
- MS-DOS or Windows applications, or it can be used as a DLL for Windows.
- COMM-DRV is priced at $189.95. Contact WCSC, 2470 S. Dairy Ashford, Suite 188,
- Houston, TX 77077, (800) 966-4832 or (713) 498-4832; FAX: (713) 568-3334.
-
-
- EMS Adds Products to Library of PD/Shareware C Utilities
-
-
- EMS Professional Software has added 70 new products to its Library of
- PD/Shareware C Utilities. The library includes 787 products for C/C++
- programmers. A database accompanying the library indexes the products and
- includes descriptions. Searches by type, name, vendor, or free text are
- supported. The library is available on disk or CD-ROM. Contact EMS, 4505
- Buckhurst Court, Olney, MD 20832, (301) 924-3594; FAX: (301) 963-2708.
-
-
- Shamus Software Announces Version 3.2 of its C Arithmetic Library
-
-
- Shamus Software Ltd. has announced version 3.2 of its MIRACL product, a
- Multi-precision Integer and Rational Arithmetic C library. New features
- include extensions conditionally-compiled in-line assembly. The libraries are
- of use primarily for implementing cryptography systems. Source is included.
- MIRACL is portable and supported platforms include: PC compatibles, Macintosh,
- Acorn, Sun, and VAX. Contact Shamus Software Ltd., 94 Shangan Road, Ballymun,
- Dublin 9, Ireland, Tel: 8425430.
-
-
- Liant Cuts Price and Boosts Execution Speed of LPI-C Compiler
-
-
- Liant Software has improved the execution speed of its LPI-C compiler, while
- cutting the price. LPI-C is bundled with CodeWatch, Liant's X/Motif
- source-level debugger. Liant reports that its LPI-C compiler v2.0 generates
- code that runs 50 percent faster than previous versions, achieving a 43,000
- rating on the Dhrystone 2.1 benchmark on a 33MHz, i486 system. Liant also
- reduced the suggested list price of LPI-C from $895 to $595. Liant LPI-C v2.0
- is a C compiler for UNIX applications on i386/i486 and Sun SPARC platforms.
- LPI-C is ANSI C compliant, and has passed the FIPS-160 ANSI/ISC C
- Validation-Suite (NIST-certified) and the Plum Hall ANSI C Validation Suite.
- Version 2 incorporates a new optimizer and an improved code generator. The
- optimizer provides global optimization across the entire compilation unit,
- loop unrolling, and function in-lining. Users can select either standard UNIX
- system header files or the LPI-C ANSI runtime library to ensure portability
- across operating systems and architectures for strictly conforming ANSI
- applications. Version 2.0 also compiles pre-ANSI sources including PC-based
- code. LPI-C also includes a windowed debugger, CodeWatch. Contact Liant
- SOftware Corporation, (508) 872-8700.
-
-
- ImageSoft Updates CommonView for OS/2
-
-
- ImageSoft has introducted the OS/2 2.0 version of CommonView, their
- application framework of C++ classes for developing GUI-based applications.
- CommonView for OS/2 v2.0 supports 32-bit applications. ImageSoft has also
- announced an agreement with J Systems, Inc., to publish Object/Designer, an
- extensible C++, C, and Pascal application generator for Windows. Contact
- ImageSoft Incorporated, 2 Haven Avenue, Port Washington, NY 11050, (516)
- 767-2233; FAX: (516) 767-9067.
-
-
- Dyad Software Ships M++ Version 4.0
-
-
-
- Dyad Software has begun shipping verion 4.0 of their M++ math library. The new
- version adds a set of spectral operations, a BitArray class, a PointerArray
- class, huge memory pointers for MS-DOS users, and a set of assembly language
- Basic Linear Algebra routines (BLAs). The spectral methods allow FFTs on
- vectors or arrays of vectors as well as multidimensional FFTs (up to four
- dimensions). M++ is available for MS-DOS C++ compilers (Borland, Microsoft,
- Zortech, and MetaWare) for $495, and for UNIX, WIndows NT, and OS/2 compilers
- for $695. Contact Dyad Software, Bellevue, WA, (206) 637-9426.
-
-
- Electronic Imagery Enhances Image Processing Software
-
-
- Electronic Imagery (EI) has announced enhancements to its image processing
- software. ImageScale Plus, ImageScale Plus for UNIX, and ImageScale Plus
- Developer's Toolkit for MS-DOS and UNIX applications, now include the JPEG
- compression/decompression algorithm. EI has released ImageManager, a file
- manager for pictorial images, documents, and associated text files in an SQL
- database, MSWindows environmennt. Another new release, ImageCount, supports
- image counting and object recognition that can define, count, number, and
- measure objects in imaging files and video frames. Contact Electronic Imagery,
- Inc., 1100 Park Central Boulevard South, Suite 3400, Pompano Beach, FL 33064,
- (305) 968-7100; FAX: (305) 968-7319.
-
-
- Liant Upgrades C-scape User Interface Management System
-
-
- Liant has announced a major upgrade (version 4.0) of its C-scape User
- Interface Management System, an object-oriented C development tool for
- creating portable text and GUI applications. Liant describes the most
- significant enhancement to C-scape as its greatly improved look and feel, with
- support for CUA (Common User Access) style boarders for both text and graphics
- mode, scroll bars, minimize/maximize buttons, menus, and other windowing
- functions. Contact Liant Software Corporation, Framingham, MA, (508) 872-8700.
-
-
- Data Entry Workshop Supports Interactive Design of Validated Entry Screens
-
-
- TurboPower Software has announced Data Entry Workshop, a collection of tools
- for writing validated data entry screens and other Windows controls. Data
- Entry Workshop builds controls in a three-step process: first, use Resource
- Workshop to place and edit the controls interactively; second, run the MAKESRC
- utility to generate the source code (C++ or Pascal); finally, use Borland's
- ObjectWindows Library to access the controls.
- Data Entry Workshop provides the following controls: Simple Entry Field,
- Numeric Entry Field, DEW Shade Control, Toolbox Control, Picture Entry Field,
- Spin Control, Meter Control, and Toolbar Control. Data Entry Workshop is
- designed for use with Borland C++, Borland Turbo Pascal for Windows, or
- Borland Turbo C++ for Windows. Data Entry Workshop includes full source code,
- comprehensive documentation, pop-up help, and example programs. Data Entry
- Workshop costs $189, and no royalty payments are required. Contact TurboPower
- Software, P.O. Box 49009, Colorado Springs, CO 80949-9009, (415) 322-3417.
-
-
- SET Laboratories Announces PC-METRIC 4.0 for C
-
-
- Set Laboratories, Inc., has announced version 4.0 of its software measurement
- and analysis package, PC-METRIC for C. Additions in version 4.0 include new
- measures of control flow complexity, iEEE standard size counting measures, and
- a completely revamped interactive query and analysis system for tracking
- metrics across releases. Contact SET Laboratories, Inc., P.O. Box 868, Mulino,
- OR 97042, (503) 829-7123; FAX: (503) 829-7220.
-
-
- Eighteen Eight Laboratories Introduces Three Interface Cards for PL2500
-
-
- Eighteen Eight Laboratories has announced three new interface cards for the
- PL2500 family of AT-hosted Floating Point Array Processors. The new cards use
- the PL2500's SPAN32 bus to transfer data at up to 15 million bytes per second.
- The PL2500 on-board rountines are callable from C (also Fortran and Pascal)
- control programs. Contact Eighteen Eight Laboratories, 1247 Tamarisk Lane,
- Boulder City, NV 89005, (702) 294-5009 or (800) 888-1119; FAX: (702) 294-2611.
-
-
- Aggregate Releases GNU make Compatible NetMake
-
-
- Aggregate Computing, Inc., has released NetMake 1.2, a distributed, parallel
- version of the UNIX make utility. NetMake can use multiple systems across a
- network of Sun workstations and servers in parallel, to handle Sun, BSD, or
- GNU makefiles. Contact Aggregate Computing, Inc., 300 South Highway 169, Suite
- 400, Minneapolis, MN 55426, (612) 546-5579; FAX: (612) 546-9485.
-
-
- SunPro Announces Object Technology Agreement with Rouge Wave Software
-
-
- SunProg, the software development business of Sun Microsystems, Inc., has
- entered a technology development and licensing agreement with Rogue Wave
- Software, Inc., a supplier of C++ class library technology. The agreement
- centers on Rogue Wave's Tools.h++ class library, a toolbox of nearly 100 C++
- classes. Contact SunPro, 2550 Garcia Avenue, Mountain View, CA 94043-1100,
- (415) 960-1300; FAX: (415) 969-9131.
-
-
- Genus Plans for GIF Toolkit and Printer Toolkit
-
-
- Genus Microprogramming has announced plans for a December release of GIF
- Toolkit and GX Printer, a printer toolkit. The GIF Toolkit provides over 100
- routines for incorporating GIF images into applications, and conforms to the
- GIF89a specifications. The GX Printer toolkit supoprt grtaphics printing from
- the Genus PCX or GIF toolkits, the display, or any GX virtual buffer. Contact
- Genus Microprogramming, 1155 Dairy Ashford, Suite 200, Houston, TX 77079,
- (800) 227-0918 or (713) 870-0737.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Mr. Plauger,
- I am a subscriber of The C Users Journal and I have been enjoying it from I
- started my subscription. In particular I want to ask you something. I am
- working in a project that requires sophisticated and unusual macros. Working
- on them I realized that the following code:
- {
- int i=1;
-
- (1, i)++;
-
- printf("Value of i is %d\n", i);
- }
- will print:
- Value of i is 2
- This makes sense to me. But I have tried to compiled with four different
- compilers:
- Sun ANSI C Compiler complains saying that the result of 1 is not a LHS value.
- It is certainly not.
- GNU C Compiler compiles and executes correctly.
- Sun C++ Compiler compiles and runs correctly
- Because I am getting two different results with two compiler that are supposed
- to be ANSI compliant, which one is the correct one? Is the construction valid?
- Thank you very much for your help. And sorry to bother you with this kind of
- simple questions.
- Daniel M. German
- dmg@cs.wm.edu
- Believe it or not, all compilers are behaving properly. The C Standard says
- the result of a comma operator is an rvalue, and the ++ operator is defined
- only for modifiable lvalue operands. Applying ++ to an rvalue is thus
- undefined behavior, which leaves implementations free to do as they choose.
- The strictest approach is to issue a diagnostic, as the Sun ANSI compiler
- does. But a common extension is to find some lvalue behind the rvalue and
- apply ++ to the lvalue. That's what the other compilers seem to be doing.
- For what it's worth, I was the one who screamed loudest that the comma
- operator (and a few others) be rvalues. Hope this helps. -- pjp
- Dear Mr. Plauger,
- In the July 1991 issue of CUJ, Jonathan Walker III had a lovely article in
- which he presented an algorithm for positioning a generalized tree. Last
- spring and summer, a student and I wrote a couple of programs utilizing this
- algorithm. These programs take bracketted output from syntactic and
- morphological parsers and display visual representations (trees) in an
- X-window or a large curses pad (and use the vi keys, hjkl, to navigate through
- various parts of the tree). The X-window version has been tested on Sun3s,
- Sun4s, and MIPS machines, and the curses version on Suns, PCs running XENIX,
- and PCs running MS-DOS. There is also a primitive version using Borland's bgi
- for MS-DOS.
- We would like to make these programs available for use by linguists by putting
- them in an ftp site. Last summer I wrote to Mr. Walker at the address given in
- his article to ask for permission to do this (his code makes up about 25% of
- each program). I haven't received a reply from him, and I'm writing to you to
- ask first if CUJ has a more recent address for him), and secondly if, failing
- that, it would be possible for CUJ to give up permission to use the code in
- these small, public-domain applications.
- On another topic, I've been a subscriber of CUJ since I found out about it a
- few years ago, and have enjoyed it immensely as well as learned a great deal
- from it. I particularly enjoy your articles on Standard C. (At my age, 50, I
- give myself the luxury, however of sticking to K&R C (the first edition). I'm
- professionally a linguist, anyway, and not actively involved in training
- programmers. I've noticed that the computer science students I work with all
- code in Standard C.
- With many thanks for your help,
- Chet Creider
- <creider@csd.uwo.ca>
- Diane Thomas, CUJ Managing Editor responds:
- All CUJ code is now posted on USENET. Please check the notice about online
- source code located in the table of contents for details.
- Dear P.J. Plauger,
- My copy of the Journal arrived yesterday, and I was pleased to see the usual
- broad range of articles. I found your column of particular interest, as I have
- recently reviewed some of the coding errors that I make. I hope that the
- Journal will not drift too far from C to C++, as I consider C++ to be a very
- different language, requiring different design considerations and often used
- for very different projects. Perhaps you could consider a little coverage of
- Objective C, which appears to me to be a much more helpful framework for OOP.
- The article "Time Complexity" by Wilbon Davies (page 31) displays a
- misunderstanding of the bubble sort. The code shown is incomplete,
- exaggerating the time taken:
- swap = 1;
- while (swap == 1) {
- swap = 0;
- for (i = 0; i < n - 1; i++) {
- if (x[i] < x[i + 1]) {
- tmp = x[i];
- x[i] = x[i + 1];
- x[i + 1] = tmp;
- swap = 1;
- }
- }
- }
- A practical bubble sort expands this simply, collapsing the scope of the sort
- faster. Where the data is already sorted, only (n - 1) compares are performed,
- partially and randomly sorted data require progressively more work. The worst
- case is for reverse sorted input, where no improvement is made over the
- published form. Keeping the format above:
- last = n;
- while (last > 0) {
- limit = last - 1
- last = 0;
- for (i = 0; i < limit; i++) {
- if (x[i] < x[i + 1]) {
- tmp = x[i];
- x[i] = x[i + 1];
- x[i + 1] = tmp;
- last = i;
- }
-
- }
- }
- A number of improvements can be made to this algorthim to give acceptable
- execution time, and reduce worst-case behaviour. In particular alternating
- between sorting forwards and backwards, this collapses the scope of the sort
- still faster. It also has the benefit of moving worst-case execution from a
- reverse sorted input, to an esoteric order to require the maximum exchanges.
- In several applications I have had the task of sorting large structures,
- rather than pointers, with the primary constraint on use of memory. I have
- found that an optimised version of the Bubble Sort has given very acceptable
- results.
- This incorrect version often features in articles comparing sort methods. The
- problem seems to be that the authors find it in several refernce books. In
- these books it is introduced briefly, only for a paragraph discussing its
- worst case operation. I would very much appreciate if articles in your Journal
- refering to "fastest bubble time," or similar, actually use the practical
- version, rather than propagate the brain-dead one on this occasion.
- I am happy to discuss this further, and you may edit the letter if you wish to
- publish it in the journal.
- Yours sincerely,
- Anthony Naggs
- Software/Electronics Consultant)
- Email: amn@vms.brighton.ac.uk
- P O Box 1080,
- or xa329@city.ac.uk
- Peacehaven,
- East Sussex
- BN10 8BT
- Phone: +44 273 589701
- Great Britain
- When Kernighan and I wrote The Elements of Programming Style, we discovered
- that "improving" a bubble sort often made it run slower rather than faster.
- Naturally, you can favor certain patterns of input to advantage. But the extra
- baggage you add generally slows average behavior for random input. A "brain
- dead" bubble sort is quite fast enough for sorting small quantities of data.
- For sorting large quantities, you're better off switching to a better
- algorithm than gilding this particular lily. Optimizations can pay off in the
- middle region, if you can determine what that is. -- pjp
- Dear P.J.:
- Is there an e-mail address for the C Users Journal? I didn't receive the
- September '92 issue and, being a hard-up student, want to avoid the cost of an
- international phone call to sort it out. By the way, I would also like to
- commend you on the thoughtfulness and intelligence of your writing, I
- appreciate it very much.
- Thanks,
- Jane Anna Langley
- 105 Osborne Street
- South Yarra
- University of Melbourne
- Victoria 3141
- AUSTRALIA
- (613) 03 820 3629
- s342046@emu.insted.unimelb.edu.au
- Diane Thomas, CUJ Managing Editor, responds:
- All questions regarding subscriptions or any other customer relations topic
- can be sent to cujsub@rdpub.com
- Just to start off, I find the magazine that you publish is the best that I
- have come across. To better clarify this, I appreciated the points you brought
- up in the Editor's Forum about product reviews and how the magazine stands on
- the issue.
- After reading that, I examined the editorial box to the right of the article
- and I believe I have found a mistake. Under trademarks, OS/2 is listed under
- the competitor's company, Microsoft. In the article, "Debugging with
- Assertions" on page 42, there is an error in the code in listing 2:
- void CheckEmpty() {
- assert(StackPtr = 1)
- }
- It should be as follows:
- void CheckEmpty() {
- assert(StackPtr == 1)
- }
- Keep up the good work!
- Sincerely
- Eric V. Blood
- ericb@sierra.com
- Thanks.--pjp
- Dear Mr. Pugh,
- To gain control of the specific area of the screen scrolled when printf does a
- line feed from the last row, I installed my own video interrupt service
- routine (ISR) for int10h. My ISR chains to the original ISR after testing AH
- for function code 6 (scroll up). If (and only if) AH==6, my ISR replaces the
- value in CH with a row number passed from the calling program before chaining
- to the original ISR. This gives the calling program a method of preventing
- data above a given row from scrolling off the screen, as long as scrolling is
- done by int10h calls. (Some compilers implement cprintf, cputs, and putch of
- <conio.h> so they can scroll up without using int10h.)
- My test program reports the vector of the original video ISR and the vector of
- the my replacement ISR. A test version of my ISR displays a signature on row
- 14 of the screen whenever AH==6. The signature consists of the char
- equivalents of CH (0), CL (0), DH (24), DL (79), AH (6), AL (1), and the value
- to be put into CH (from the calling program. CH,CL is used by the original ISR
- as the upper-left row,col of the window to be scrolled, while DH,DL is used as
- its lower-right row,col. AL is the number of lines to be scrolled.
- On a PC Designs XT with Hercules video under DOS 3.1, the test program and my
- ISR work as expected. On an AST Premium with VGA under DOS 3.3, my ISR is not
- called at all, as indicated by the absence of the diagnostic signature, even
- though the vectors reported show that my ISR was properly installed. The same
- failure was noted on a Packard-Bell with EGA under DOS 3.3.
- Suspecting that EGA/VGA video subsystems bypass int10h for all scrolling
- functions, I studied PC & PS/2 Video Systems (Richard Wilton, Microsoft Press,
- 1987), Programmer's Guide to the IBM PC (Peter Norton, Microsoft Press, 1985),
- and back issues of C Users Journal and Dr. Dobb's Journal. I found nothing
- that either confirmed or dispelled that notion.
- Can you offer any suggestions that may lead to an answer to this puzzle?
- Thank you,
- Sid Sanders
- 5 Seneca Avenue
- Geneseo, NY 14454-9508
- Sure looks like a bypass to me, but I'm hardly an expert in this area. Anybody
- out there got any ideas? I suggest you contact Mr. Sanders directly for speed.
- Send us a copy of your letter if you think the lore is worth sharing. -- pjp
- Re your editorial in the October 1992 C Users Journal:
- I bought a copy of the Shamus Software library MIRACL to fiddle with p. It has
- many mathematics routines and some top coding and decoding stuff. So I ported
- it to Coherent 310 and now to Coherent 401 which is the 32-bit system. I could
- compute p to 500 places in the 16-bit system and it does much more in the
- 32-bit version. I can get 50,000 places, but it takes 57 hours! It yields a 12
- page printout and I am not sure of its accuracy at this time. I would like a
- copy of the "Spigot" algorithm originally due to Stanley Rabinowitz which
- really works. I got one in an article by Peter Morrison on comp.lang.c which
- produces rubbish only.
- Also the Coherent 401 produces LOTTO programs with one million games in them.
- Of course it takes up 39 Mbytes but I use grep to see how many winning games
- are in the file.
- I tried porting the MIX Multi-C library to Coherent but it is no good. There
- may be a bug in the way coherent uses the typedef enum. It has a definition of
- ECODE in the file mtc_defs.h. This reads typedef enum { 13 names of errors.. }
- ECODE. Then it has the line:
-
- typedef ECODE (*TRAVERSE_FUNC) (void*,
- void*, int, int, void*);
- This is not acceptable to the compiler, which produces this message:
- 58: mtc_defs.h: missing ')'
- 58: mtc_defs.h: missing semicolon
- 58: mtc_defs.h: declarator syntax
- 58: mtc_defs.h: external syntax
- Have you got any ideas on what I can do with this?
- Jonathan Kitchin
- Perth, Western Australia
- jon@dialix.oz.au
- Once a compiler produces a diagnostic, it often gets confused for awhile. Try
- omitting the earlier statement (or fixing it if you know how). There's a good
- chance the subsequent diagnostics will evaporate. -- pjp
- Dear Sir,
- Some time ago, I purchased your book, "The Standard C Library," together with
- the code disk. I just thought I would pass on that it has been one of the
- better investments I have made in computer science books. I find it very
- instructive and a good source of ideas and algorithms. (I recently had to
- implement malloc on an embedded system with no operating system! Studying how
- you did it gave me a good kick start.)
- I read your "Standard C" column on bugs (CUJ September 1992) with great
- interest. I wish there were more articles about people's bugs and problems. I
- think we could learn more from them than from some of the "how to do it"
- articles. No doubt you have received other letters on this, but there seems to
- be a bug in your bug fix. [Bug report omitted. Same as earlier reports -- pjp]
- I hope that this bug did not creep into the v1.1 code disk.
- Speaking of the v1.1 code disk. You mentioned in the column that it now
- available, but I couldn't see anything in CUJ about how to order it. Do you
- need some "proof of ownership" for the upgrade? I can't see a serial number on
- the disks. Or do you rely on your purchase records? [MasterCard number
- omitted, for obvious reasons. -- pjp]
- Yours faithfully,
- Ian Cargill
- 54 Windfield
- Leatherhead
- Surrey
- KT22 8UQ
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Portable User Interface Using curses
-
-
- Matt Weisfeld
-
-
- Matt Weisfeld is currently employed by the Allen-Bradley Company in Highland
- Heights, Ohio. He responsible for the design and development of test software
- on VAX/VMS, UNIX, DOS, and other platforms. Matt is currently working on a
- book entitled Building and Testing Portable Libraries in C, to be published by
- QED in 1993. He can be reached on Compuserve at [71620,2171].
-
-
- The topics of portability and user interfaces are usually not mentioned in the
- same conversation, because the hardware-dependent nature of terminal devices
- makes user interfaces notoriously difficult to port. However, you don't
- necessarily need to write a separate user interface for each platform, even
- when you need maximum portability. This article presents a library of routines
- that allows a programmer to create a portable, text-based user interface using
- curses.
- curses, the screen-handling package available as part of the C package on VMS
- and most flavors of UNIX or as shareware, allows you to update screens
- efficiently. curscr keeps an image of the current screen. You change this
- image by changing the standard screen, stdscr, or by creating a new screen.
- refresh or wrefresh change curscr to match stdscr.
-
-
- The User Interface
-
-
- The example user interface discussed here can be ported to VAX/VMS,
- MS-DOS/BORLAND C, Hewlett-Packard/HPUX, and SUN/GNU with revision. The display
- consists of a main window, a menubar, and a dialog box (see Figure 1).
- The main window occupies the entire screen and is the backdrop for all other
- constructs. At the top of the main window, a single-line menubar presents the
- user with the available program options. The user chooses an option either by
- entering the first letter of the option or by using the arrow and return keys.
- Choosing one of these options will activate a pulldown menu containing further
- options. The dialog box, used to print informational messages and accept
- additional user input, resides at the bottom of the main window.
- Library functions to handle these user interface constructs, and other
- specific tasks, simplify the process of building screen applications. The
- separate library files can be linked into specific user applications.
- There are three major reasons for using curses: portability, availability, and
- usability. Even if curses is inappropriate for a specific application, you can
- apply the methods presented here for creating a menubar and pulldown menus to
- any user interface. The libraries can be treated as shells, with the curses
- commands replaced by other user interface commands. The appropriate #ifdefs
- make the libraries portable to multiple platforms.
-
-
- Primary curses Screen Structures
-
-
- Listing 1 contains a simple curses application. Just as C defines stdout,
- stdin, and stderr for input and output, curses keeps a memory representation
- of the screen in stdscr. All window operations affect only this memory
- representation. To change the screen itself, kept in curscr, you execute
- refresh or wrefresh--even when deleting a window.
- WINDOW, a data structure in curses.h required by all curses applications,
- contains information such as window location and size. Each window created
- must correspond to a pointer of this structure type. All curses programs
- create stdscr by default. Other curses constructs must be explicitly created.
- In most cases, curses treats stdscr differently from other windows. For
- example, to clear a window, curses performs the wclear(win) command, but to
- clear stdscr, curses uses the clear command, with no parameters.
-
-
- Creating a Popup Window
-
-
- Since most operations for any user interface involve windows, I built a
- library function called popup to create a popup window. (Listing 3 contains
- popup and all other library code presented in this article.) To create a
- window that entirely covers stdscr, I call popup with MAX_ROWS and
- MAX_COLUMNS.
- WINDOW *mainwin;
-
- mainwin = popup (MAX_ROWS,
- MAX_COLUMNS, 0,0);
- The constants MAX_ROWS and MAX_COLUMNS represent the standard screen size. The
- header file menu.h (Listing 2) defines all the constants and structures for
- this user interface.
- There are three popup windows in this application: the menubar, the dialog
- box, and a window used for pulldown menus. These are global to all functions
- and thus are declared as extern in most of the files.
- Color presents a special problem when writing a portable routine for creating
- a popup window. Since PC curses has color capabilities, whereas VMS and UNIX
- do not, ifdefs are used to take advantage of this feature. The PC curses
- command wattrset controls color. The colors representing the foreground and
- background are ORed together with:
- wattrset(mainwin, F_RED B_BLACK);
- PC curs es also has many more box characters to choose from. Many different
- effects can be obtained by using colors and box characters on the PC. However,
- if you desire a simple border around a window similar to VMS and UNIX, simply
- set the background to black and execute the box command on all platforms.
-
-
- Special Keyboard Input
-
-
-
-
- Accepting Single Keystrokes
-
-
- The example application uses the arrow keys. This causes a portability problem
- when creating a windowed curses application. wgetch gets characters from a
- window. However, curses buffers this input and echoes it to the screen. To
- prevent line buffering and echo, making the program accept one keystroke at a
- time, you must call crmode to set the cbreak mode and noecho to unset the echo
- mode.
- Using the arrow keys (from the keypad) on MS-DOS is very straightforward.
- Either the getch or the wgetch commands will return the necessary key code.
- (For all codes see Listing 2.)
- Obtaining non-printable characters with VMS requires the use of low-level
- Screen Management (SMG) commands. (See the VMS SMG manual for a complete
- description.) The two SMG commands needed here are CREATE_VIRTUAL_KEYBOARD,
- which activates the program for keyboard input, and READ_KEYSTROKE, which
- returns a keystroke. (VMS returns a short.)
-
-
-
- Interpreting Escape Sequences
-
-
- On UNIX systems, use of the arrow keys, escape key, and return key presents
- another problem. Both the HP and SUN systems return keystrokes from the keypad
- with escape sequences. Some systems, such as the HP, include a function called
- keypad. This function activates the keypad and returns the proper key code
- directly, saving the programmer from having to interpret the escape sequences.
- When the keypad function is not available, the method for dealing with the
- escape sequences depends on the system. For example, on the SUN system,
- entering a keypad character will return an escape sequence in three parts.
- When the first getch is recognized as an escape, two more getch commands must
- be called in succession. The third character contains the code needed.
- The character codes used in these libraries are defined in the file menu.h as:
- UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, ESCAPE, and RETURN.
-
-
- Creating a Menubar
-
-
- In all environments, the same structure defines a menubar:
- typedef struct mbar {
- char string[80];
- char letter;
- int pos;
- } MENUBAR;
- The first field holds the actual string that represents the particular option.
- For example, if one of the options across the top relates to printing, then
- the string print is a logical choice. The second field is the letter that
- invokes the option from the keyboard. The final field, called pos, holds the
- location of the string within the menubar.
- This example uses the code
- #define TCHOICES 3
-
- MENUBAR menubar[TCHOICES] = {
- "file", 'f', O,
- "edit", 'e', O,
- "options", 'o', O,
- };
- to create a menubar (see Listing 4). This declaration creates a menubar with
- three different options, file, edit, and options, invoked by entering an f, e,
- or o respectively. The positions are initially set to zero, and calculated,
- when needed, by the menubar routine. All the libraries use the constant
- TCHOICES to identify the number of options available. To add or delete
- options, adjust TCHOICES and add or delete the appropriate number of lines in
- the menubar structure.
- The routine topbar generates the menubar window (the window only, not its
- contents). In most cases, windows are created as separate entities. But since
- the menubar is a permanent part of stdscr, I made the menubar a subwindow of
- stdscr to improve efficiency. This technique allows you to refresh just
- stdscr, instead of refreshing both stdscr and the menubar. The code
- WINDOW *swin;
-
- if ((swin = subwin(win,3,(win->MAXX)-4,(win->BEGY)+1,
- (win->BEGX)+2)) == NULL)
- clean_up ();
- creates the menubar. The parameter list for the subwin command includes the
- window pointer of the parent. curses uses this pointer to calculate where to
- position the menubar (see curses.h). Beware not to use a pointer until you
- create the actual window. Until a newwin or subwin command is invoked, the
- pointer is null and passing a null pointer to a function may cause unexpected
- results.
- Even though the curses implementations for all the platforms is highly
- consistent, the variable names vary across different environments. (To
- position the menubar, you need the WINDOW structure coordinates of the parent
- window.) VMS uses _beg_x and beg_y while MS-DOS and UNIX use beg_x and beg_y.
- To make the code more portable, my code uses macros, such as BEGX.
-
-
- Displaying a Menu
-
-
- The function do_menubar performs all the tasks associated with selecting an
- option from a menubar. First, do_menubar prints the string in the window. The
- sample menubar includes three strings: file, edit, and options. You should
- space these three strings appropriately so that they fill the top of the
- screen evenly. This application uses a function called strmenu for spacing the
- strings. strmenu takes the menubar structure and the width of the parent
- window as parameters.
- strmenu proceeds in three stages. First, strmenu calculates the number of
- spaces allocated to each string by dividing the width of the parent window by
- the number of strings in the menubar (in this case three). Next, strmenu
- enters a loop that builds the menubar by copying each string and padding it
- with the proper number of spaces, highlighting the current choice (initially
- the one on the left) in upper case. Finally, strmenu returns the string
- pointer to the calling function, and prints the menubar using the mvwaddstr
- function.
-
-
- Detecting a Selection
-
-
- Once the menubar is in place, the user must be able to select one of the
- options. The user will type either the first character of the option or the
- return key to activate the highlighted option. By typing the left and right
- arrow keys, the user can highlight different options. After receiving a
- keystroke, a switch statement controls the action. If the program encounters
- an arrow, it moves the highlight either to the left or right (with allowances
- for wrap-around). An escape terminates the program, while a return breaks out
- of the loop and invokes the option currently highlighted. By default the
- program sends all other sequences back to the calling program as a character
- code.
-
-
- Creating a Pulldown Menu
-
-
- A pulldown menu is basically a popup window, except the pulldown routine must
- be able to adjust for windows of different sizes. To define the choices
- contained in each pulldown menu, I created the structure CHOICES:
- typedef struct choices {
- char string[20];
- char letter;
- int (*funcptr)();
- } CHOICES;
-
- As with the menubar structure, CHOICES contains the string that represents the
- option and the letter that invokes it. The third field, a function pointer,
- represents the function that will be executed when the option is chosen. The
- prototypes for these functions are in Listing 5.
- For example, suppose that invoking the menubar option file produces a pulldown
- menu with the options open, close, and exit. The application initializes the
- structure
- CHOICES choices1[3] = {
- "open ", 'o', c_open,
- "close", 'c', c_close,
- "exit ", 'e', c_exit,
- };
- Thus, if the user chooses open, the application calls function c_open. (I used
- the c_ prefix to avoid possible function name conflicts.)
- In this example application, there are three options (see Listing 4). They are
- all tied together with
- typedef struct pmenu {
- int num;
- int maxlength;
- CHOICES *ptr;
- } PULLDOWN;
- The structure PULLDOWN contains three pieces of information. The first field
- represents the number of options in the pulldown. The second indicates the
- maximum string length. The option close has five letters, and thus 5 is the
- maximum length. When creating the pulldown, the menu should have equal
- proportions. Thus, the shorter strings are padded with spaces to match that of
- the longest. The third field is the pointer to the structure that hold the
- choice information for this particular menu. The initialization of the entire
- PULLDOWN structure is
- PULLDOWN pullmenu[3] = {
- 3, 5, choices1,
- 4, 6, choices2,
- 3, 7, choices3,
- };
- To create a pulldown, the application calls the function do_pulldown. Choosing
- an option from the pulldown menu works like the menubar, except that
- do_pulldown uses UP_ARROW and DOWN_ARROW.
- Unlike the menubar, the pulldown menu must be erased after an option has been
- chosen, and whatever was underneath must be restored. The command touchwin
- performs this task. For this example, the pulldown window blocks both the
- menubar and stdscr, so both must be restored.
-
-
- Tying It All Together
-
-
- The program in Listing 4 demonstrates the advantages of building these
- libraries. The actual application requires only a dozen or so lines of code.
- The program consists of two basic parts: building the screen and creating the
- menus.
- The functions that the menu choices invoke are simply shells. (The programmer
- would substitute the appropriate functionality.) The only functions that
- perform any tasks are the version function, which displays the current program
- version, and the exit function, which terminates the application (Listing 6).
- To run the application, compile menu.c, uilibs.c, and funcs.c with the
- appropriate defines for the host platform. Then link them together with the
- curses library provided by the package. When the user starts the program, the
- menu screen will appear.
-
-
- Conclusion
-
-
- The curses interface has its limitations. A commercial package written in
- curses will most likely not show up on the store shelves. However, if you need
- a reasonably efficient and portable method of creating user interfaces, curses
- is an option.
- Figure 1 Sample menu screen using curses
-
- Listing 1 sample.c -- sample curses application
- #include <stdio.h>
- #include <curses.h>
-
- /* define window pointer */
- WINDOW *win;
-
- main()
- {
-
- /* must be called before any curses command */
- initscr();
-
- /* get space for window at coordinates
- /* (y,x,begy,begx) */
- /* y is # of rows, x is # of columns */
- /* begy & begx are the positions on the screen */
- win = newwin(24,80,0,0);
-
- /* surround the window with characters provided */
- box (win, '', '-');
-
-
- /* place the string in win at position y,x */
- mvwaddstr(win,2,2,
- "enter a character to erase window");
-
- /* this must be done to view changes on screen */
- /* otherwise changes are made only in memory */
- wrefresh(win);
-
- /* get a single character from the keyboard */
- wgetch(win);
-
- /* erase the window in memory */
- werase(win);
-
- /* necessary to complete the erase on screen */
- wrefresh(win);
-
- /* remove the window from memory */
- delwin(win);
-
- /* must be called to end a curses program */
- endwin();
-
- }
-
- /* End of File */
-
-
- Listing 2 menu.h
- /* screen dimensions*/
- #define MAX_ROWS 24
- #define MAX_COLUMNS 80
-
- /* keystroke codes and color */
- #ifdef HPUX
- #define UP_ARROW 3
- #define DOWN_ARROW 2
- #define LEFT_ARROW 4
- #define RIGHT_ARROW 5
- #define RETURN 10
- #define ESCAPE 27
- #endif
- #ifdef SUN
- #define UP_ARROW 65
- #define DOWN_ARROW 66
- #define LEFT_ARROW 68
- #define RIGHT_ARROW 67
- #define RETURN 10
- #define ESCAPE 27
- #endif
- #ifdef VMS
- #define UP_ARROW 274
- #define DOWN_ARROW 275
- #define LEFT_ARROW 276
- #define RIGHT_ARROW 277
- #define RETURN 13
- #define ESCAPE 291 /* F11 */
- #endif
-
- #ifdef BCC
- #define MAINCOLOR (F_RED B_BLACK)
- #define DIALOGCOLOR (F_CYAN B_BLACK)
- #define UP_ARROW 56
- #define DOWN_ARROW 50
- #define LEFT_ARROW 52
- #define RIGHT_ARROW 54
- #define RETURN 10
- #define ESCAPE 27
- #endif
-
- /* macros for portability */
- #ifndef VMS
- #define BEGX _begx
- #define BEGY _begy
- #define MAXX _maxx
- #else
- #define BEGX _beg_x
- #define BEGY _beg_y
- #define MAXX _max_x
- #endif
-
- /* box characters */
-
- #ifdef BCC
- #define SINGLE_SIDE -77 /* single bar */
- #define SINGLE_ACROSS -60
- #define DOUBLE_SIDE -70 /* double bar */
- #define DOUBLE_ACROSS -51
- #else
- #define SINGLE_SIDE ''
- #define SINGLE_ACROSS '-'
- #define DOUBLE_SIDE '"'
- #define DOUBLE_ACROSS '='
- #endif
-
- #define TCHOICES 3
-
- /* menubar structure */
- typedef struct mbar {
- char string[80];
- char letter;
- int pos;
- }MENUBAR;
-
- /* pulldown menu choices */
- typedef struct choices {
- char string[20];
- char letter;
- int (*funcptr)();
- } CHOICES;
-
- /* pulldown menu structure */
- typedef struct pmenu {
- int num;
- int maxlength;
- CHOICES *ptr;
- } PULLDOWN;
-
-
- /* prototypes */
- WINDOW *topbar(WINDOW *);
- WINDOW *pulldown(int,int,int,int);
- WINDOW *popup(int,int,int,int);
- void move_window(WINDOW *win,int y, int x);
- void print_string(WINDOW *,int, int, char *);
- void erase_window(WINDOW *);
- void delete_window(WINDOW *);
- void refresh_window(WINDOW *);
- int to_dialogue(char *);
- void clear_dialogue(void);
- void touch_window(WINDOW *);
- char *strmenu(int, MENUBAR *, int);
- char menu_choice(char *);
- int clean_up(void);
- void repaint(void);
- char do_pulldown(int,PULLDOWN *, MENUBAR *);
- void set_stdscr(void);
- void execute_command(int, int, PULLDOWN *);
- char do_menubar(WINDOW *, MENUBAR *);
- void strtoupper(char *);
- void strtolower(char *);
- void set_stdscr(void);
- char get_keystroke(void);
- /* End of File */
-
-
- Listing 3 uilibs.c -- library code
- #include <stdio.h>
- #include <stdlib.h>
- #include <signal.h>
- #include <time.h>
- #include <string.h>
- #ifdef VMS
- #include <smgdef.h>
- #endif
- #ifdef BCC
- #include <dos.h>
- #include <conio.h>
- #include <ctype.h>
- #endif
-
- #include "curses.h"
- #include "menu.h"
-
- extern WINDOW *dialogue;
- extern WINDOW *tbar;
-
- static bar_size; /* size of menubar */
- static int.menu_pos; /* position in menubar */
-
- /* display menubar */
- WINDOW *topbar(WINDOW *win)
- {
-
- WINDOW *swin;
-
- int string_count, string_size;
- if((swin = subwin(win,3,(win->MAXX)-4,(win->BEGY)+1,
-
- (win->BEGX)+2)) == NULL)
- clean_up();
-
- #ifdef BCC
- wattrset(swin, F_BLUE B_GRAY);
- #endif
-
- box (swin, SINGLE_SIDE,SINGLE_ACROSS);
-
- bar_size = (swin->MAXX)-2;
- menu_pos=0;
-
- return (swin);
- }
-
- /* print string to menubar */
- char do_menubar(WINDOW *swin, MENUBAR *menubar)
- {
-
- char * menu;
-
- char buffer[80];
-
- int status;
-
- #ifdef VMS
- int keyboard;
- short term_code;
- #else
- char term_code;
-
- #endif
-
- #ifdef VMS
- if ( (( status =
- SMG$CREATE_VIRTUAL_KEYBOARD(&keyboard))&1)!=1)
- clean_up();
- #endif
-
- term_code = 0;
-
- while (term_code != RETURN) {
-
- /* get the new menubar string */
- menu = strmenu(bar_size, menubar, menu_pos);
-
- mvwaddstr(swin, 1, 1, menu);
- wrefresh(swin);
-
- /* get a single keystroke */
-
- #ifdef VMS
- if ( (( status = SMG$READ_KEYSTROKE
- (&keyboard,&term_code))&l)!=l)
- clean_up();
- #endif
- #ifdef BCC
- term_code = wgetch(swin);
- #endif
-
- #ifdef HPUX
- term_code = getch();
- #endif
- #ifdef SUN
- term_code = getch();
- if (term_code == ESCAPE) {
- getch();
- term_code = getch();
- }
- #endif
-
- /* process keystroke */
- switch (term_code) {
-
- /* arrows check for wrap-around */
- case LEFT_ARROW:
- if (menu_pos == 0)
- menu_pos = TCHOICES-1;
- else
- menu_pos--;
- break;
-
- case RIGHT_ARROW:
- if (menu_pos == TCHOICES-1)
- menu_pos = 0;
- else
- menu_pos++;
- break;
-
- /* do nothing */
- case RETURN:
- break;
-
- /* exit program */
- case ESCAPE:
- clean_up();
- break;
-
- /* return keyboard input */
- default :
- return (term_code);
- break;
-
- }
-
- }
-
- /* return highlighted option */
- return (menubar[menu_pos].letter);
-
- }
-
- WINDOW *popup(int rows,int columns,int sy,int sx)
- {
- WINDOW *win;
-
- win = newwin(rows, columns, sy, sx);
-
- if(win == NULL) {
-
- endwin();
- clean_up();
- }
-
- #ifdef BCC
- wattrset(win, F_BLACK B_GRAY);
- #endif
- box(win, SINGLE_SIDE, SINGLE_ACROSS);
- wrefresh(win);
-
- return (win);
-
- }
-
- /* erase windows and surrounding box */
- void erase_window(WINDOW *win)
- {
-
- werase(win);
- box(win, ' ', ' ');
- wrefresh(win);
-
- }
-
- void delete_window(WINDOW *win)
- {
-
- delwin(win);
-
- }
-
- void refresh_window(WINDOW *win)
- {
-
- wrefresh(win);
-
- }
-
- void touch_window(WINDOW *win)
- {
-
- touchwin(win);
- wrefresh(win);
-
- }
-
- /* process pulldown menu options */
- char do_pulldown
- (int i, PULLDOWN *pullmenu, MENUBAR *menubar)
-
- {
-
- WINDOW *subwin1;
-
- int j;
- int position, oldpos;
-
- char *ptr;
-
-
- int status;
-
- #ifdef VMS
- int keyboard;
- short term_code;
- #else
- char term_code;
- #endif
-
- #ifdef VMS
- if ( (( status =
- SMG$CREATE_VIRTUAL_KEYBOARD(&keyboard))&1)!=1)
- clean_up();
- #endif
-
- subwin1 = popup( (pullmenu[i].num)+2,
- (pullmenu[i].maxlength)+2,stdscr->BEGY+3,
- (menubar[i].pos)+2);
-
- /* print pulldown options */
-
- for (j=0;j<pullmenu[i].num;j++) {
-
- ptr = pullmenu[i].ptr[j].string;
-
- mvwaddstr(subwin1, j+1, 1, ptr );
-
- }
-
- term_code = 0;
-
- position=0;
- oldpos = 0;
-
- while (term_code != RETURN) {
-
- /* highlight selected option */
-
- ptr = pullmenu[i].ptr[position].string;
-
- strtoupper(ptr);
-
- mvwaddstr(subwin1, position+1, 1, ptr );
-
- wrefresh(subwin1);
-
- /* get keystroke */
-
- #ifdef VMS
- if ( (( status =SMG$READ_KEYSTROKE
- (&keyboard,&term_code)) & 1)!=1)
- clean_up();
- #endif
- #ifdef BCC
- term_code = wgetch(subwin1);
- #endif
- #ifdef HPUX
- term_code = getch();
- #endif
-
- #ifdef SUN
- term_code = getch();
- if (term_code == ESCAPE) {
- getch();
- term_code = getch();
- }
- #endif
-
- oldpos = position;
-
- /* process keystroke */
-
- switch (term_code) {
-
- case UP_ARROW:
-
- if (position == 0)
- position = pullmenu[i].num-1;
- else
- position--;
-
- break;
-
- case DOWN_ARROW:
-
- if (position == pullmenu[i].num-1)
- position = 0;
- else
- position++;
-
- break;
-
- /* do nothing */
- case RETURN:
- break;
-
- /* get keyboard input and
- erase menu */
- default :
- erase_window(subwin1);
- delwin(subwin1);
-
- touchwin(stdscr);
- wrefresh(stdscr);
- touchwin(dialogue);
- wrefresh(dialogue);
-
- return (term_code);
- break;
-
- }
-
- /* restore to lowercase */
-
- ptr = pullmenu[i].ptr[oldpos].string;
-
- strtolower(ptr);
- mvwaddstr(subwin1, oldpos+1, 1, ptr );
-
-
- wrefresh(subwin1);
-
- }
-
- /* return highlighted optoin
- and erase menu */
- delwin(subwin1);
- erase_window(subwin1);
- touchwin(stdscr);
- wrefresh(stdscr);
- touchwin(dialogue);
-
- wrefresh(dialogue);
- return (pullmenu[i].ptr[position].letter);
-
- }
-
- /* calculate and produce menubar string */
- char *strmenu(int length, MENUBAR *menubar, int pos)
- {
-
- int i,j,k;
- int count;
- int string_length;
-
- static char buffer[100];
-
- /* determine max length for string */
- string_length = length/TCHOICES;
-
- k = 0;
- j = 0;
-
- /* add proper number of options */
- for (i=0;i<TCHOICES;i++) {
-
- menubar[i].pos = k;
-
- /* add each option, highlight as necessary */
- for (j=0;menubar[i].string[j]!='\0';j++,k++) {
- if (pos == i)
- buffer[k] = toupper(menubar[i].string[j]);
- else
- buffer[k] = tolower(menubar[i].string[j]);
- }
-
- /* pad with spaces to proper length */
- while (k<(string_length*(i+1)+2)) {
- buffer[k] = ' ';
- k++;
- }
-
- }
-
- return(buffer);
-
- }
-
- /* initialize the screen at start */
-
- void set_stdscr(void)
- {
-
- int i;
-
- wclear (stdscr);
-
- #ifdef BCC
- wattrset(stdscr, F_RED B_BLUE);
- /* fill in screen with color */
- for (i=0;i<MAX_ROWS;i++)
- mvinsertln(i,0);
- #endif
- box(stdscr, DOUBLE_SIDE, DOUBLE_ACROSS);
-
- refresh();
-
- return;
-
- }
-
- /* print string to dialogue box */
- int to_dialogue(char *string)
- {
-
- clear_dialogue();
- mvwaddstr(dialogue, 1, 1, string);
-
- box(dialogue, SINGLE_SIDE, SINGLE_ACROSS);
- wrefresh(dialogue);
-
- return;
-
- }
-
- void clear_dialogue(void)
- {
-
- werase(dialogue);
- box(dialogue, SINGLE_SIDE, SINGLE_ACROSS);
- wrefresh(dialogue);
-
- return;
-
- }
-
- void execute_command
- (int i, int choice, PULLDOWN *pullmenu)
- {
-
- int j;
-
- touch_window(tbar);
-
- for (j=0;j<pullmenu[i].num;j++) {
-
- /* use function pointer to execute command */
- if ( choice == pullmenu[i].ptr[j].letter) {
- (*(pullmenu[i].ptr[j].funcptr))();
-
- break;
- };
-
- }
-
- clear_dialogue();
-
- }
-
- /* convert a string to all uppercase */
- void strtoupper(char *string)
- {
-
- int i;
-
- for (i=0;string[i]!='\0';i++) {
-
- string[i] = toupper(string[i]);
-
- }
-
- return;
-
- }
-
- /* convert a string to all lowercase */
- void strtolower(char *string)
- {
-
- int i;
-
- for (i=0;string[i]!='\0';i++) {
-
- string[i] = tolower(string[i]);
-
- }
-
- return;
-
- }
-
- /* End of File */
-
-
- Listing 4 menu.c -- code for creating a menubar
- #include <stdio.h>
- #include <stdlib.h>
- #include <signal.h>
- #include <time.h>
- #include <string.h>
-
- #ifdef BCC
- #include <dos.h>
- #include <conio.h>
- #endif
-
- #include "curses.h"
- #include "menu.h"
- #include "internal.h"
-
-
- char choice;
-
- /* windows global to all routines */
-
- WINDOW *dialogue;
- WINDOW *tbar;
-
- /* define menubar with three options */
-
- MENUBAR menubar[TCHOICES] = {
- "file", 'f', 0,
- "edit", 'e', 0,
- "options", 'o', 0,
- };
-
- /* define pulldown menu sub-choices */
-
- CHOICES choices1[3] = {
- "open ", 'o', c_open,
- "close", 'c', c_close,
- "exit ", 'e', c_exit,
- };
-
- CHOICES choices2[4] = {
- "copy ", 'c', c_copy,
- "paste ", 'p', c_paste,
- "delete", 'd', c_delete,
- "move ", 'm', c_move,
- };
-
- CHOICES choices3[4] = {
- "version", 'v', c_version,
- "compile", 'c', c_compile,
- "link ", 'l', c_link,
- "run ", 'r', c_run,
- };
-
- /* tie all choices into one struct */
-
- PULLDOWN pullmenu[TCHOICES] = {
- 3, 5, choices1,
- 4, 6, choices2,
- 4, 7, choices3,
- };
-
- main()
- {
-
- int i,j,k;
-
- initscr();
-
- /* needed to return one keystroke at a time */
-
- #ifndef VMS
- cbreak();
- #else
- crmode();
-
- #endif
-
- /* activate keypad code */
-
- #ifdef HPUX
- keypad(stdscr, TRUE);
- #endif
-
- noecho();
-
- #ifdef BCC
- cursoff();
- #endif
-
- /* set up screen */
-
- set_stdscr();
-
- dialogue = popup(3, MAX_COLUMNS-4, MAX_ROWS-4, 2);
-
- clear_dialogue();
-
- tbar = topbar(stdscr);
-
- /* enter loop to process options */
-
- for (;;) {
-
- choice = do_menubar(tbar, menubar);
-
- for (i=0;i<TCHOICES;i++) {
-
- if ( choice == menubar[i].letter) {
-
- choice = do_pulldown(i,pullmenu,menubar);
-
- execute_command(i, choice, pullmenu);
-
- break;
-
- } /* if */
- } /* for loop */
-
- } /* end main loop (for (;;))*/
-
- }
-
- /* these commands must be called at exit */
- int clean_up()
- {
-
- erase();
- refresh();
- endwin();
-
- #ifdef BCC
- clrscr();
- #endif
-
-
- exit(0);
-
- return(0);
- }
- /* End of File */
-
-
- Listing 5 internal.h -- function prototypes
- extern int c_open();
- extern int c_close();
- extern int c_exit();
- extern int c_copy();
- extern int c_paste();
- extern int c_delete();
- extern int c_move();
- extern int c_compile();
- extern int c_link();
- extern int c_run();
- extern int c_version();
- extern int c_testchar();
- /* End of File */
-
-
- Listing 6 funcs.c
- #include <stdio.h>
- #include "curses.h"
- #ifdef BCC
- #include <dos.h>
- #endif
- #include "menu.h"
- #include "internal.h"
-
- /*
- all these functions are shells
- except for c_exit & c_version
- */
-
- int c_open()
- {
-
- to_dialogue("open");
-
- sleep(3);
-
- return;
- }
-
- int c_close()
- {
-
- to_dialogue("close");
- sleep(3);
-
- return;
- }
-
- int c_exit()
- {
- to_dialogue("exit");
-
- clean_up();
-
- return;
-
- }
-
- int c_copy()
- {
- to_dialogue("copy");
- sleep(3);
-
- return;
-
- }
-
- int c_paste()
- {
- to_dialogue("paste");
- sleep(3);
-
- return;
- }
-
- int c_delete()
- {
- to_dialogue("delete");
- sleep(3);
-
- return;
- }
-
- int c_move()
- {
- to_dialogue("move");
- sleep(3);
-
- return;
- }
-
- int c_compile()
- {
- to_dialogue("compile");
- sleep(3);
-
- return;
-
- }
-
- int c_link()
- {
- to_dialogue("link");
- sleep(3);
-
- return;
-
- }
-
- int c_run()
- {
-
- to_dialogue("run");
- sleep(3);
-
- return;
- }
-
- int c_version()
- {
-
- to_dialogue("Version 3.0");
- sleep(3);
-
- return;
-
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Prompting Function
-
-
- Dale A. Panattoni
-
-
- Mr. Panattoni received a Bachelor of Science degree in Computer Information
- Systems from DeVry Institute of Technology in Los Angeles, CA. He has worked
- the last four years as a Programmer/Analyst for DataStar Corporation, a
- company that specializes in business and fruit accounting software. You may
- contact him at DataStar Corporation, 6 South 55th Avenue, Yakima, WA 98908,
- (509) 453-2455, or at his home number, (509) 453-2455.
-
-
-
-
- Introduction
-
-
- In school, they tell you that when writing the user interface section of a
- program, you should write it as if a monkey will be sitting at the keyboard.
- Based on my experience, I sometimes think that it would be easier to write
- code for a monkey than a real user. A monkey may bang at the keyboard, but a
- user often times will try to outwit the program and unknowingly enter
- incorrect information.
- Several years ago I was shown a data input function, called prompt, that not
- only provided a good method for entering data, but also did basic validation
- of the data being entered. The function was printed in a book titled
- Variations in C by Steve Schustack (MicroSoft Press, 1985). After several
- modifications and enhancements, this function has become a cornerstone for all
- of my data input routines, both in MS-DOS and UNIX.
- In order to demonstrate prompt without requiring you to have a commercial
- windowing library, the version listed here has been stripped of all of its
- windowing library calls and replaced with Standard C I/O routines. To read a
- character from the keyboard, this version uses getchar. To display to the
- screen it uses printf. And to position the cursor, it uses a macro called
- MOVE_CUR. Since the MOVE_CUR macro uses ANSI codes, you need to include the
- ansi.sys driver in the file config.sys. Because of these changes, this
- function as it stands is written for MS-DOS, but it can easily be made to work
- with any MS-DOS or UNIX windowing library by changing just a few function
- calls.
- prompt accepts input from the user and returns to the calling program an
- integer value that represents a terminating key. The terminating key is the
- key that is pressed by the user signaling either that some action may need to
- be performed by the calling program or that the user is done entering data in
- the field. The calling program will determine what needs to be done based on
- that terminating key. For example, If the terminating key is an F1, you may
- want to display a help screen or a pick list pertaining to the current field.
- If the terminating key is what I call a "moving" terminating key--such as Up
- Arrow, Down Arrow, Home, End, Tab, or Enter--the calling program will probably
- want to move to the next or previous field.
- The keys.h header file included in prompt (see Listing 1) defines the values
- returned from getchar when the special keys, such as function and cursor keys,
- are pressed. This header file needs to be included not only with the file that
- defines prompt, but also with any program that calls it, if that calling
- program needs to know what terminating key caused prompt to return. When you
- implement a windowing library with prompt, this header file will probably need
- to be replaced by the windowing package's header file that defines its own
- return codes for the keyboard.
- Besides the definitions of the special keys, I have included definitions for
- four other terminating keys which can be returned by prompt. Because there are
- times when a data field requires specific validation, I wanted to know whether
- the user changed the data. If no changes occur, there is no need to do any
- validating. Therefore I have defined a NO_CHANGE value for Up Arrow, Down
- Arrow, Tab, and Enter (and only these four keys). They are the most common
- keys that a user would press when done entering information for a field. If
- you find that you use other terminating keys to end data input, you may need
- to add NO_CHANGE values for those keys as well.
-
-
- Calling the Prompting Function
-
-
- The function prompt expects nine parameters:
- data is a character pointer that points to a buffer holding the default value
- of the data field to be entered by the user. It may be initialized to NULL, or
- it may have a preset value. When prompt returns to the calling program, the
- buffer that data points to will be changed if the user has entered any data.
- match_char is a character code that represents what type of input will be
- accepted as being valid from the keyboard. These codes are setup and
- maintained in the match function.
- min_len is an integer value depicting a minimum number of characters to be
- entered by the user for this data field. The prompt function will not return a
- "moving" terminating key unless the user has met this requirement.
- max_len is an integer value depicting the maximum number of characters that
- can be entered for this data field.
- row and col are two integers which represent the starting row and column where
- the user will begin entering data.
- fyi is a character string that can be used either as the field's title, or as
- a "For Your Information" (FYI) line to give the user instructions about what
- is to be entered. If you decide not to use an FYI, you can pass a null string
- for this parameter.
- fyi_row and fyi_col are two integers which represent the starting row and
- column for the FYI message display.
- All of the row and column values refer to an entire screen of 12 rows and 80
- columns. If you implement a windowing library with prompt, you will probably
- want to add another parameter for a Window pointer. Then all of the row and
- column numbers will be relative to the window being used.
- When calling prompt, you can be as simple as you want, or you can analyze the
- return code in detail. In the sample program (See Listing 2), I check to see
- what terminating key is returned by prompt to determine what field the user
- wants to go to next. If you have more than a couple of fields to call using
- prompt, I suggest making an array of "field" structures that will hold all of
- the values for prompt for each field. This has two immediate advantages.
- First, the parameters passed to prompt are more readable at a glance. And
- second, if the array of structures is used every time a field's data is
- displayed to the screen, when your client asks you to move screen fields
- around, you can move a field anywhere they want in one simple change.
-
-
- Using the Prompting Function
-
-
- prompt is a versatile function. It not only allows the user to enter in new
- data but gives the user the freedom to edit existing data without having to
- retype the entire field. When the program's cursor first arrives at a field,
- the user can overwrite its data by simply typing new information. prompt will
- wipe out the old data and replace it with the newly-entered information as
- long as the first key entered by the user is not Right Arrow. If Right Arrow
- is pressed as the first key, prompt will enter into an edit mode. Edit mode
- will allow the user to move through the field's data using Left Arrow, Right
- Arrow, Home, and End. The user also has the option of toggling between an
- Insert mode and a Typeover mode by pressing the Insert key. When in Typeover
- mode, the cursor appears as a block. When in Insert mode, the cursor appears
- as an underscore. Because this version of prompt has been stripped of a
- commercial windowing library in order to make it work with the Standard C
- library, I have added the change_cur function to change the cursor's
- appearance. If you implement a windowing library prompt, you will probably
- want to replace change_cur with a similar function from your windowing
- library.
-
-
- Implementation
-
-
- prompt is really quite basic when you look at it closely. (See Listing 3.)
- When first called, it displays the data field and the For Your Information
- line. After that, the function goes into a loop that gets characters from the
- user until a terminating key is pressed. For each character entered by the
- user, prompt will verify that it is valid based on the match code that was
- passed in as a parameter. If the character entered is found to be valid, it is
- displayed to the screen and the user is prompted to enter another character.
- All editing keys such as Backspace, Left Arrow, Right Arrow, Home, and End can
- be entered by the user.
- When a terminating key is entered, prompt will do one of two things depending
- on what kind of terminating key is pressed. If the terminating key is not a
- "moving" key, prompt simply returns to the calling program the terminating key
- that was pressed. If the terminating key is a "moving" key, prompt will make
- sure that the length of the field's data meets or exceeds the field's minimum
- length requirement. If the minimum length has been met, prompt will return to
- the calling program. But before returning, prompt checks to see if the user
- has entered anything for this field. If not, then a NO_CHANGE value for the
- "moving" key is returned. Otherwise, the "moving" terminating key is returned
- to the calling program.
-
-
- Real-Time Validation
-
-
- match is a function that you, the programmer, set up to define a series of
- one-character codes that will represent sets of valid data. (See bottom of
- Listing 3.) You can have as many sets as you like, and add them as often as is
- needed. This function is what I think makes prompt so great. Once the user has
- entered in the field's data, you can assume that the field is valid. prompt
- will not allow any characters to be entered that are not defined as being
- correct for a particular match code. There may be times however when you will
- still have to do some validating when prompt returns to the calling program.
- For example, while prompt can make sure that the user enters all of the
- correct characters that make up a date, it does not validate the date. But
- based on my experience with this function, prompt eliminates 50 to 60 percent
- of the user entry errors by keeping them from typing invalid keys from the
- beginning.
-
- Listing 1 keys.h -- defines the values returned from getchar when special keys
- are pressed
- /* */
-
- /* Definitions Of Keys From The */
- /* Keyboard */
- /* */
-
- #define C_UP 328 /* Up Arrow */
- #define C_DOWN 336 /* Down Arrow */
- #define C_PGUP 329 /* Page Up Key */
- #define C_PGDN 337 /* Page Down Key */
- #define C_CR 13 /* Enter Key */
- #define C_BACK 8 /* Backspace Key */
- #define C_LEFT 331 /* Left Arrow */
- #define C_RIGHT 333 /* Right Arrow */
- #define C_TAB 9 /* Tab Key */
- #define C_HOME 327 /* Home Key */
- #define C_END 335 /* End Key */
- #define C_F1 315 /* F1 - F12 Keys */
- #define C_F2 316
- #define C_F3 317
- #define C_F4 318
- #define C_F5 319
- #define C_F6 320
- #define C_F7 321
- #define C_F8 322
- #define C_F9 323
- #define C_F10 324
- #define C_F11 325
- #define C_F12 326
- #define C_ESC 27 /* Escape Key */
- #define C_INS 338 /* Insert Key */
- #define C_DEL 339 /* Delete Key */
- #define CR_NO_CHG -1 /* This Value Represents
- The ENTER Key Pressed
- By The User Without
- Making Any Changes To
- The Field's Data */
- #define UP_NO_CHG -2 /* Up Arrow - No Changes */
- #define TB_NO_CHG -3 /* Tab Key - No Changes */
- #define DN_NO_CHG -4 /* Down Arrow - No Changes */
-
-
- Listing 2 Sample Program using prompt ()
- #include <stdio.h>
- #include <conio.h>
- #include <ctype.h>
- #include <dos.h>
- #include <string.h>
- #include "./keys.h"
-
- /* Identify Field Indexes */
- #define FNAME 0
- #define LNAME 1
- #define SEX 2
- #define AGE 3
-
- #define MOVE_CUR(row,col) printf("\x1B[%d;%df",row,col);
-
- /* Prototypes */
- extern int prompt (char *, char, int, int, int, int,
- char *, int, int);
-
-
- struct fields {
- short row; /* Field Row */
- short col; /* Field Column */
- short fyi_row; /* FYI Row */
- short fyi_col; /* FYI Column */
- short min_len; /* Minimum Length */
- short max_len; /* Maximum Length */
- char match; /* Match Character Code */
- char fyi[81] ; /* FYI Message */
- };
-
- struct fields field[] = {
- { 10,37,15,35,0,30,'A',"ENTER IN YOUR FIRST NAME."},
- { 11,37,15,35,0,30,'A',"ENTER IN YOUR LAST NAME. "},
- { 12,37,15,35,0, 1,'X',"ENTER IN YOUR SEX. (M/F) "},
- { 13,37,15,35,0, 3,'#',"ENTER IN YOUR AGE. "}
- };
-
- void main()
- {
- char xbuf[81]; /* Buffer */
- char fname[31]; /* First Name */
- char lname[31]; /* Last Name */
- char sex; /* Male/Female */
- int c;
- int age;
- int index;
-
- /* Initialize Variables And Draw Screen Titles */
- fname[0] = lname[0] = sex = '\0';
-
- MOVE_CUR(10,25);
- printf("FIRST NAME:");
- MOVE_CUR(11,25);
- printf("LAST NAME:");
- MOVE_CUR(12,25);
- printf("SEX M/F ..:");
- MOVE_CUR(13,25);
- printf("AGE ......:");
- age = 0;
- index = FNAME;
-
- while (1) {
- switch (index) {
- case FNAME:
- sprintf(xbuf,"%-s",fname);
- c = prompt(xbuf,
- field[FNAME].match,
- field[FNAME].min_len,
- field[FNAME].max_len,
- field[FNAME].row,
- field[FNAME].col,
- field[FNAME].fyi,
- field[FNAME].fyi_row,
- field[FNAME].fyi_col);
- switch (c) {
- case C_UP :
- case UP_NO_CHG:
-
- case C_END :
- /* Go To Last Screen Field */
- index = AGE;
- break;
-
- case C_ESC :
- /* Exit Program */
- exit(0);
-
- case C_CR :
- case C_DOWN:
- case C_TAB :
- strcpy(fname,xbuf);
- case CR_NO_CHG:
- case DN_NO_CHG:
- case TB_NO_CHG:
- /* Go To Next Field */
- index = LNAME;
- break;
- }
- MOVE_CUR(10,37);
- printf("%-30.30s",fname);
- break;
-
- case LNAME:
- sprintf(xbuf,"%-s",lname);
- c = prompt(xbuf,
- field[LNAME].match,
- field[LNAME].min_len,
- field[LNAME].max_len,
- field[LNAME].row,
- field[LNAME].col,
- field[LNAME].fyi,
- field[LNAME].fyi_row,
- field[LNAME].fyi_col);
- switch (c) {
- case C_END :
- /* Go To Last Screen Field */
- index = AGE;
- break;
-
- case C_ESC :
- /* Exit Program */
- exit (0);
-
- case C_UP :
- case UP_NO_CHG:
- case C_HOME :
- /* Go To Previous Field */
- index = FNAME;
- break;
-
- case C_CR :
- case C_DOWN:
- case C_TAB :
- strcpy(lname,xbuf);
- case CR_NO_CHG:
- case DN_NO_CHG:
- case TB_NO_CHG:
-
- /* Go To Next Field */
- index = SEX;
- break;
- }
- MOVE_CUR(11,37);
- printf("%-30.30s",lname);
- break;
-
- case SEX:
- sprintf(xbuf,"%c",sex);
- c = prompt(xbuf,
- field[SEX].match,
- field[SEX].min_len,
- field[SEX].max_len,
- field[SEX].row,
- field[SEX].col,
- field[SEX].fyi,
- field[SEX].fyi_row,
- field[SEX].fyi_col);
- switch (c) {
- case C_UP :
- case UP_NO_CHG:
- /* Go To Previous Field */
- index = LNAME;
- break;
-
- case C_ESC :
- /* Exit Program */
- exit(0);
-
- case C_HOME:
- /* Go To First Screen Field */
- index = FNAME;
- break;
-
- case C_CR :
- case C_DOWN:
- case C_TAB :
- sex = xbuf[0];
- case C_END :
- case CR_NO_CHG:
- case DN_NO_CHG:
- case TB_NO_CHG:
- /* Go To Next Previous Field */
- index = AGE;
- break;
- }
- MOVE_CUR(12,37);
- printf("%c",sex);
- break;
-
- case AGE:
- sprintf(xbuf,"%-d",age);
- c = prompt(xbuf,
- field[AGE].match,
- field[AGE].min_len,
- field[AGE].max_len,
- field[AGE].row,
- field[AGE].col,
-
- field[AGE].fyi,
- field[AGE].fyi_row,
- field[AGE].fyi_col);
- switch (c) {
- case C_UP :
- case UP_NO_CHG:
- /* Go To Previous Field */
- index = SEX;
- break;
-
- case C_ESC :
- /* Exit Program */
- exit(0);
-
- case C_HOME:
- case C_CR :
- case C_DOWN:
- case C_TAB :
- age = atoi(xbuf);
- case CR_NO_CHG:
- case DN_NO_CHG:
- case TB_NO_CHG:
- /* Go To Next Screen Field */
- index = FNAME;
- break;
- }
- MOVE_CUR(13,37);
- printf("%- 3d",age);
- break;
- }
- }
- }
-
- /* End of File */
-
-
- Listing 3 Prompt Function
- #include <stdio.h>
- #include <conio.h>
- #include <ctype.h>
- #include <dos.h>
- #include <string.h>
- #include "./keys.h"
-
- #ifndef YES
- #define YES 1
- #endif
-
- #ifndef NO
- #define NO 0
- #endif
-
- #define M_INSERT 1
- #define M_TYPEOVER 2
-
- #define MOVE_CUR(row,col) printf("\x1B[%d;%df",row,col);
- #define NORMAL "\x1B[Om" /* ANSI Normal Video */
- #define REVERSE "\x1B[7m" /* ANSI Reverse Video */
-
-
- /* Prototypes */
- extern int match (int, char);
- extern void change_cur (int, int);
- extern void main (void);
-
- int key;
-
- int prompt( data, match_ch, min_len, max_len, row, col,
- message, fyi_row, fyi_col)
-
- char data []; /* Return Message */
- char match_ch; /* Code For Which Characters
- Are Allowed */
- int min_len; /* Min Length Allowed */
- int max_len; /* Max Length Allowed */
- int row; /* Window Row */
- int col; /* Window Column */
- char *message; /* Prompt Message */
- int fyi_row; /* FYI Window Row */
- int fyi_col; /* FYI Window Column */
- {
- char buf [80];
- int terminate; /* Key That Caused Termination */
- int index,i; /* General Index Variables */
- int mode; /* Mode: INSERT or TYPEOVER */
- int changed; /* Changed Flag: 1=YES, 0=NO */
- int edit; /* Edit Flag: 1=YES, 0NO */
- int frst_key; /* First Key Flag: 1=YES, 0=NO */
- char more; /* YES/NO Flag . . .
- Used To Terminate Input */
-
- /* Initializations */
- edit = NO;
- frst_key = NO;
- changed = NO;
-
- mode = M_TYPEOVER;
- change_cur(0, 13); /* Set Cursor To Block Mode */
-
- /* Display The FYI Message At The FYI Row, Colunn */
- if (strlen(message)) {
- MOVE_CUR (fyi_row, fyi_col);
- printf(message);
- }
-
- printf(REVERSE); /* Change To Reverse Video */
-
- strcpy(buf, data); /* Make A Copy Of The Default
- Return Value */
-
- for (index = strlen(buf); index < max_len; ++index) {
- buf [index] = ' '; /* Fill Remainder Of Default
- Value With Blanks */
- }
- buf [index] = '\0';
-
- /* Display Default Value For Field */
- MOVE_CUR (row, col);
- printf(buf);
-
-
- /* Position Cursor At Beginning Of Field */
- MOVE_CUR (row, col);
-
- /* Get Input From User */
- for (index = 0,more = YES; more; ) {
- key = getch();
- if (key == 0)
- key = getch()+256;
-
- switch(key) (
- case C_F1 : /* F1 Key */
- case C_F2 : /* F2 Key */
- case C_F3 : /* F3 Key */
- case C_F4 : /* F4 Key */
- case C_F5 : /* F4 Key */
- case C_F6 : /* F6 Key */
- case C_F7 : /* F7 Key */
- case C_F8 : /* F8 Key */
- case C_F9 : /* F9 Key */
- case C_F10 : /* F0 Key */
- case C_F11 : /* F11 Key */
- case C_F12 : /* F12 Key */
- case C_ESC : /* ESCAPE Key */
- frst_key = NO;
- terminate = key;
- buf[index] = '\0';
- more = NO;
- break;
- case C_INS : /* INSERT Key */
- frst_key = NO;
- if (mode == M_INSERT) {
- /* Set The Mode To TYPEOVER and
- Change Cursor To Block Mode */
- mode = M_TYPEOVER;
- change_cur(0, 13);
- }
- else {
- /* Set The Mode To INSERT And
- Change Cursor To Underline */
- mode = M_INSERT;
- change_cur(12, 13);
- }
- putchar(bur[index]);
- MOVE_CUR(row, (col + index));
- break;
-
- case C_BACK : /* BACKSPACE Key */
- case C_DEL : /* DELETE Key */
- frst_key = NO;
- changed = YES;
- if (index >= max_len)
- index = max_len - 1;
- for (i = index; i < max_len; i++) {
- if (buf[i+1] == '\0')
- buf[i] = ' ';
- else
- buf[i] = buf[i+1];
- }
-
-
- /* Redisplay The Field */
- MOVE_CUR(row, col);
- printf(buf);
-
- /* Reposition The Cursor. */
- MOVE_CUR(row, (col + index));
- putchar(buf[index]);
- MOVE_CUR(row, (col + index));
-
- if (key == C_DEL)
- break;
-
- case C_LEFT : /* LEFT ARROW */
- first_key = NO;
- if (index <= 0)
- break;
- if (index >= max_len)
- index = max_len -1;
- index--;
- MOVE_CUR(row, (col + index));
- break;
-
- case C_RIGHT: /* RIGHT ARROW */
- frst_key = NO;
- edit = YES;
- if (index >= (max_len - 1))
- break;
- index++;
- MOVE_CUR(row, (col + index));
- break;
-
- case C_HOME : /* HOME KEY */
- case C_END : /* END KEY */
- frst_key = NO;
- if (edit == YES) {
- if (key == C_END)
- index = end_of_fld(buf,(strlen(buf)));
- else
- index = 0;
- MOVE_CUR(row, (col + index));
- break;
- }
-
- case C_DOWN : /* DOWN ARROW */
- case C_UP : /* UP ARROW */
- case C_PGDN : /* PAGE DOWN */
- case C_PGUP : /* PAGE UP */
- case C_CR : /* CARRIAGE RETURN */
- case C_TAB : /* TAB Key */
-
- frst_key = NO;
-
- /* Reset Mode and Edit Flag */
- edit = NO;
- mode = M_INSERT;
-
- if (strlen(data) >= (unsigned)min_len &&
- index == 0 && changed == NO) {
-
- /* If The User Has Not Changed The Value Of
- The Data Field, Return The Appropriate
- NO CHANGE Key For The terminating Key
- That Was Pressed. */
-
- if (key == C_CR) {
- /* Return No Change For Carriage Return */
- terminate = CR_NO_CHG;
- }
- if (key == C_UP) {
- /* Return No Change For Up Arrow */
- terminate = UP_NO_CHG;
- }
- if (key == C_DOWN) {
- /* Return No Change For Down Arrow */
- terminate = DN_NO_CHG;
- }
- if (key == C_TAB) {
- /* Return No Change For Tab Key */
- terminate = TB_NO_CHG;
- }
- if (key != C_CR &&
- key != C_UP &&
- key != C_DOWN &&
- key != C_TAB)
- terminate = key;
-
- strcpy(buf, data);
-
- more = NO;
- break;
- }
-
- /* If Minimum Length Requirement Has been Met,
- Quit */
- if (index >= min_len) {
- terminate = key;
- more = NO;
- break;
- }
-
- /* Else Ignore */
- break;
-
- default :
- if (index == max_len)
- /* At End Of Data Field - Do Nothing */
- break;
-
- /* We Have Dealt With All Of The Keys That We
- Needed Above, So Now We Will Filter Out All
- Keys And Characters Above 122 (z) Here. */
-
- if (key > 'z') /* Ignore Key */
- break;
-
- if (match(index, match_ch) == YES) {
- /* Only Accept The User Entered Character
- If It Successfully Passed The match
-
- function. */
- edit = YES;
- changed = YES;
-
- if (frst_key == YES) {
- /* If This Is The First Valid Key That
- The User Has Entered, Then Wipe Out
- The Field. */
- for (i = 0; i < max_len; i++)
- buf[i] = ' ';
- frst_key = NO;
- }
-
- if (mode == M_INSERT) {
- /* Insert The User Entered Character
- Into The Field */
- for (i = (max_len - 1); i > index; i--)
- buf[i] = buf[i-1];
- }
-
- /* Overwrite The Buffer With The User
- Entered Character */
- buf[index] = (char)key;
-
- /* Redisplay The Field */
- MOVE_CUR(row, col);
- printf(buf);
-
- /* Reposition The Cursor. */
- index++;
- if (index >= max_len)
- index --;
- MOVE_CUR(row, (col + index));
- putchar(buf[index]);
- MOVE_CUR(row, (col + index));
- }
- break;
- }
- }
- /* Redraw The Field In The Window's Normal
- Color Attribute */
- printf(NORMAL);
- MOVE_CUR (row, col);
- printf(buf);
-
- strcpy(data, buf);
- data[(end_of_fld(data,(strlen(data))) + 1)] = '\0';
-
- /* Change Cursor Back To Underline */
- change_cur(12, 13);
- return (terminate);
- }
-
- static int match(index,match_ch)
- int index;
- char match_ch;
- {
- int matches = YES;
-
-
- switch (match_ch) {
- case '$' : /* 0-9 or ,$. */
- if (!isdigit(key) && !strchr(".,$",key))
- matches = NO;
- break;
-
- case '#' : /* 0 - 9 */
- if (!isdigit(key))
- matches = NO;
- break;
-
- case 'A' : /* A - Z */
- if (!isalpha(key) && !strchr(" ",key))
- matches = NO;
- break;
-
- case 'D' : /* Date: 0-9 or - or / or .*/
- if (!isdigit(key) && !strchr(".-*/",key))
- matches = NO;
- break;
-
- case 'l' : /* a-z, A-Z, 0-9, or ;:.,/?*-$#()'! or
- leading space */
- if (!isalnum(key) &&
- !strchr(" !@#$%^&*()-_=+[{]}';:.,/?",key))
- matches = NO;
- break;
-
- case 'Q' : /* YyNn as Reply To Yes/No Question */
- if (!strchr("YyNn",key))
- matches = NO;
- key = toupper(key);
- break;
-
- case 'S' : /* 0-9 or + or - */
- if (!isdigit(key) && !strchr("+-",key))
- matches = NO;
- break;
-
- case 'T' : /* Time: 0-9 or . or : */
- if (!isdigit(key) && !strchr(".:",key))
- matches = NO;
- break;
-
- case 'X' : /* MmFf As Reply To Male/Female */
- if (!strchr("MmFf",key))
- matches = NO;
- key = toupper(key);
- break;
-
- default :
- matches = NO;
- break;
- }
- return (matches);
- }
-
- /* This Function Will Return A 0 Based Index Of The
- First Non-Blank Character At The End Of A String. */
-
- int end_of_fld(string, length)
- char *string;
- int length;
- {
- int i;
-
- for (i = length - 1; i >= 0; i--) {
- if (string[i] != ' ' && string[i] != '\0')
- return(i);
- }
- return(0);
- }
-
- /* This Function Will Change The Appearance Of The
- Cursor To A Block Or An Underscore */
- void change_cur(start, end)
- int start;
- int end;
- {
- union REGS r;
-
- r.h.ah = 1;
- r.h.ch = start;
- r.h.cl = end;
- int86(0x10, &r, &r);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Mapping Functions for Repetitive Structures
-
-
- Steven K. Graham
-
-
- Steven K. Graham has worked for Hewlett-Packard, served on the Faculty at
- UMKC, and is currently a Senior Engineer with CSI.
-
-
- Higher-order functions take other functions as arguments. They are usually
- used to apply a function to some repetitive data object, such as a list or a
- tree. Such use of higher-order functions is referred to as mapping, and the
- higher-order functions are called mapping functions. The key benefit of
- higher-order functions is the extra layer of abstraction they introduce. In
- essence, mapping functions allow you to create custom control structures that
- can be applied to data structures routinely used in a particular environment.
- Mapping functions do not simplify code intended for a single use or a single
- purpose. Only when a data object is processed repeatedly in similar
- ways--where the control flow is the same, but the functions performed vary--is
- a mapping function valuable. As an example, consider the general case of
- processing records in a particular kind of data file. This can be regarded as
- mapping the processing function onto the records of the file. A mapping
- function could be built that takes any given record processing function and
- applies it to all the records of a file.
-
-
- Uses for Mapping Functions
-
-
- Despite sparse use in traditional programming languages, mapping functions are
- used in many familiar contexts. In mathematics, a higher-order function is
- used to express iterated sums:
- Click Here for Equation
- The summation applies the function F to each integer from 1 to n, and
- accumulates the sum. If n were 5 and F(x) = x x, then sum = 11 + 22 + 33 + 44
- + 55. It can be argued that a large part of the success of mathematics is due
- to developing effective language and notations to express complex ideas. In
- UNIX, the notion of higher-order commands has appeared in commands that take
- other commands as arguments and control their application. sed is a good
- example of this.
- Some languages provide convenient methods for expressing higher-order
- functions. Lisp and its dialects such as Scheme, provide functions as first
- class objects--functions can be passed as arguments, returned as values,
- assigned as array elements and so on. So Lisp programmers commonly use mapping
- functions and are accustomed to exploiting their power. Other languages
- restrict the use of functions to varying degrees, but higher-order functions
- are possible in languages readily available, such as C. Because of C's
- (relatively) strong typing, mapping functions force the processing functions
- they apply to return a particular type, and it is necessary to coerce types to
- pass a function argument to a mapping function.
-
-
- Example Programs
-
-
- Listing 1 illustrates a C version of summation. It defines two mapping
- functions, sum and dsum, and three processing functions which are mapped onto
- a range of integers, pi, id, and sq. Note that sum and dsum are basically for
- loops that process the integers from a to b, at each step accumulating the
- result of applying the function parameter term to the current value of i.
- Because of C's typing, you need two versions to accommodate different return
- types despite the identical control structures, so dsum is used to accumulate
- a floating-point sum. One of the processing functions, id, is particularly
- noteworthy, because it is only in the context of higher-order functions that a
- programmer ever needs an identity function that does nothing but return its
- argument value. However, id is needed for sum to simply add the integers from
- a to b.
- The call to dsum, approximates p using a partial sum of an infinite series.
- This is drawn from the identity that
- Click Here for Equation
- which can be written as
- Click Here for Equation
- with the function pi (in Listing 1) providing the value for each term, given
- the value for i.
- Higher-order functions become mapping functions when applied to repeating
- structures, such as lists or trees. A list is comprised of an item and a
- pointer to the rest of the list (which is also a list). A tree is comprised of
- a node, with subtrees as children. This mapping approach can be applied to the
- directory tree of a UNIX file system (see Listing 2). The function mapdir is a
- recursive function that maps its argument filefn onto all the files in the
- hierarchy below a commandline directory argument.
- This example applies the function suidfile that prints out the name of files
- that have the setuid bit set. The setuid bit indicates that when the file is
- executed, it will change its effective user ID to the owner of the file when
- it is run. Programs that set the user ID can lead to gaping holes in system
- security. (Sun's Programmer's Guide to Security Features comments on its use,
- "1. Don't do it unless absolutely necessary.") Using mapdir, it is simple to
- search an entire directory structure for programs that set the user ID. This
- example could be part of a suite of programs designed to explore the file
- system for potential security problems. A more elaborate system could be
- devised to check other file attributes, such as comparing file permissions to
- the permissions of the directory that contains them. The real power of this
- approach is that the mapdir function is independent of any particular use.
- Simply by calling mapdir with the function printfile (in Listing 3) as an
- argument, you have a program to display directories recursively. By creating
- routines that check for unusually large files, for unusually small files, or
- for large numbers of files within a single directory, you can build tools to
- help manage disk usage.
- The functions and structures used in mapdir are drawn from the standard
- include files, dirent.h, sys/dirent.h and sys/stat.h. These examples were
- developed on a Sun SPARCstation using SunOS 4.1. dirent.h provides the DIR
- struct, along with the functions opendir, closedir, and readdir to manipulate
- it. sys/dirent.h provides the struct dirent, for directory entries:
- struct dirent {
- off_t d_off;
- /* offset of next disk dir entry */
- unsigned long d_fileno;
- /* file number of entry */
- unsigned short d_reclen;
- /* length of this record */
- unsigned short d_namlen;
- /* length of string in d_name */
- char d_name[255+1];
- /* name (up to MAXNAMLEN + 1) */
- };
- For the example programs, the field d_name, a string that specifies the file
- name for a particular directory entry, was the only field used. stat.h
- provides the following structure to store information about files:
- struct stat {
- dev_t st_dev;
- /* device file resides on */
- ino_t st_ino;
- /* file serial number */
- mode_t st_mode;
- /* file mode */
- short st_nlink;
- /* # of hard links to file */
- uid_t st_uid;
- /* owner's user ID */
-
- gid_t st_gid;
- /* owner's group ID */
- dev_t st_rdev;
- /* dev identifier for *
- /* dev identifier for *
- * special files */
- off_t st_size;
- /* file size in bytes *
- * - (off_t is a long) */
- time_t st_atime;
- /* last access time */
- int st_spare1;
- time_t st_mtime;
- /* last modify time */
- int st_spare2;
- time_t st_ctime;
- /* last status change time */
- int st_spare3;
- long st_blksize;
- /* preferred block size */
- long st_blocks;
- /* # of blocks allocated */
- long st_spare4[2];
- };
- The example programs use the st_mode field, as well as some of the macros and
- constants defined in stat.h. In particular, the macros S_ISDIR, S_ISLNK, and
- the bit mask S_ISUID are used to examine the st_mode field, which records file
- types and permissions. stat.h also declares the stat function that reads the
- file information. stat has the declaration
- int stat(path, buf)
- char *path;
- struct stat *buf;
- The mapdir program can be made more general. In Listing 3, mapif.c, the mapdir
- function has been modified to accept three functions as arguments: a predicate
- function, a then function and an else function. The purpose is to not only map
- these functions across the file system, but also provide alternative actions
- depending on the results of the predicate function. The example uses a
- function that tests for the setuid bit, a printfile function, and a "do
- nothing" function, to reproduce the behavior of the earlier example from
- Listing 2.
-
-
- Variations to Mapping Functions
-
-
- In these example, the functions that are mapped rely on a single filename
- argument. This can be varied. A file descriptor could be passed. The complete
- path could be passed in addition to the filename. Other parameters might
- specify the level in the file hierarchy or information about the directory
- containing the file. Global variables or additional parameters could be set
- using command-line arguments and used to provide thresholds for searching for
- unusually large or small files, or perhaps a search string for building a
- grep-like function out of mapdir and a routine that searches a single file.
- Besides operating on files within the file system, a mapping function could be
- written that processes the directories rather than (or in addition to)
- processing the files, for example, counting the number of files in each
- directory.
- The file system isn't the only regularly structured data in a UNIX system. A
- simple procedure based on the mapping notion could be written that applies a
- function to each line within a file. Using such a routine, functions could
- operate on the passwd file, checking for users with no password, or checking
- directory protections on the home directories of each user. A function could
- be written to process every user's .cshrc file, perhaps to incorporate changes
- uniformly.
- UNIX, perhaps more than any other system, is characterized by an abundance of
- tools. As I mentioned at the outset, many of these tools incorporate the
- notion of function or command arguments. The find command, with an - exec
- parameter, processes the file system in much the way that mapdir does. So why
- bother with mapdir? First, the key point is not the particular example, but
- the general idea: whenever possible, abstract general behaviors over regular
- structures into mapping functions. Second, tools built in C rather than as
- shell scripts or command invocations are more flexible. Their input and output
- can be varied; alternatives can be handled; and programs can be tailored to
- particular uses. Finally, the performance of different approaches will vary.
- In some cases, a program may be more efficient than a command with respect to
- some essential resources.
- (C) 1993 by Steven K. Graham
-
- Listing 1 Mapping and processing functions
- int sum(a,b, term)
- int a,b, (*term) ();
- {
- int i, sum = 0;
- for (i=a; i<b; i++)
- sum = sum + (*term) (i);
- return sum;
- }
-
- double dsum(a,b, term)
- int a,b;
- double (*term) ();
- {
- int i;
- double sum = 0;
- for (i=a; i<b; i++)
- sum = sum + (*term) (i);
- return sum;
- }
-
-
- double pi(x)
- int x;
- { return 1.0/(16*x*x + 16*x + 3); }
-
- int id(x)
- int x;
- { return x; }
-
- int sq(x)
- int x;
- { return x*x; }
-
- int main(argc, argv)
- int argc;
- char **argv;
- {
- int n,x;
- char * pid, *psq, *ppi;
-
- if (argc < 2) return -1;
- n = atoi(argv[1]);
- x = atoi(argv[2]);
-
- pid = (char *) id;
- psq = (char *) sq;
- printf("%d is the sum of %d to %d\n", sum(n,x,pid), n, x);
- printf("%d is the sum of squares from %d to %d\n\n", sum(n,x,psq),n,x);
- ppi = (char *) pi;
- printf("%10.8f is an approximation of PI\n\n", 8*dsum(n,x,ppi));
-
- return 0;
- }
-
- /* End of File */
-
-
- Listing 2 Mapping functions applied to a repeating structure -- in this case a
- UNIX file system
- #include <string.h>
- #include <dirent.h>
- #include <sys/stat.h>
- #include <sys/param.h>
-
- #define LASTCHR(s) s[strlen(s)-1]
-
- int suidfile(name)
- char *name;
- {
- struct stat stbuf;
- stat(name, &stbuf);
- if (stbuf.st_mode & S_ISUID) printf("%s\n", name);
- return 0;
- }
-
- int mapdir(name, filefn)
- char *name;
- int (*filefn) ();
- {
- DIR *dirp;
-
- struct dirent *entry;
- struct stat stbuf;
- char olddir[MAXPATHLEN];
- char cname[MAXPATHLEN];
-
- getwd(olddir);
- strcpy(cname, name);
- if (chdir(name)) {
- printf("Couldn't change to directory %s\n", name);
- return -1;
- }
- if ((dirp = opendir(name)) == NULL){
- printf("Unable tto open DIR %s\n", name);
- chdir(olddir);
- return -1;
- }
- for (entry=readdir(dirp); entry != NULL; entry=readdir(dirp)) {
- if (0 != stat(entry->d_name, &stbuf))
- printf("Can't read file information %s\n", entry->d_name);
- else if (strcmp(entry->d_name, ".") == 0
- strcmp(entry->d_name, "..") == 0)
- /* don't pursue these entries */ ;
- else if (S_ISLNK(stbuf.st_mode))
- /* don't pursue links */ ;
- else if (S_ISDIR(stbuf.st_mode)) {
- if (LASTCHR(cname) != '/') strcat(cname, "/");
- strcat(cname, entry->d_name);
- mapdir(cname, filefn);
- strcpy(cname, name);
- } else
- (*filefn) (entry->d_name);
- }
- closedir(dirp);
- chdir(olddir);
- return 0;
- }
-
- char *setupdir(buf, name)
- char *name;
- char *buf;
- {
- char curdir[MAXPATHLEN];
-
- getwd(curdir);
- if (name[0] == '/') /* absolute pathname */
- strcpy(buf, name);
- else { /* relative pathname*/
- strcpy(buf, curdir);
- if (buf[strlen(buf)-1] != '/') strcat(buf, "/");
- /* may be at root */
- strcat(buf, name);
-
- }
- return buf;
- }
-
- int main(argc, argv)
- int argc;
- char **argv;
-
- {
- char *filefn;
- char name[MAXPATHLEN];
-
- if (argc < 2) {
- printf("Usage: %s <directory>",argv[0]);
- return -1;
- }
- setupdir(name, argv[1]);
- filefn = (char *) suidfile;
- return mapdir(name, filefn);
- }
- /* End of File */
-
-
- Listing 3 mapdir -- maps its argument onto all the files in the hierarchy
- below the command-line directory argument.
- #include <string.h>
- #include <dirent.h>
- #include <sys/stat.h>
- #include <sys/param.h>
-
- #define LASTCHR(s) s[strlen(s)-1]
- int do_nothing(name)
- char *name;
- { return 0; }
-
- int printfile(name)
- char *name;
- { printf("%s\n", name);
- return 0;
- }
-
- int suidfile(name)
- char *name;
- { struct stat stbuf;
- stat(name, &stbuf);
- return (stbuf.st_mode & S_ISUID);
- }
-
- int mapdir(name, predfn, thenfn, elsefn)
- char *name;
- int (*predfn)(), (*thenfn)(), (*elsefn)();
- {
- DIR *dirp;
- struct dirent *entry;
- struct stat stbuf;
- char olddir[MAXPATHLEN];
- char cname[MAXPATHLEN];
-
- getwd(olddir);
- strcpy(cname, name);
- if (chdir(name)) {
- printf("Couldn't change to directory %s\n", name);
- return -1;
- }
- if ((dirp = opendir(name)) == NULL){
- printf("Unable tto open DIR %s\n", name);
- chdir(olddir);
- return -1;
-
- }
- for (entry=readdir(dirp); entry != NULL; entry=readdir(dirp)) {
- if (0 != stat(entry->d_name, &stbuf))
- printf("Can't read file information %s\n",
- entry->d_name);
- else if (strcmp(entry->d_name, ".") == 0
- strcmp(entry->d_name, "..") == 0)
- /* don't pursue these entries */ ;
- else if (S_ISLNK(stbuf.st_mode))
- /* don't pursue links */ ;
- else if (S_ISDIR(stbuf.st_mode)) {
- if (LASTCHR(cname) != '/') strcat(cname, "/");
- strcat(cname, entry->d_name);
- mapdir(cname, predfn, thenfn, elsefn);
- strcpy(cname,name);
- } else
- if ((*predfn) (entry->d_name))
- (*thenfn) (entry->d_name);
- else (*elsefn) (entry->d_name);
- }
- closedir(dirp);
- chdir(olddir);
- return 0;
- }
-
- char *setupdir(buf, name)
- char *name;
- char *buf;
- {
- char curdir[MAXPATHLEN];
-
- getwd(curdir);
- if (name[0] == '/')
- strcpy(buf, name);
- else { strcpy(buf, curdir);
- if (buf[strlen(buf)-1] != '/') strcat(buf, "/");
- /* may be at root */
- strcat(buf, name);
- }
- return buf;
- }
-
- int main(argc, argv)
- int argc;
- char **argv;
- {
- char *predfn, *thenfn, *elsefn;
- char name[MAXPATHLEN];
-
- if (argc < 2) {
- printf("Usage: %s <directory>",argv[0]);
- return -1;
- }
- setupdir(name, argv[1]);
- predfn = (char *) suidfile;
- thenfn = (char *) printfile;
- elsefn = (char *) do_nothing;
- return mapdir(name, predfn, thenfn, elsefn);
- }
-
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Natural Language Processor
-
-
- Russell Suereth
-
-
- Russell Suereth has been consulting for over 12 years in the New York City and
- Boston areas. He started coding and designing systems on IBM mainframes and
- now also builds PC software systems. You can write to Russell at 84 Old
- Denville Rd, Boonton, NJ 07005, or call him at (201) 334-0051.
-
-
- Hardware and software are sold with CD-ROMs, sound boards, and speech
- synthesizers. These components allow the user and the computer to interact in
- a human manner. This stage of user and computer interaction can be extended
- further with human language. The natural language processor presented in this
- article is a practical application of the use of human language.
-
-
- Processing Human Language
-
-
- A natural language processor takes a human statement, analyzes it, and
- responds in a way that appears human. But this appearance isn't a mystery. A
- natural language processor performs certain processes based on the words in
- the input sentence. In many ways, processing human language is just another
- data-processing function. The input sentence is the input transaction record.
- The words in the sentence are fields in the input record. The dictionary is a
- master file of word information. The meaning is derived from a combination of
- information in the input sentence, the dictionary, and program code. The
- generated response to the sentence is the output.
- Some simple natural language processors process one-word commands that don't
- require much analysis, such as find, up-date, and delete. This type of
- processor uses a small dictionary to identify the commands. When used with a
- database, commands to such a processor could execute functions to find,
- update, and delete records. A similar processor can be connected to a
- remote-controlled car and used with the commands forward, reverse, and stop.
- Other natural language processors are more sophisticated and can process
- multiple input sentences. A larger dictionary contains information about the
- word such as whether it is a noun, a preposition, or a verb. This complex
- processor can analyze a sentence, identify parts of the sentence, derive
- meaning from the sentence, and generate an appropriate response or action.
- Theoretically, a complex processor may parse through a book and derive
- meanings and themes from the sentences. It may then generate a response based
- on these meanings and themes.
-
-
- Building a Processor
-
-
- The natural language processor presented here is a miniature version of the
- complex processor just described. It can process several English input
- sentences such as Jim is running in the house, Bill is walking on the street,
- and Sue is going to a store. It can also process sentences such as Where was
- Bill walking? and generate a response that states Bill was walking on the
- street. An actual session with the processor is shown in Figure 1. This
- miniature processor, however, has a limited capability. It can only be used
- with the kinds of input sentences shown in the figure. But this capability can
- be increased by expanding existing routines and adding new ones. Some areas of
- possible expansion will be indicated throughout this article.
- A natural language processor must perform some amount of analysis on the
- sentence and the words in it. The analysis may be simple and only identify
- that the words are valid by matching them with the dictionary. Or, it may be
- complex and identify structures, phrases, meanings, and themes in the
- sentence. The analysis this particular processor performs identifies phrase
- structures and meanings.
- This processor uses transformational grammar to identify underlying phrase
- structures in the input sentence. Each word in the sentence has a type such as
- noun or verb. Specific combinations of these types make a certain phrase
- structure. For example, the words a, the, and these are determiners and are
- located right before a noun. A determiner-noun combination such as the house
- is a kind of noun phrase. Phrase structures must be identified in order to
- identify sentence elements such as subject, action, and place. Figure 2 shows
- some abbreviated phrase structures used in transformational grammar.
- There are a limited number of underlying phrase structures in transformational
- grammar. But there are an unlimited number of possible sentences. The
- relationship of structures to possible sentences is shown with the two
- sentences Jim is running in the house and Sue is going to a store. The
- underlying structure (name-auxiliary-verb-preposition-determiner-noun) is the
- same in these two examples, although the words make the two sentences
- different. The limited number of structures can be coded in the program
- without coding the many combinations of words that may occur.
- Transformational grammar also defines how words are used together or used in a
- certain context. For example, the woman drove the car is correct but the table
- drove the car is not. Words have restrictions to determine how they can be
- used together properly. The inanimate object table cannot be used with the
- action verb drove. Only humans can drive so a HUMAN restriction should be
- assigned to the verb drove. Additional restrictions of MAN, WOMAN, PERSON, and
- TEENAGER should also be assigned. The natural language processor in this
- article uses only one restriction labeled ING for words such as going,
- running, and walking. An expanded natural language processor may use many
- additional restrictions. One of these is verb tense so that Jim was runs in
- the house is recognized as an incorrect sentence.
-
-
- Processing the Sentence
-
-
- Listing 1 contains the natural language processor. The main routine first
- calls the initialize routine. This routine initializes some variables that are
- used throughout the program. Each entry in the subjects, actions, and places
- arrays is initialized. The first entry contains the subject, action, and place
- of the first input sentence. The second entry contains this information for
- the second input sentence. The arrays have 20 entries and can contain
- information from 20 input sentences. Next, the main routine opens the
- dictionary file named diction. If the dictionary opened successfully, the main
- control flow of the program is entered.
- The main control flow of the program is a while statement that loops once for
- each input sentence. Several processes occur within the while statement for
- each sentence. From a broad view, the program extracts the sentence words and
- matches them with the dictionary, which contains information about the word.
- The program loads this word information into arrays that it analyzes to
- determine the underlying structure of the sentence. The program identifies the
- subject, action, and place words in the sentence and copies these words to
- their arrays, which contain an entry for each input sentence. The program
- determines the appropriate response and then generates that response. When
- there are no more input sentences the program closes the dictionary and then
- ends.
- From a detailed view, the while loop executes until the input sentence is
- null, that is, when only Enter is pressed on the keyboard. First, the program
- calls the reset_sentence routine. This routine initializes variables that are
- used for each input sentence. reset_sentence initializes the word_ct variable,
- which will contain the number of words in the sentence. reset_sentence also
- initializes array entries. These arrays contain an entry for each word in the
- sentence, allowing up to 10 words in an input sentence. The type_array
- contains five additional entries for each word, allowing up to five possible
- types for the word. Examples of types are noun, preposition, and verb. The
- main routine then parses through the input sentence to extract each sentence
- word. For each word in the sentence main calls the get_record routine and
- increments word_ct.
- The get_record routine reads each record in the dictionary and calls the
- match_record routine to determine whether the dictionary word matches the
- sentence word. If they match, then the types variable is incremented to
- accommodate another type for the same word. The dictionary can contain
- multiple types for the same word. The word jump, for instance, can be a noun
- or a verb and would have a dictionary record for each type. When the end of
- the dictionary file has been reached, the types variable is checked to see
- whether the word was found. If the word wasn't found and the first character
- of the word is uppercase, then the word is a name. Names such as Jim and Sue
- aren't kept in the dictionary. The word is then copied to word_array for later
- processing. The routine can be modified to find the word faster by changing
- the dictionary to an indexed file. The dictionary appears in Listing 2. It is
- the same dictionary used in the session shown in Figure 1.
- get_record calls the match_record routine for each record in the dictionary.
- match_record compares the passed sentence word with the word in the current
- dictionary record. match_record extracts the word from the dictionary with the
- extract_word routine, then it matches the extracted dictionary word with the
- passed word. If the match is successful, then the type is extracted from the
- dictionary record and copied to type_array.
- If the type is a verb, then the root is extracted from the dictionary with the
- extract_root routine and copied to root_array. In a group of similar words
- such as run, runs, ran, and running, the word run is the root. Each verb in
- the dictionary has a root. The root will later identify a group of similar
- words that may be used in a generated response sentence. An expanded natural
- language processor that generates many different responses would find the root
- invaluable. For example, given the input sentence Jim is running in the house,
- a generated response may be Why does Jim run in the house? or It appears that
- Jim runs often. The response words run and runs can be identified in the
- dictionary through the common root run.
-
-
- The Underlying Structure
-
-
- The check_underlying routine identifies underlying phrase structures in the
- input sentence. The code shows two specific underlying structures that can be
- identified. The first underlying structure is a question sentence that starts
- with a WH word. A WH word is a word such as where or what that starts with the
- letters wh. The next word in the input sentence must be an auxiliary which is
- labeled AUX. The next word must be a name, and the last word must be a verb.
- This underlying structure has the types: WH-AUX-NAME-VERB. It can be used for
- many similar sentences such as Where was Bill walking and Where was Sue going.
- The check_underlying routine calls check_type which compares the passed type
- with the possible types in type_array. The type_array variable holds the
- possible types for each input sentence word. If the first input sentence word
- has a WH type, then it matches the structure for the first word. Each word in
- the input sentence is checked to see if it matches the type in the structure.
- If all the input sentence words match, then the sentence has the underlying
- structure WH-AUX-NAME-VERB.
- Once the underlying structure is matched, the correct type is copied to
- prime_types. The prime_types array identifies that the word jump, for example,
- is a noun rather than a verb. Verbs refer to actions and nouns refer to
- places. This type identification will be used later to identify words in the
- sentence that refer to an action or a place.
- Next, the kind of phrase is assigned. Auxiliaries and verbs are assigned to
- verb phrases, determiners and nouns are assigned to noun phrases, and
- prepositions are assigned to prepositional phrases. Phrases are combinations
- of specific, adjacent word types. For instance, a noun phrase has a DET-NOUN
- combination. Phrase identification will be needed later to locate words in the
- sentence that identify a place. For example, a place can be identified by the
- prepositional phrase in the house. In an expanded natural language processor,
- phrase identification would be increased to several processes. What initially
- looks like a noun phrase such as the house may be a prepositional phrase such
- as in the house after additional sentence analysis.
- The second underlying structure that can be identified has the types
- NAME-AUX-VERB-PREP-DET-NOUN. This structure can be used for sentences such as
- Bill is walking in the street and Sue is going to a store. The two coded
- underlying structures will only accept input sentences such as Sue is going to
- a store and Where was Sue going? Other kinds of sentences can be processed
- when other underlying structures are coded.
- Additional underlying structures that can be coded are shown in Figure 2.
- Notice that the two coded structures aren't shown. These two structures were
- created only for explanatory purposes instead of the more lengthy code
- required for all the underlying structures. In an expanded natural language
- processor, the transformational grammar structures would be coded. For
- example, one coded structure would be DET-NOUN to identify a kind of noun
- phrase.
-
-
- Identifying Sentence Elements
-
-
- The elements of subject, action, and place help convey the meaning in the
- input sentence. Subject identifies who or what the sentence is about, action
- identifies the activity that is performed, and place identifies where the
- activity occurred. In the input sentence Sue is going to a store, the subject
- is Sue, the action is going, and the place is to a store. Without the
- identification of these elements, the sentence would be merely composed of
- meaningless words, types, and phrases.
- Three routines in the processor identify the sentence elements. The
- check_subject routine looks at each word in the input sentence. If the word is
- a name, then check_subject copies the word to the subjects array, which
- contains a subject entry for each input sentence. The check_action routine
- also looks at each word in the input sentence. If the word is a verb, then
- check_action copies the root of the word to the actions array, which contains
- an action entry for each input sentence. The root will be useful when
- expanding the processor. It will allow the processor to determine that Jim ran
- on the street and Jim runs on the track are similar actions. It will also
- allow an appropriate form of run to be used in a response statement. For
- example, Jim ran should be used to describe past tense and Jim runs to
- describe present tense. The check_place routine looks at each word in the
- input sentence too. If the word is in a prepositional phrase, then check_place
- concatenates the word to the places array, which contains a place entry for
- each input sentence. Each word in the prepositional phrase refers to a place
- and will be concatenated to the places array.
-
- With this information, a simulated understanding of the sentence can be
- derived. The processor does this by matching the subject and action words in
- the current input sentence with information in previous sentences. For
- example, one input sentence can be Jim is running in the house. The processor
- will place Jim in the subjects array, run in the actions array, and in the
- house in the places array. A later input sentence can be a question that asks,
- Where was Jim running? The processor will identify Jim as the subject and run
- as the action. Since this is a question, the processor will also search the
- subjects array and actions array for the words Jim and running. When a match
- is found, the corresponding places array will be used to create a response
- that states, Jim was running in the house. If a person saw only the input
- sentences and responses as shown in Figure 1, then the processor would appear
- to have some degree of understanding. But this is only an appearance. The
- processor generates a canned response of words that are based on a combination
- of input sentence words and information in the arrays.
- An expanded natural language processor can contain code for a number of
- responses. It can also contain the routines check_manner and check_time to
- identify how and when something occurred. These two routines should allow
- prepositional phrases to identify elements of place such as in my house as
- well as elements of manner and time such as in my joy and in the morning.
-
-
- Generating a Response
-
-
- The response is the output statement from the natural language processor.
- After reading and processing an input sentence, the natural language processor
- must generate an appropriate response in acknowledgement. When a person
- speaks, the listener responds to let the speaker know the words were heard and
- understood. The response may be a simple nod of the head or several sentences
- that explain the listener's understanding. Two considerations that determine
- the kind of response are the listener's knowledge of the spoken information,
- and whether the spoken words were a question or a statement.
- The make_response routine uses these two considerations to generate responses
- to the input sentence. First, it checks to see whether the first word in the
- input sentence is Where. If it is, then the input sentence is a question. The
- second consideration is whether the processor has knowledge of information in
- the input sentence. The processor has this knowledge when the information
- exists in sentence elements from previous sentences. In the question Where was
- Jim running, the subject is Jim, and the action is running. Since it's a
- where-question, it's asking for a location associated with the words Jim, and
- run which is the root of running. The processor keeps information from
- previous sentences in the subjects, actions, and places arrays. The places
- array contains locations. The make_response routine searches the subjects and
- actions arrays for a match with Jim and run. When it finds a match the
- associated entry in the places array will contain the information to generate
- a response that states where Jim is running.
- When the input sentence is a where-question, and make_response does not find
- Jim and run, the processor doesn't have enough information to indicate Jim's
- location. The routine then moves the statement I don't know to the response
- variable. When the input sentence is not a question, it is simply a statement
- of fact. The routine then moves Ok to the response variable. Other kinds of
- responses can be coded and generated. For example, an array of You don't say,
- Please go on, and Tell me more statements can be coded and used as responses.
- When the input sentence is a where-question, and make_response finds Jim and
- run, the make_response routine calls the make_answer routine. make_answer
- creates an answer by placing the associated subject, action, and place words
- together in the response variable.
- The make_answer routine is passed an array index that relates the appropriate
- entries in the subjects and places arrays. First, the routine copies the
- appropriate subject to the response variable giving Jim. It then concatenates
- was to the variable to give Jim was. Next, it calls the get_verb_ing routine
- to retrieve the ING version of the action word. The ING version is a word such
- as running or walking. The ING verb must be used in the response because other
- selections of Jim was runs and Jim was ran are incorrect. The get_verb_ing
- routine reads each record in the dictionary file. It calls the match_verb_ing
- routine to determine whether the record contains the correct ING verb. The
- correct ING verb has an ING restriction. The correct ING verb also has a root
- that matches the action in the input sentence. If match_verb_ing finds the
- correct ING verb, the get_verb_ing routine concatenates it to the response
- giving Jim was running. Finally, the make_answer routine concatenates the
- appropriate place words to the response resulting in Jim was running in the
- house.
-
-
- Expanding the Processor
-
-
- There are several ways this natural language processor can be expanded. The
- simplest would be to add words to the dictionary. Adding words would enable
- more words to be used in the coded, underlying structures. The number of
- generated responses may increase when words are added to the dictionary. More
- words cause more possible word combinations which allow more possible
- generated responses. The additional words, though, will require word
- restrictions that define how the words can go together properly. When
- expanding the processor in this way, expect to add restrictions to the
- dictionary and to enhance the program code that processes the restrictions.
- Another way to expand the processor is to consider the sentence tense.
- Basically, a sentence refers to something in the past, present, or future.
- Sentence tense affects the words used in the sentence as well as its context
- and meaning. In this miniature processor, the auxiliary that helps define the
- tense is ignored. The generated response is even hard-coded with the auxiliary
- was. The first step in this expansion would be to add a tense restriction in
- the dictionary for auxiliary and verb words. For example, the word is would
- have a present-tense restriction and will a future-tense restriction. Next,
- the code would have to be expanded to accommodate the several kinds of
- auxiliary structures. For example, auxiliaries can occur as have, could have,
- or could have been. An overall auxiliary tense would have to be derived from
- these individual auxiliary words. A verb with the appropriate tense can be
- retrieved from the dictionary after the overall auxiliary tense has been
- determined.
- Expanding this natural language processor will provide additional human
- language capabilities. But not all processors require the same capabilities.
- The capabilities that are needed for a given processor depend, in part, on the
- kinds of input sentences and words that are expected. This natural language
- processor, presented in its current form, doesn't read a book or answer
- questions about the book. But expanding this processor will make that
- capability possible.
- (C) 1993 by Russ Suereth.
- Figure 1 A processor session
- Sentence: Jim is running in the house
- Response: Ok
-
- Sentence: Sue is going to a store
- Response: Ok
-
- Sentence: Bill is walking on the street
- Response: Ok
-
- Sentence: Where was Jim running
- Response: Jim was running in the house
-
- Sentence: Where was Bill going
- Response: I don't know
-
- Sentence: Where was Sue going
- Response: Sue was going to a store
- Figure 2 Phrase structures
- Sentence = Noun Phrase + Verb Phrase
- Noun Phrase = Pronoun, or
- Name, or
- Noun, or
- Determiner + Noun
- Verb Phrase = Verb, or
- Verb + Place, or
- Verb + Manner, or
- Verb + Time, or
- Verb + Reason, or
- Auxiliary + Verb, or
- Auxiliary + Verb + Place, or
- Auxiliary + Verb + Manner, or
- Auxiliary + Verb + Time, or
- Auxiliary + Verb + Reason
-
- The following examples are words that may be
- found in the phrase structures:
-
-
- Pronoun = he, she, they, it
- Name = Jim, Florida, Moby Dick
- Common Noun = house, store, street
- Determiner = a, the, that, those
- Verb = run, walk, go, read
- Auxiliary = is, was, have, did, could
- Place = in the house, to a store
- Manner = with ease, quickly
- Time = in the morning, at noon
- Reason = due to the snow, since it rained
-
- Listing 1 NATURAL.C - A natural language processor
- /* copyright 1993 by Russ Suereth */
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <ctype.h>
-
- #define ING 73 /* Restriction for ING word */
-
- void initialize(void);
- void reset_sentence(void);
- void get_record(char *);
- char *extract_word(void);
- int match_record(char *, int);
- char *extract_root(void);
- void check_underlying(void);
- int check_type(char *,int);
- void check_subject(void);
- void check_action(void);
- void check_place(void);
- void make_response(void);
- void make_answer(int);
- void get_verb_ing(void);
- int match_verb_ing(void);
-
- FILE *infile;
- char dic_record[80];
- int sentence;
- int word_ct;
- char word_array[10] [15];
- char root_array[10][15];
- char prime_types [10] [11];
- char phrases [10] [11];
- char type_array[10][5][11];
- char subjects [20] [15];
- char actions[20][15];
- char places[20][31];
- char response[80];
-
- void main()
- {
- char *cur_word;
- char in_sentence[80];
-
- initialize();
- if ((infile = fopen("diction", "r+")) == NULL) {
- printf ("\nError opening dictionary\n");
- exit(0);
-
- }
- printf("\nSentence: ");
-
- while(gets(in_sentence)) {
- if (in_sentence[O] == '\0') break;
- reset_sentence();
-
- cur_word = strtok(in_sentence, " ");
- while(cur_word != NULL) {
-
- get_record(cur_word);
- cur_word = strtok(NULL," ");
- if (++word_ct > 9) break;
- }
-
- check_underlying();
-
- check_subject();
- check_action();
- check_place();
-
- make_response();
- printf("Response: %s\n\nSentence: ", response);
-
- if (++sentence > 19) break;
- } /* end while */
-
- fclose(infile);
- return;
- }
-
- /*****************************************************/
- /* Initialize variables (subjects, actions and */
- /* places arrays contain entries for 20 sentences). */
- /*****************************************************/
- void initialize()
- {
- int i;
- for (i=0; i<20; i++) {
- subjects[i][0] = '\0';
- actions [i][0] = '\0';
- places[i][0] = '\0';
- }
- sentence = 0;
- return;
- }
-
- /****************************************************/
- /* These variables are initialized for each new */
- /* input sentence (each of the 10 word entries for */
- /* the input sentence has 5 type_array entries). */
- /****************************************************/
- void reset_sentence()
- {
- int i,j;
- word_ct = 0;
- for (i=0; i<10; i++) {
- word_array[i] [0] = '\0';
- root_array[i][0] = '\0';
-
- prime_types[i] [0] = '\0';
- phrases[i][0] = '\0';
- for (j=0; j<5; j++)
- type_array[i][j][0] = '\0';
- }
- return;
- }
-
- /****************************************************/
- /* Get all the records from the dictionary. If the */
- /* passed word is not in the dictionary, then the */
- /* word could be a name. */
- /****************************************************/
- void get_record(char *pass_word)
- {
- int types = 0;
- rewind (infile);
- fgets(dic_record, 80, infile);
- while (! feof(infile)) {
- if (match_record(pass_word, types) == 0)
- types++;
- fgets(dic_record, 80, infile);
- }
- if (types == 0) {
- if (isupper( (int) pass_word[0]))
- strcpy(type_array[word_ct][types], "NAME");
- else
- strcpy(type_array[word_ct][types],
- "NOTFOUND");
- }
- strcpy(word_array[word_ct], pass_word);
- return;
- }
-
- /*******************************************************/
- /* Compare the passed word with the word in the */
- /* current dictionary record. If they are the same, */
- /* then extract the type (NOUN, VERB, etc.). If the */
- /* type is a VERB, then also extract the root and */
- /* and copy it to the root array. */
- /*******************************************************/
- int match_record(char *pass_word, int types)
- {
- int i, j;
- char *root;
- char *dic_word;
- dic_word = extract_word();
- /* Check if passed word equals dictionary word */
- if (strcmpi(pass_word, dic_word) != 0) return(1);
-
- /* Word found, get the type */
- for (i=14,j=0; i<20; i++) {
- if (isspace(dic_record[i])) break;
- type_array[word_ct][types][j++] = dic_record[i];
- }
- /* Trim the type */
- type_array [word_ct][types][j] = '\0';
-
- if (strcmp(type_array[word_ct][types],
-
- "VERB") == 0) {
- root = extract_root();
- strcpy(root_array[word_ct], root);
- }
-
- return(0);
- }
-
- /******************************************************/
- /* Extract the word from the dictionary. The word is */
- /* 14 characters in length and starts in column 1. */
- /******************************************************/
- char *extract_word()
- {
- int i, j;
- char dic_word[15];
- for (i=0,j=0; i<14; i++) {
- if (isspace(dic_record[i])) break;
- dic_word[j++] = dic_record[i];
- }
- /* Trim the dictionary word */
- dic_word[j] = '\0';
- return(dic_word);
- }
-
- /******************************************************/
- /* Extract the root from the dictionary. It */
- /* identifies a group of similar words (the root for */
- /* run, ran, runs and running is run). It is 14 */
- /* characters in length and starts in column 35. */
- /******************************************************/
- char *extract_root()
- {
- int i, j;
- char root[15];
- for (i=34,j=0; i<48; i++) {
- if (isspace(dic_record[i])) break;
- root[j++] = dic_record[i];
- }
- /* Trim the root */
- root[j] = '\0';
- return(root);
- }
-
- /******************************************************/
- /* Determine if the input sentence contains a known, */
- /* underlying structure. If it does, then assign the */
- /* correct types and phrases for the words. */
- /******************************************************/
- void check_underlying()
- {
- int i;
-
- /* Structure WH-AUX-NAME-VERB */
- i = 0;
- if ( (check_type("WH", i) == 0) &&
- (check_type("AUX", i+1) == 0) &&
- (check_type("NAME", i+2) == 0) &&
- (check_type("VERB", i+3) == 0) ) {
-
- strcpy(prime_types[i], "WH");
- strcpy(prime_types[i+1], "AUX");
- strcpy(prime_types[i+2], "NAME");
- strcpy(prime_types[i+3], "VERB");
- strcpy(phrases[i], "WHQUESTION");
- strcpy(phrases[i+1], "VERBPHRASE");
- strcpy(phrases[i+2], "NOUNPHRASE");
- strcpy(phrases[i+3], "VERBPHRASE");
- return;
- }
-
- /* Structure NAME-AUX-VERB-PREP-DET-NOUN */
- if ( (check_type("NAME", i) == 0) &&
- (check_type("AUX", i+1) == 0) &&
- (check_type("VERB", i+2) == 0) &&
- (check_type("PREP", i+3) == 0) &&
- (check_type("DET", i+4) == 0) &&
- (check_type("NOUN", i+5) == 0) ) {
- strcpy(prime_types[i], "NAME");
- strcpy(prime_types [i+1], "AUX");
- strcpy(prime_types [i+2], "VERB");
- strcpy(prime_types[i+3], "PREP");
- strcpy(prime_types[i+4], "DET");
- strcpy(prime_types[i+5], "NOUN");
- strcpy(phrases[i], "NOUNPHRASE");
- strcpy(phrases[i+1], "VERBPHRASE");
- strcpy(phrases[i+2], "VERBPHRASE");
- strcpy(phrases[i+3], "PREPPHRASE");
- strcpy(phrases[i+4], "PREPPHRASE");
- strcpy(phrases[i+5], "PREPPHRASE");
- return;
- }
-
- return;
- }
-
- /******************************************************
- /* Compare the passed type with all the types for */
- /* this word in the type_array. If the type is */
- /* found, then return 0. The pass_number parameter */
- /* identifies the word in the input sentence. */
- /*****************************************************/
- int check_type(char *pass_type, int pass_number)
- {
- int i;
- for (i=0; type_array[pass_number][i][0]; i++) {
- if (strcmp(type_array[pass_number][i],
- pass_type) == 0)
- /* Passed type is found in array */
- return(0);
- }
- /* Passed type is not found in array */
- return(1);
- }
-
- /*****************************************************/
- /* If the correct type is "NAME", then the word */
- /* refers to a subject so copy the word to the */
- /* subjects array. */
-
- /*****************************************************/
- void check_subject()
- {
- int i;
- for (i=0; i<word_ct; i++) {
- if (strcmp(prime_types[i], "NAME") == 0) {
- strcpy(subjects[sentence], word_array[i]);
- break;
- }
- }
- return;
- }
-
- /*****************************************************/
- /* If the correct type is "VERB", then the word */
- /* refers to an action so copy the word's root from */
- /* the root array to the actions array. */
- /*****************************************************/
- void check_action()
- {
- int i;
- for (i=0; i<word_ct; i++) {
- if (strcmp(prime_types[i], "VERB") == 0) {
- strcpy(actions[sentence], root_array[i]);
- break;
- }
- }
- return;
- }
-
- /*****************************************************/
- /* If the phrase is a "PREPPHRASE", then all the */
- /* words in the phrase refer to a place. Concatenate */
- /* these words to the places array. */
- /*****************************************************/
- void check_place()
- {
- int i;
- for (i=0; i<word_ct; i++) {
- if (strcmp(phrases[i], "PREPPHRASE") == 0) {
- strcat(places[sentence], " ");
- strcat(places[sentence], word_array[i]);
- }
- }
- return;
- }
-
- /****************************************************/
- /* Determine the kind of response to generate. If */
- /* the input sentence is a where-question and the */
- /* subject and action is found in a previous array */
- /* entry, then the response can state the location */
- /* of where the subject and action occured. */
- /****************************************************/
- void make_response()
- {
- int i;
-
- /* Last input sentence is not a where-question */
-
- if (strcmpi(word_array[0],"where") != 0) {
- strcpy(response, "Ok");
- return;
- }
-
- /* Last input sentence is a where-question */
- for (i=sentence-1; i >= 0; i--]) {
- if ( (strcmp(subjects[i],
- subjects[sentence]) == 0) &&
- (strcmp(actions[i],
- actions[sentence]) == 0) &&
- (strlen(places[i]) != 0) ) {
- make_answer(i);
- return;
- }
- }
-
- /* Not enough information in actions and */
- /* subjects arrays. */
- strcpy(response, "I don't know");
- return;
- }
-
- /****************************************************/
- /* Generate a response that states the location of */
- /* where the subject and action occured. */
- /****************************************************/
- void make_answer(int prev_sentence)
- {
- strcpy(response, subjects[prev_sentence]);
- strcat(response, " ");
- strcat(response, "was "};
- get_verb_ing();
- strcat(response, places[prev_sentence]);
- return;
- }
-
- /****************************************************/
- /* Retrieve the ING version of the word from the */
- /* dictionary (the ING version of run is running). */
- /****************************************************/
- void get_verb_ing()
- {
- rewind (infile);
- fgets(dic_record, 80, infile);
- while (! feof(infile)) {
- if (match_verb_ing() == 0) break;
- fgets(dic_record, 80, infile);
- }
- return;
- }
-
- /******************************************************/
- /* If the root in the current dictionary record */
- /* matches the root in the actions array, and the */
- /* current dictionary record has an ING restriction, */
- /* then extract the dictionary word and return O. */
- /******************************************************/
- int match_verb_ing()
-
- {
- int i;
- char *root;
- char *dic_word;
-
- root = extract_root();
- if (strcmp(actions[sentence],root) == 0) {
- /* Root found, look for ING restriction */
- for (i=24; i<33; i++) {
- if (isspace(dic_record[i])) break;
- if (dic_record[i] == ING) {
- dic_word = extract_word();
- strcat(response, dic_word);
- return(0);
- }
- }
- }
- return(1);
- }
-
- /* End of File */
-
-
- Listing 2 DICTION -- the dictionary file
- /* copyright 1993 by Russ Suereth */
- a DET
- the DET
- house NOUN
- street NOUN
- store NOUN
- jump NOUN
- go VERB go
- goes VERB go
- going VERB I go
- went VERB go
- run VERB run
- runs VERB run
- running VERB I run
- ran VERB run
- walk VERB walk
- walks VERB walk
- walking VERB I walk
- walked VERB walk
- jump VERB jump
- jumps VERB jump
- jumping VERB I jump
- jumped VERB jump
- is AUX
- was AUX
- to PREP
- in PREP
- on PREP
- where WH
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Mixed Numbers in C
-
-
- P.J. LaBrocca
-
-
- P.J. LaBrocca is the author of ReCalc(TM), a set of rational expression
- calculators that never give answers (well, almost never), and run identically
- on PCs, Macintoshes, and Apples. He has a BS and MA in Chemistry and teaches
- computer science at Peter Rouget Middle School 88 in Brooklyn, NY 13782, (607)
- 746-7175.
-
-
-
-
- Introduction
-
-
- This article describes a method of representing and manipulating mixed numbers
- in C. A simple mixed number calculator, MixCalc, exercises the functions. You
- can use MixCalc to help you do your kids' fractions homework.
- The first part presents the package for working with mixed numbers in a C
- program. It describes mixed_t, the data structure used to represent mixed
- numbers, and the functions that do calculations with them. The discussion then
- turns to MixCalc, a rational number calculator (Listing 1 and Listing 2).
- MixCalc accepts infix expressions involving any combination of whole numbers,
- fractions and mixed numbers and returns an answer as either a whole number, a
- fraction, or a mixed number. For example, given this input
- 3 23 / ( 12 + 2 )
- MixCalc returns
- 1 715
- A vertical bar, , is used as the fraction bar to differentiate it from the
- division sign.
- I based the code for MixCalc's parser (Listing 3) on the recursive-descent
- parser from Bjarne Stroustrup's The C++ Programming Language. I modified it to
- work with mixed_ts and added some error recovery.
- A rational number is any number that can be expressed as the quotient of two
- whole numbers. Integers are rational numbers. A proper fraction's value is
- less than one. An improper fraction's value is equal to or greater than one. A
- mixed number is the sum of an integer and a proper fraction. Since a mixed
- number can be expressed as an improper fraction it is a rational number. I use
- the term mixed number loosely to refer to any rational number.
- Most programming languages do not provide built-in support for fractions or
- mixed numbers. You approximate mixed numbers as decimal numbers using
- floating-point types. For most applications this is fine. However, mixed
- numbers have one big advantage over decimal numbers--they are always exact.
- Using decimal numbers, 1 divided by 3 results in the approximation 0.33333...;
- the mixed number result is exactly 13. Of course, there is a down side. In
- certain calculations the integers used for the numerator and denominator can
- overflow. However, in an application using, say, the English measuring system,
- five-eighths looks a lot better as 58 than as 0.625.
-
-
- Mixed Number Package
-
-
- The data structures and functions for mixed numbers are in Listing 4 - Listing
- 8, fraction.c, mixed.h, error.c, mix_num2.c, and primes.c.
- Since I wanted the intermediate results of calculations available for display
- (for an application not discussed here) I provided storage for each possible
- component of a mixed number. Integer types are used for the whole number part,
- the numerator part, the denominator part and the sign. Fractions must be
- factored into primes in order to reduce them. The factors of the numerator and
- denominator are stored in a two-dimensional array of integers. Including the
- arrays rather than pointers to dynamically-allocated storage simplifies the
- code at the expense of stack size. At compile time, be sure to provide a
- generous stack.
- Include mixed.h in each file that uses the functions, and call init_primes
- once before doing any calculations. init_primes sets up a table of prime
- numbers using the sieve of Eratosthenes. Declare mixed numbers using mixed_t.
- It's a good idea to initialize mixed_ts as soon as possible. You assign values
- by calling mix_init or mix_clear. mix_init accepts a pointer to a mixed_t, and
- three integer values for the whole number, numerator, and denominator parts of
- the mixed number. The sign of the mixed_t is determined by the first integer;
- the others should be positive. mix_clear sets the mixed number to zero. Both
- functions truncate the arrays holding the prime factors of the numerators and
- denominators.
- The typedef Integer controls the size of the int used to store the parts of
- mixed_ts. Use any size as long as it's signed. Naturally, larger ints make
- overflow less likely, but take more room and time. The output function
- mix_print, and any input functions you supply, need adjustment if you change
- the size of Integer. The sign of a mixed_t is stored in an int as +1 or -1. It
- is used in intermediate calculations.
- Notice that a mixed_t with value zero has its whole number and numerator parts
- set to zero, but its denominator part is set to one. A denominator is also a
- divisor, and division by zero is meaningless. Also note that a fraction that
- has not yet been factored has the first elements of its factors array set to
- one.
- The functions for manipulating mixed_ts are in fraction.c. They are used by
- the functions that perform arithmetic operations on mixed numbers, and for
- converting mixed numbers to equivalent forms.
- To reduce fractions to lowest terms you need to know the prime factors of the
- numerator and denominator. These are provided by a call to mix_factor.
- mix_factor takes the address of a mixed number and puts its prime factors into
- the factors array. The whole number part is not considered. mix_factor is
- called by mix_reduce, which returns a pointer to a mixed number with the
- fraction part in lowest terms. mix_reduce loops through the two arrays of
- factors comparing them element by element, and either ignoring them or
- multiplying them into temporary variables. For example, consider reducing the
- fraction 4290. After the call to mix_factor the arrays look like this:
- Numerator 2 3 7 1
- Denominator 2 3 3 5 1
- The pointers top and bottom point to the prime factors under consideration,
- one from the numerator, the other from the denominator. If the elements are
- equal, the factors are common. Advancing the pointers cancels the common
- factors. In the example, the two's, and then the three's are ignored. When the
- factors are unequal the smaller is multiplied into its temporary variable and
- its pointer is advanced. This continues until the sentinel, i.e., an element
- equal to one, is encountered in one or both arrays. Any remaining factors are
- multiplied into the corresponding temporary variable. The rest of the function
- adjusts the whole number part, if needed, and makes sure the mixed number is
- in a consistent state if it started out equal to zero.
- An improper fraction is one in which the numerator is greater than or equal to
- the denominator. In the case of the mixed numbers developed here this means
- that the whole number part must be zero. mix_make_improper converts a mixed_t
- into an improper fraction. This function is called by mix_add and mix_sub. You
- can it call before mix_print to display a mixed_t as an improper fraction.
- mix_print displays a mixed number on standard output in a reasonable form
- within the limits of a text-based environment. Some details are presented
- later.
- mix_num2.c contains the functions for doing arithmetic with mixed numbers. The
- four basic operations are provided, plus negation and calculating the lowest
- common denominator.
- Addition and subtraction are accomplished by converting the mixed numbers to
- improper fractions, bringing them to a common denominator and adding the
- numerators. For multiplication the numerators are multiplied, then the
- denominators. The division function takes the reciprocal of the second mixed
- number and calls the multiplication function. The possibility of taking the
- reciprocal of zero presents a complication. As it stands, mix_recip complains
- if the denominator will become zero. Otherwise it returns the reciprocal.
- mix_recip does not alter its argument. Your program should check if a mixed
- number is zero before dividing by it. The arithmetic functions return
- unreduced results.
- mix_neg reverses the sign of a mixed_t. mix_lcd returns the lowest common
- denominator of two mixed_ts.
- If during calculations the numerator becomes zero, the denominator gets set to
- one. If the value of a mixed number becomes zero the sign is set to positive.
- These actions ensure a consistent starting point for further calculations.
- A greatest_common_divisor function could be used to "pre-reduce" fractions
- during calculations. However, taking out gcds does not guarantee lowest terms.
- To ensure lowest terms mix_reduce still has to be called. There might be some
- practical advantage, say, when adding up a long list of fractions if you avoid
- overflow. However, 61% of the time random number pairs have a gcd equal to
- one. Most of the time calling a gcd function adds overhead but little else.
- Note that mix_add and mix_sub alter the representation, but not the value, of
- their arguments. To preserve the original representation copy it to another
- mixed_t.
-
-
- Limits
-
-
- Storing numbers in computer memory has limitations. Range and domain errors
- are reported for many calculations involving floating-point types. For integer
- types errors may or may not be reported. If you are lucky bizarre results let
- you know something is amiss. When working with mixed_ts here are some limits
- to keep in mind.
- Whole numbers, numerators and denominators are stored in longs. If the size of
- a calculation's result doesn't fit in a long the result is wrong. You might
- get a run-time error or garbage. The functions do not take any precautions.
- The largest prime number in the prime number table is 15991. Numbers with
- prime factors bigger than that produce all kinds of annoying errors. Some
- examples are given later.
- The factors array holds 40 prime factors, a generous amount. If more need to
- be stored other memory gets over-written with the usual unpredictable results.
- Actually you might consider reducing the size.
- The mixed_ts in this version are big so stack overflow is likely. Increase the
- amount of stack space allocated at compile and link time as needed.
-
-
-
- Scanning a Mixed Number
-
-
- MixCalc's parser is based on the one presented in Stroustrup's The C++
- Programming Language. I discuss only those parts I modified for MixCalc.
- The main alteration was to the function than scans the input. For MixCalc the
- scanning function, gettok (Listing 9), has to recognize mixed numbers in their
- varying guises. gettok reads the standard input, breaks it up into tokens,
- returns the tokens to the parser and constructs a mixed_t. If it detects an
- unrecognized token it issues a diagnostic to standard error and resets MixCalc
- via a call to longjmp.
- The setjmp/longjmp combination provides a good method of error recovery for a
- calculator program where an error in the input stream makes the rest of the
- expression meaningless. A variable of type jmp_buf is defined and a call is
- made to setjmp before entering the main loop. If longjmp is called later on
- the environment is reset to the way it was when setjmp was called. An error in
- an expression causes the rest of the current line to be discarded. MixCalc
- resumes on the next line.
- Scanning a mixed number presents a problem similar to scanning a
- floating-point number. Valid input can come in several forms. An acceptable
- mixed number may consist of a whole number, a fraction, or a whole number
- followed by a fraction. An optional sign may precede any of these. An irksome
- part of scanning a mixed number is that it is natural to have space embedded
- in it. gettok tackles these problems.
- Positive and negative signs are treated as operators, not as part of the mixed
- number. They are passed to the parser.
- When gettok encounters a digit it pulls in an integer using scanf. Then white
- space is skipped. The next character can be a digit, a fraction bar, or some
- other character (an operator or an unknown character). If the next character
- is a digit then the scanner expects an integer, optional white space, a
- fraction bar, optional white space, and another integer. Otherwise an error
- message is displayed and MixCalc resets itself. If the next character is a
- fraction bar then an integer preceded by optional white space must follow.
- Otherwise an error as above is generated. For each of the three possible
- successful scans gettok constructs a mixed_t and returns the token NUMBER.
- Note that the fraction bar is not an operator in MixCalc, rather it is a
- delimiter.
-
-
- Compiling MixCalc
-
-
- MixCalc compiles as presented here with Microsoft C 6.00 or C/C++ 7.0, and
- Zortech C/C++ 3.0. A makefile (Listing 10) is provided for building MixCalc
- using Microsoft and Zortech. It's set to use the Microsoft compilers as
- shipped. To switch to Zortech move the # on the lines referring to the CC
- macro and the linker. The file works with Microsoft's NMAKE and Zortech's MAKE
- utilities. I also use this makefile to backup source files and update
- hardcopy. For example, make backup copies those files that changed since the
- last backup to a floppy on drive a:. To use backup and hcopy with Zortech's
- MAKE add exclamation points before the commands.
- For Microsoft's PWB create a project called mixcalc. Add the seven C files to
- the project. Then choose build from the project menu. The PWB does not use the
- makefile provided with MixCalc.
- I like to compile everything with the large model, but any model should work.
- For more information on using MixCalc see the sidebar "How to Use MixCalc."
- How to Use MixCalc
- MixCalc is a rational number calculator. It accepts expressions consisting of
- any combination of whole numbers, fractions, and mixed numbers and returns a
- result in the appropriate reduced form. +,-, * and / perform addition,
- subtraction, multiplication, and division, respectively. Parentheses change
- the order of operations and nest arbitrarily deep. Several expressions,
- separated by semicolons, may appear on one line. A carriage return ends a
- line. MixCalc accepts input from the command line or from a redirected file.
- An error in the input flushes the current line and resets MixCalc. Start
- MixCalc by typing mixcalc at the DOS prompt. To do a series of calculations
- from a text file called examples, type
- mixcalc < examples
- To save the results of your calculations to a file called results, type
- mixcalc < examples > results
- An error causes a message to be displayed and halts the redirected session.
- Several lines of a MixCalc session with some comments follow.
- White space is optional except to separate the whole number part from the
- fraction part of a mixed number.
- > 12*12
- 14
- >12 * 12
- 14
- > 2 23+1 56
- 4 12
- Parentheses work just as expected.
- > 5 79 + 11 23 * 3 12
- 46 1118
- > (5 79 + 11 23) * 3 2
- 61 118
- The fraction bar is not an operator; it has meaning only within a fraction.
- > (1/2)3
- Unknown token:
- > 1/23
- 1 12
- This is acceptable to MixCalc, but might not be what was intended. The above
- example is "one divided by two-thirds," NOT "one divided by two, divided by
- 3." One-half over three equals one-sixth:
- > (1/2)/3
- 16
- End a MixCalc session by hitting control-Z (or control-C). All of the limits
- of the mixed number package apply to MixCalc. Numbers near the largest prime
- available to MixCalc are used in some examples to force errors. Here's how two
- compilers dealt with them.
-
-
- Microsoft C/C++ 7.0
-
-
- > 115991 + 115991
- 215991
- > 116000 + 115991
- - integer divide by 0
- > 116001 + 115991
- - integer divide by 0
-
-
- Zortech C/C++ 3.0
-
-
-
- > 115991 + 115991
- 215991
- > 116000 + 115991
- 1
- > 116001 + 115991
- 721426102 1879503890-2113923584
- In the first example everything stays within bounds. The numerator becomes
- 2x15,991 and the denominator 1,5991x15,991. Both fit in longs and have prime
- factors less than or equal to 15,991. The second example produces the
- numerator 31,991, which is a prime bigger than the biggest available to
- MixCalc. Note the denominator, 255,856,000, fits in a long and factors nicely.
-
- Listing 1 mixcalc.h - header for MixCalc
- /* mixcalc.h */
- /* Adapted from The C++ Programming Language by Bjarne
- Stroustrup Modified by P.J. LaBrocca */
-
- #ifndef MIXCALC_H
- #define MIXCALC_H
-
- #include "mixed.h"
- #include <setjmp.h>
-
- enum token_value {
- NAME = 1, NUMBER, END, ENDFILE = END,
- PLUS = '+', MINUS = '-',MUL = '*', DIV = '/',
- PRINT = ';',ASSIGN = '=', LP = '(', RP = ')'
- };
- extern enum token_value curr_tok;
-
- extern mixed_t *M;
-
- //extern mixed_t number_value; //parser.c
-
- extern jmp_buf startup;
-
- //function prototypes
- mixed_t expr(void);
- mixed_t term(void);
- mixed_t prim(void);
-
- enum token_value get_token(void);
-
- #endif
- /* End of File */
-
-
- Listing 2 mixcalc.c - a rational number calculator
- /* mixcalc.c */
- /* Copyright 1992 by P.J. LaBrocca */
-
- #include "mixed.h"
- #include "mixcalc.h"
- #include <stdio.h>
-
- #ifdef __ZTC__ /* For Zortech Compiler */
- _stack = 15000;
- #endif
-
- enum token_value curr_tok;
-
-
- void main()
- {
- mixed_t ans;
- mixed_t work;
-
- init_primes();
-
- mix_clear( &ans );
- mix_init( &work, 3, 1, 7 );
-
- setjmp( startup );
- M = &work;
- while(1) {
- printf("> ");
- curr_tok = gettok();
- if(curr_tok == END) {
- break;
- }
- if(curr_tok == PRINT)
- continue;
- ans = expr( );
-
- mix_print( mix_reduce( &ans) );
- }
- }
-
- /* End of File */
-
-
- Listing 3 parser.c - parser For mixcalc.c
- /* parser.c */
- /* Adapted from The C++ Programming Language by Bjarne Stroustrup
- Modified by P.J. LaBrocca
- */
-
- #include "mixed.h"
- #include "mixcalc.h"
- #include <stdio.h>
-
- mixed_t number_value;
-
- mixed_t expr(void)
- {
- mixed_t left = term();
- mixed_t tmp;
-
- for(;;)
- switch(curr_tok) {
- case PLUS:
- curr_tok = gettok();
- tmp = term();
- left = mix_add( &left, &tmp );
- break;
- case MINUS:
- curr_tok = gettok();
- tmp = term();
- left = mix_sub( &left, &tmp );
- break;
- default:
-
- return left;
- }
- }
-
- mixed_t term()
- {
- mixed_t d;
- mixed_t left = prim();
-
- for(;;)
- switch(curr_tok) {
- case MUL:
- curr_tok = gettok();
- d = prim();
- left = mix_mul( &left, &d );
- break;
- case DIV:
- curr_tok = gettok();
- d = prim();
- if( (d.whole == 0) && (d.num == 0) ) {
- fprintf( stderr, "Division by 0\n");
- fflush( stdin );
- longjmp( startup, 1 );
- }
- left = mix_divide( &left, &d );
- break;
- default:
- return left;
- }
- }
-
- mixed_t prim()
- {
- struct name *n;
- mixed_t e;
-
- switch(curr_tok) {
- case NUMBER:
- number_value = *M;
- curr_tok = gettok();
- return number_value;
-
- case MINUS:
- curr_tok = gettok();
- e = prim();
- mix_neg( &e );
- return e;
- case PLUS:
- curr_tok = gettok();
- return prim();
-
- case LP:
- curr_tok = gettok();
- e = expr();
- if(curr_tok != RP) {
- fprintf( stderr, "')' expected\n");
- fflush( stdin );
- longjmp( startup, 1 );
- }
-
- curr_tok = gettok();
- return e;
- case END:
- return * mix_init( &e, 1, 0, 1 );
- default:
- fprintf( stderr, "primary expected, found %c\n", curr_tok);
- fflush( stdin );
- longjmp( startup, 1 );
- }
- }
-
- /* End of File */
-
-
- Listing 4 fraction.c - functions for manipulating mixed_ts
- /* fraction.c */
- /* Copyright 1992 by P.J. LaBrocca */
-
- #include <stdio.h>
- #include "mixed.h"
-
- mixed_t *mix_init( mixed_t *m, Integer w, Integer n, Integer d )
- {
- m->sign = POSITIVE;
- m->whole = w;
- if( w < 0) {
- m->sign = NEGATIVE;
- m->whole *= -1;
- }
- m->num = n;
- m->den = d;
- m->factors[ NUMER ][ 0 ] = 1;
- m->factors[ DENOM ][ 0 ] = 1;
- return m;
- }
-
- mixed_t *mix_clear( mixed_t *m )
- {
- m->whole = 0;
- m->num = 0;
- m->den = 1;
- m->factors [ NUMER ][ 0 ] = 1;
- m->factors [ DENOM ][ 0 ] = 1;
- m->sign = POSITIVE;
- return m;
- }
-
- mixed_t *mix_factor( mixed_t *m )
- {
- Integer n;
- int i;
- Integer *pi;
- Integer *pp;
-
- for( i = 0; i < 2; ++i) {
- pp = Primes; /* point to global array of primes */
- (i != 0) ? (n = m->den) : (n = m->num);
- pi = &m->factors[i][0];
-
-
- while(n > 1) {
- if( !(n % *pp) ) { /* if there is no remainder */
- n = (Integer) (n / *pp); /* factor the prime out of number */
- *pi = *pp; /* save the prime */
- ++pi;
- continue; /* try the prime again */
- }
- ++pp; /* next prime */
- }
- *pi = 1;
- pp = Primes;
- }
- return m;
- }
-
- mixed_t *mix_reduce( mixed_t *m )
- {
- Integer tnum = 1, tden = 1;
- Integer *top = &m->factors[NUMER][0];
- Integer *bot = &m->factors[DENOM][0];
-
- if( m->num == 0) {
- return m;
- }
- if( m->den == 1 ) {
- m->whole += m->num;
- m->num = 0;
- return m;
- }
- mix_factor( m ); /* got to factor to reduce */
- /*accumulators for reduced numerator & denominator*/
- while(*top != 1 && *bot != 1) { /* neither factor is sentinel */
- if(*top == *bot) { /* if the current factors are equal..*/
- ++top; /* ..cancel them & continue */
- ++bot;
- continue;
- } /* otherwise accumulate the smaller*/
- (*top < *bot) ? (tnum *= *top++) : (tden *= *bot++);
-
- }
- while(*top != 1) /* any remaining factors are */
- tnum *= *top++; /* multiplied in */
- while(*bot != 1)
- tden *= *bot++;
- if(tnum == tden) { /*ie, n/d == 1*/
- ++m->whole; /*add 1 to whole*/
- m->num = 0;
- m->den = 1;
- }
- else if(tnum > tden) { /*improper fraction*/
- m->whole += (Integer) (tnum / tden);
- m->num = tnum % tden;
- m->den = tden;
- }
- else { /*proper fraction*/
- m->num == tnum;
- m->den = tden;
- }
- if(m->num == 0) { /* keep zero-valued fractions*/
-
- m->den = 1; /* in consistent state*/
- if(m->whole == 0)
- mix_clear( m );
- }
- return m;
- }
-
- void mix_make_improper( mixed_t *m ) /* converts invoking instance*/
- { /* into an improper fraction*/
- m->num += m->whole * m->den; /* if possible*/
- m->whole = 0;
- }
-
- /* If sizeof( Integer ) changes
- change %ld
- */
- void mix_print( mixed_t *m )
- {
- printf("\t");
- if( m->sign == -1 )
- printf("-");
- if( m->whole != 0 )
- printf("%ld", m->whole);
- if( m->num != 0 )
- printf(" %ld%ld",m->num, m->den);
- if( (m->whole == 0) && (m->num == 0) )
- printf("0");
- printf("\n");
- }
-
- /* End of File */
-
-
- Listing 5 mixed.h
- /* mixed.h */
- /* Copyright 1992 by P.J. LaBrocca */
-
- #ifndef MIXED_H
- #define MIXED_H
-
- typedef long Integer;
-
- #define MAXFACTOR 40 /* maximum number of factors */
- #define NUMER 0 /* index for numerator */
- #define DENOM 1 /* index for denominator */
- #define POSITIVE 1
- #define NEGATIVE -1
-
- typedef struct {
- Integer whole;
- Integer num;
- Integer den;
- int sign;
- Integer factors[2][MAXFACTOR];
- } mixed_t;
-
- extern Integer Primes[]; /* space for prime numbers */
-
- mixed_t mix_error(char *s);
-
-
- void init_primes( void );
-
- mixed_t *mix_init( mixed_t *m, Integer w, Integer n, Integer d );
- mixed_t *mix_clear( mixed_t *m );
- mixed_t *mix_factor( mixed_t *m );
- mixed_t *mix_reduce( mixed_t *m );
- void mix_make_improper( mixed_t *m );
- void mix_print( mixed_t *m );
-
- mixed_t mix_sub(mixed_t *x, mixed_t *y);
- mixed_t mix_add(mixed_t *x, mixed_t *y);
- mixed_t mix_mul(mixed_t *x, mixed_t *y);
- mixed_t mix_recip(mixed_t *f); -
- mixed_t mix_divide(mixed_t *f, mixed_t *g);
- Integer lcd(mixed_t *f, mixed_t *g);
- void mix_neg(mixed_t *f);
-
- #endif
-
- /* End of File */
-
-
- Listing 6 error.c
- /* error.c */
- /* Copyright 1992 by P.J. LaBrocca */
-
- #include "mixed.h"
- #include <stdio.h>
-
- mixed_t mix_error(char *s)
- {
- mixed_t t;
-
- fprintf(stderr, "Error: %s\n", s);
- return *mix_init( &t, 1, 0, 1 );
- }
-
- /* End of File */
-
-
- Listing 7 mix_num2.c -- functions for doing arithmetic with mixed numbers
- /* mix_num2.c */
- /* Copyright 1992 by P.J. LaBrocca */
-
- #include "mixed.h"
- #include <stdlib.h>
-
- mixed_t mix_sub(mixed_t *x, mixed_t *y)
- {
- mixed_t sum, xt, yt;
-
- mix_clear( &sum );
- mix_make_improper(x);
- mix_make_improper(y);
-
- xt.num = x->num * y->den;
- xt.den = x->den * y->den;
- yt.num = y->num * x->den;
-
- yt.den = y->den * x->den;
-
- sum.num = x->sign * xt.num - y->sign * yt.num;
-
- if(sum.num < 0) {
- sum.num = labs(sum.num);
- sum.sign = NEGATIVE;
- }
- sum.den = xt.den; /*xt.den == yt.den at this point*/
- if(sum.num == 0) {
- sum.den = 1;
- if(sum.whole == 0)
- sum.sign = POSITIVE;
- }
- return sum;
- }
-
- mixed_t mix_add(mixed_t *x, mixed_t *y)
- {
- mixed_t sum, t, xt, yt;
-
- mix_clear( &sum );
-
- mix_make_improper(x);
- mix_make_improper(y);
-
- xt.num = x->num * y->den;
- xt.den = x->den * y->den;
- yt.num = y->num * x->den;
- yt.den = y->den * x->den;
-
- sum.num = x->sign * xt.num + y->sign * yt.num;
-
- if(sum.num < 0) {
- sum.num = labs(sum.num);
- sum.sign = NEGATIVE;
- }
- sum.den = xt.den; /*xt.den == yt.den at this point*/
-
- if(sum.num == 0) {
- sum.den = 1;
- if(sum.whole == 0)
- sum.sign = POSITIVE;
- }
- return sum;
- }
-
- mixed_t mix_mul(mixed_t *x, mixed_t *y)
- {
- mixed_t product;
- Integer xn, yn;
-
- mix_clear( &product );
-
- xn = x->sign * (x->whole * x->den + x->num);
- yn = y->sign * (y->whole * y->den + y->num);
-
- product.num = xn * yn;
- product.den = x->den * y->den;
-
- if(product.num < 0) {
- product.num = labs(product.num);
- product.sign = NEGATIVE;
- }
- if(product.num == 0) {
- product.den = 1;
- if(product.whole == 0)
- product.sign = POSITIVE;
- }
- return product;
- }
-
- mixed_t mix_recip(mixed_t f) /*reciprocal*/
- { /* does not alter f*/
- Integer tmp;
-
- mix_make_improper( &f );
- if(f.num == 0) {
- mix_error("denominator will become zero");
- return f;
- }
-
- tmp= f.num;
- f.num = f.den;
- f.den = tmp;
- return f;
- }
-
- mixed_t mix_divide(mixed_t *f, mixed_t *g)
- {
- mixed_t rec = mix_recip( *g );
-
- return mix_mul( f, &rec );
- }
-
- Integer mix_lcd(mixed_t *f, mixed_t *g)
- {
- int i = 0, j = 0;
- Integer low[30];
- Integer *l = low;
- Integer t;
-
- mix_factor(g);
- mix_factor(f);
- white(1) {
- if(f->factors[1][i] == 1) {
- while(g->factors[1][j] != 1)
- *l++ = g->factors[1][j]++];
- break;
- }
- else if(g->factors[1][j] == 1) {
- while(f->factors[1][i] != 1)
- *l++ = f->factors[1][i++];
- break;
- }
- else if(f->factors[1][i] == g->factors[1][j]) {
- *l++ = f->factors[1][i];
- ++i;
- ++j;
-
- }
- else if(f->factors[1][i] > g->factors[1][j]) {
- *l++ = g->factors[1][j];
- ++j;
- }
- else if(f->factors[1][i] < g->factors[1][j]) {
- *1++ = f->factors[1][i];
- ++i;
- }
- }
- *l = 1;
- t = 1;
- i = 0;
-
- while(low[i] !=1)
- t *= low[i++];
- Return t;
- }
-
- void mix_neg(mixed_t *f)
- {
- if(f->sign == NEGATIVE)
- f->sign = POSITIVE;
- else
- f->sign = NEGATIVE;
- }
- /* End of File */
-
-
- Listing 8 primes.c -- function to set up table of prime numbers
- /* primes.c */
- /* Copyright 1992 by P.J. LaBrocca */
-
- #include <stdlib.h>
- #include "mixed.h"
-
- #define NUM 16000 /* highest number to check for being prime */
-
- Integer Primes[ 2000 ];
-
- void init_primes( void )
- {
- char *mark = malloc(NUM * sizeof(char) );
- Integer *pr = Primes; /* point to global array */
- Integer j, k;
-
- for(j = 0; j < NUM; ++j)
- mark[j] = 1; /* mark everything prime */
- for(j = 4; j < NUM; j += 2)
- mark[j] = 0; /* scratch off all the even numbers ... */
- *pr++ = 2; /* ... except for 2; put it in primes array */
- for(j = 3; j < NUM; j += 2) /* check each odd number: */
- if(mark[j]) { /* if it's marked... */
- *pr++ = j; /* ..record it in array.. */
- for(k = j + j; k < NUM; k += j)
- mark[k] = 0; /* ..and scratch off all its multiples */
- }
- free( mark );
- }
-
- #undef NUM
- /* End of File */
-
-
- Listing 9 gettok.c - scanning function that must recognize mixed numbers in
- various forms
- /* gettok.c */
- /* Copyright 1992 by P.J. LaBrocca */
-
- #include <stdio.h>
- #include <ctype.h>
- #include "mixed.h"
- #include "mixcalc.h"
-
- #define LookAhead(x) while( (x = getchar()) == '' x == '\t')
-
- jmp_buf startup;
-
- mixed_t *M;
- enum token_value CurrentToken;
-
- enum token_value gettok( void ) {
- int c;
- Integer tmp1, tmp2, tmp3;
-
- LookAhead( c );
- if( c == EOF )
- return CurrentToken = ENDFILE;
- if( isdigit(c) ) {
- ungetc( c, stdin );
- scanf("%ld", &tmp1);
- LookAhead(c);
- if( isdigit(c) ) {
- ungetc( c, stdin );
- scanf("%ld", &tmp2);
- LookAhead(c);
- if(c != '') {
- fprintf( stderr, "Expected ''\n");
- fflush( stdin );
- longjmp( startup, 1 );
- }
- LookAhead(c);
- if( !isdigit(c) ) {
- fprintf( stderr, "Expected a digit\n");
- fflush( stdin );
- longjmp( startup, 1 );
- }
- ungetc( c, stdin );
- scanf("%ld", &tmp3 );
- if( tmp3 == 0 ) {
- fprintf( stderr, "Denominator cannot be zero\n");
- fflush( stdin );
- longjmp( startup, 1 );
- }
- mix_init( M, tmp1, tmp2, tmp3 );
- return CurrentToken = NUMBER;
- }
- if( c == ''){
- LookAhead(c);
- if( !isdigit(c) ) {
-
- fprintf( stderr, "Expected a digit\n");
- fflush( stdin );
- longjmp( startup, 1 );
- }
- ungetc( c, stdin );
- scanf("%ld", &tmp2 );
- if( tmp2 == 0 ) {
- fprintf( stderr, "Denominator cannot be zero\n");
- fflush( stdin );
- longjmp( startup, 1 );
- }
- mix_init( M, 0, tmp1, tmp2 );
- return CurrentToken = NUMBER;
- }
- ungetc( c, stdin );
- mix_init( M, tmp1, 0, 1, );
-
- return CurrentToken = NUMBER;
- }
- if( c == '\n' c == ';')
- return CurrentToken = PRINT;
- switch ( c ) {
- case '*':
- case '+':
- case '-':
- case '/':
- case '(':
- case ')':
- return CurrentToken = c;
- default:
- fprintf( stderr, "Unknown token: %c\n", c);
- fflush( stdin);
- longjmp( startup, 1 );
- }
- }
-
- /* End of File */
-
-
- Listing 10 A makefile
- #makefile for mixcalc.exe
- #CC =ztc -c -ml
- CC = cl /c /AL
-
- .c.obj:
- $(CC) $*.c
-
- all :mixcal.exe
-
- mixcalc.exe :mixcalc.obj fraction.obj parser.obj mix_num2.obj primes.obj
- gettok.obj error.obj
- # blink mixcalc+fraction+parser+mix_num2+primes+gettok+error,,, /NOI
- link mixcalc+fraction+parser+mix_num2+primes+gettok+error,,, /NOI /ST:15000 ;
-
- mixcalc.obj : mixed.h mixcalc.h mixcalc.c
- fraction.obj : mixed.h fraction.c
- mix_num2.obj : mixed.h mix.num2.c
- primes.obj : mixed.h primes.c
- error.obj : mixed.h error.c
- gettok.obj : mixed.h mixcalc.h gettok.c
-
- parser.obj : mixed.h mixcalc.h parser.c
-
- backup: backup1 backup2 backup3 backup4
-
- backup1: mixcalc.c fraction.c gettok.c parser.c
- cp -m $? a:\mixb
- touch backup1
-
- backup2: mix_num2.c primes.c error.c
- cp -m $? a:\mixb
- touch backup2
-
- backup3: makefile mixed.h mixcalc.h #mixctxt
- cp -m $? a:/mixb
- touch backup3
-
- backup4:
- cp -m mixcalc.exe a:\mixb
- touch backup4
-
- hcopy: hcopy1 hcopy2
-
- hcopy1: mixcalc.c fraction.c gettok.c parser.c mixcalc.h
- pr -W -e4 -o5 $? > prn
- touch hcopy1
-
- hcopy2: mix_num2.c primes.c error.c makefile mixed.h #mixctxt
- pr -W -e4 -o5 $? > prn
- touch hcopy2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- Formal Changes to C
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of The C Users Journal. He is convenor of the
- ISO C standards committee, WG14, and active on the C++ committee, WG21. His
- latest books are The Standard C Library, published by Prentice-Hall, and ANSI
- and ISO Standard C (with Jim Brodie), published by Microsoft Press. You can
- reach him at pjp@plauger.com.
-
-
-
-
- Current Status
-
-
- The last meeting of the C standards committees, ISO JTC1/SC22/WG14 and
- ANSI-authorized X3J11, occurred jointly last December near Dulles Airport. I
- left that meeting with a warm sense of accomplishment and a humongous amount
- of homework. Both were a direct result of having the meeting go the way I'd
- hoped, for a change. In soap operas, senators and board chairpeople always
- finagle their political goals against all odds. In the real world, we lowly
- Convenors of standards committees mostly go with the flow.
- I summarized the administrative highlights in my "Editor's Forum" last issue.
- (That was in CUJ March 1993, but see also the "Editor's Forum" for January
- 1993 and for November 1992.) Here again is a brief synopsis of what happened:
- WG14 finally voted out an amendment to the C Standard. We were charged several
- years ago by SC22, our parent committee, to produce such a "normative
- addendum" to correct several perceived flaws in the coverage and expression of
- the C Standard. With a bit of eleventh-hour compromising, we finally got
- agreement within WG14. Now the amendment must survive at least two rounds of
- balloting within SC22 before it becomes formal.
- SC22 finally established sensible procedures for interpreting and correcting
- ISO programming language standards. Various authorized agencies issue Defect
- Reports to the Convenor (me again). She/he (I) must log them, acknowledge
- them, and submit them to the Working Group to develop a response. A Technical
- Corrigendum patches the standard, while a Record of Response simply explains
- the standard. (I helped develop these procedures at the SC22 plenary in
- Finland last August.)
- ANSI has adopted the ISO C Standard verbatim as its own, replacing the
- original C Standard, with slightly different formatting. That was a prelude to
- having X3J11 start an I-Project to track development of the normative
- addendum. The English of all this is that responsibility for maintaining the C
- Standard has passed from ANSI to ISO.
- X3J11 recognizes that its principal current business is to apply its expertise
- in interpreting the C Standard. WG14 has requested that X3J11 develop initial
- interpretations and X3J11 has graciously agreed to do so. WG14 retains
- ultimate responsibility for publishing the responses, as I described above.
- For these and other reasons, I have resigned as Secretary and member of X3J11.
- I get all the glory I need as Convenor of WG14, thank you. And I seem to have
- more than enough work as well. That homework I mentioned earlier has occupied
- me for over a month, off and on, since the last meeting. The responsibility
- lies with me to prepare the normative addendum for SC22 balloting as a
- Committee Draft (CD). I also inherit several years of X3J11 interpretations as
- a huge batch of Defect Reports to log and organize.
- My aim in writing this report is not to win your symphathy. (But I'll take any
- I can get by the way.) Rather, it's to spell out the current formal activities
- in the C standard arena. Note that this report does not cover the work of
- X3J11.1 (a.k.a. the Numerical C Extension Group, or NCEG). That's because the
- charter of that subcommittee is to produce only a Technical Report (TR). In X3
- land, a TR does not have the force of a standard. It is simply advisory.
- You'll hear more about X3J11.1 in future installments of this column.
- I need to cover a lot of administrivia first, so please bear with me. I
- promise to give you a few technical details of what's happening to Standard C
- before I'm done.
-
-
- The Normative Addendum
-
-
- The normative addendum has, until recently, consisted of three contributions,
- each put forth by a separate member body:
- The UK contribution endeavors to clarify several dozen areas where people
- found the C Standard unclear. Some issues arose from queries within the UK.
- Others arose from early Requests for Interpretation (RFIs) submitted to X3J11.
- All took the form of examples to be added to the C Standard. (Examples are
- part of the C Standard, but don't affect the definition of the C language.
- Hence, they are a good vehicle for adding clarification without running the
- risk of inadvertently changing the language.)
- The Danish contribution adds macros and several alternate ways to spell some
- of the punctuators and operators in C. The idea is to provide a way to write C
- source code more readably in character sets that commandeer things like braces
- and the tilde character for other graphics. The C Standard includes trigraphs
- for this purpose, but nobody pretends that using them makes for readable code.
- Even if you can't replace all trigraphs, proponents argue, any improvement in
- readability is worth supporting.
- The Japanese contribution adds extensive support for manipulating large
- character sets in C. The C Standard provides only the bare minimum of the
- functionality you need to play with Kanji or other large character sets. The
- Japanese delegation has developed a much more ambitious extension to C for
- this purpose.
- The biggest change we agreed to last December was to delete the UK
- contribution from the normative addendum. Don't think we considered it
- unimportant--quite the contrary. Rather, we observed that the new machinery
- for handling Defect Reports offered more apropos vehicles for publishing the
- work of the UK delegation. So we threw this piece over the wall, as it were.
- The other two pieces got final approval at the meeting. Both, however,
- suffered from a serious shortcoming. They needed to be translated into better
- "standardese." The Danish contribution evolved as a one- or two-page statement
- of intent. The Japanese contribution was remarkably refined, given the
- difficulty that English presents to the Japanese. But still there were places
- where the wording was a bit rough, or where more formal jargon was called for.
- Lucky for me, Dave Prosser took it upon himself to correct these problems. As
- the final Redactor (editor) of the C Standard, Dave speaks standardese like an
- ISO bureaucrat. He also understands C better than practically anybody else I
- know. By the time he completed a pass over the normative addendum, I had
- little left to do except carp at details, then make a stack of review copies.
- As of this writing, a review committee is checking our work. I will then
- submit the document to SC22 for CD balloting, once we get everyone's approval.
- By the time you read this, the balloting should be under way. My goal is to
- have the balloting period close shortly after the next X3J11 meeting (New York
- City in May), and before the next WG14 meeting London in July). That's all
- part of a little game of brinksmanship that we Convenors play all the time.
-
-
- Defect Reports
-
-
- Meanwhile, back at the ranch, I have this great stack of interpretations from
- X3J11. Four dozen Requests for Interpretation have percolated through ANSI
- official channels since the C Standard was approved in 1989. Over the years,
- X3J11 has patiently addressed and debated every one. The result has been two
- Technical Information Bulletins (TIBs) summarizing the RFIs and committee
- responses.
- I described some of the earliest RFIs and responses in these pages way back
- when. (See "Standard C: A Matter of Interpretation," CUJ June 1990, and
- "Standard C: Interpreting the Nasties," CUJ July 1990.) Other people have also
- discussed some of the interpretations here and in other publications. Sadly,
- however, the TIBs have yet to be officially published by ANSI.
- Now it looks like they never will be. An administrative foulup or two delayed
- the publication of TIB #1. Then ANSI switched over to the ISO C Standard and
- the situation changed. No longer was ANSI obliged to interpret the C Standard,
- since it was now an ISO document. Worse, it wasn't clear whether ANSI was even
- permitted to issue interpretations, under the agreement with ISO. TIB #2
- sailed straight into the same swamp. Now both are mired in bureaucratic
- uncertainty.
- We didn't want to lose all those probing questions to public view. And we
- certainly didn't want to waste the carefully crafted responses. So I accepted
- the obligation to treat each of the ANSI RFIs as a separate Defect Report.
- I've ensured that Defect Reports #001 through #048 correspond to ANSI RFIs #01
- through #48. (And I've already been handed Defect Report #049 through a
- separate channel, even before the dust has settled on the changeover.)
- I've built this 100-page (typeset) Defect Report Log. It contains the original
- ANSI RFIs, each accompanied by a "suggested response"--the response crafted by
- X3J11 for publication in a TIB. And remember all those examples from the UK
- contribution of the normative addendum? Well, I dealt them out as appropriate
- among the RFIs. Each example is labeled as a "suggested correction" to the C
- Standard.
- That's not the end of it, of course. X3J11 developed most of its responses
- under a severe constraint. We were originally told that we could not change a
- single word of the C Standard. Even if a slight change of wording, or an added
- sentence, could clarify our intent without changing the language definition,
- we couldn't make the change. Thus, we put a lot of energy into rationalizing
- that you could read the C Standard the way we intended. That's not the best
- way to respond to a serious complaint from a confused questioner.
- Now WG14 has machinery for making such clarifications, as Technical
- Corrigenda. The sentiment among many members of both WG14 and X3J11 is that we
- should not waste this opportunity. We could simply publish the two ANSI TIBs
- as an ISO Record of Response. That would get the interpretations out to the
- public (at last) fairly quickly. But it would leave us in the position of
- rationalizing bad standards language instead of fixing it.
- So my task instead is to circulate this Defect Report Log among the membership
- of both committees. I hope that X3J11 can give us prompt guidance about the
- best way to respond to each Defect Report. Either we accept the explanation
- from the ANSI TIB, we include the example from the UK contribution, or we
- develop amended wording to clarify the C Standard. (I like to think that only
- one of these three options will suffice in each case.)
- I hope for prompt guidance because this process has already dragged on for too
- long. The sooner we can clarify the gray areas of Standard C for the world at
- large, the happier I'll be.
-
-
-
- The Danish Contribution
-
-
- Now for a few technical details. The Danish contribution requires that all
- implementations of Standard C add a header called <iso646.h>. (The name honors
- the ISO standard which corresponds to ASCII, except that it permits certain
- graphics to be substituted for those we Americans know and love.) Listing 1
- shows the contents of this header.
- Note that you can use this file as is with any variant of ISO 646. It just
- prints funny on some national variant of that character set. The idea, in
- fact, is to confine most of the funny printing to just this header (which you
- should seldom feel moved to print.) You can then write:
- if (x != 0 x != XMAX)
- .....
- as
- if (x ne 0 or x ne XMAX)
- .....
- and the code should be readable with any national variant. If that is not
- important to you, don't include the new header. Then none of the new macros
- conflict with any names you choose.
- Besides this header, all implementations of Standard C must also recognize
- alternate spellings for six tokens:
- <: :> <% %> %: %:%:
- /* are the same as */
- [ ] { } # ##
- /* respectively */
- Because they are just alternate ways to spell the same token, you can balance
- <: with }, if you want to be perverse. And if you "stringize" one of these
- alternate forms, you get a different result than when using the older token
- (or its trigraph form). Thus:
- #define STR(X) #X
- printf(STR( <: ) STR( { ) STR( ??< ));
-
- prints <:{{.
- Before you start writing letters, let me make a few observations:
- Not all of these alternate forms are needed to solve problems with ISO 646,
- despite the name. Some help with EBCDIC as well.
- None of these alternate forms help much with using certain changeable
- characters inside character constants and string literals. You still need to
- use trigraphs sometimes.
- bitand is hardly a great name to use for & as the address-of operator.
- You can improve on these names all sorts of ways. In fact, many people have.
- In further fact, suggesting alternative lists has been a popular indoor sport
- at C standards meetings for several years now.
- The point is that this addition is not perfect. I'm pretty convinced after
- years of trying, though, that perfection is unattainable here. This particular
- approach is good enough. It can also be argued that the problem this addition
- solves is small and rapidly getting smaller. Others are pretty convinced,
- though, that it is still a problem worth solving. I believe the clutter is
- small enough that the rest of us should be tolerant.
-
-
- The Japanese Contribution
-
-
- By far the largest part of the normative addendum is the Japanese component. I
- count one new macro, three new type definitions, and 60 (!) new functions. The
- basic idea is to provide a complete set of parallels between functions that
- act on one-byte characters and functions that act on the newer wide
- characters. It's too bad we have to invent a whole set of variant names for
- these new functions. (That's one of the ways that C++ has improved code
- hygiene over C.) But I believe the time is ripe to introduce better
- wide-character support.
- Windows NT traffics consistently in (16-bit) wide characters. It exemplifies
- the new trend toward supporting large and varied character sets. Multiple sets
- of 256 characters just don't cut it for systems and applications with an
- international market.
- I plan to devote next month's column to a detailed look at the Japanese
- contribution. It's too big to due justice to in the space remaining here. For
- now, I'll simply summarize what it contains:
- A set of functions analogous to those in <ctype.h> lets you classify wide
- characters much the way you do conventional ones. You can also define your own
- categories of characters and test for them.
- A set of functions analogous to those in <string.h> lets you manipulate
- wide-character strings much the way you do conventional ones.
- A set of functions analogous to those in <stdlib.h> lets you convert numeric
- wide-character strings much the way you do conventional ones.
- Additions to the wide-character conversion functions in <stdlib.h> give you
- much tighter control over the conversion process.
- A function analogous to strftime in <time.h> lets you encode time information
- as a wide-character string much the way you do conventional ones.
- Additional conversion specifiers for the existing print and scan functions let
- you convert between occasional multibyte sequences in files and wide
- characters internal to the program.
- A set of functions analogous to those in <stdio.h> lets you manipulate
- "wide-character streams". These are files of multibyte characters that appear
- internally as sequences of wide characters.
- Some of this stuff sounds redundant, and it is. Still, there are good reasons
- for each of the additions. I'll do my best to convince you of that next month.
-
- Listing 1 The header <iso646.h>
- #define and &&
- #define and_eq&=
- #define bitand&
- #define bitor
- #define compl ~
- #define ne !=
- #define not !
- #define or
- #define or_eq =
- #define xor ^
- #define xor_eq^=
-
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On the Networks
-
-
- It's Back?
-
-
-
-
- Sydney S. Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, lecturer, author,
- professor, and President of Datacomp Systems, Inc., a consulting and contract
- programming firm specializing in databases, data presentation and windowing,
- transaction processing, networking, testing and test suites, and device
- management for UNIX and MS-DOS. He can be contacted care of Datacomp Systems,
- Inc.,3837 Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail
- on the Internet/USENET mailbox syd@DSI.COM (dsinc!syd for those that cannot do
- Internet addressing).
-
-
- Once again, and with no explanation, comp.sources.unix. seems to have gotten
- restarted again, just in time for the new year. However, comp.sources.x is
- still silent. Well, while it is still active, I guess it's time to make the
- best of comp.sources.unix. Welcome back Paul. (The moderator is Paul Vixie.)
- Let's see if we can keep it coming and empty out the backlog, as many of these
- sources are over a year old.
- First on the new list is mytinfo from Ross Ridge <ross@zooid.guild.org>.
- mytinfo is a single library that combines the functionality of the standard
- UNIX termcap and terminfo libraries. It has the special ability of being able
- to fetch terminal descriptions from both termcap and terminfo databases
- regardless of which set of functions, termcap or terminfo, are used. It can
- even read terminal descriptions from terminfo source files. Also included is a
- tool to convert from/to termcap and terminfo source and binary formats. It was
- posted as Volume 26, Issues 77-79.
- If you need restrict privileged access (super-user access) to a small set of
- commands, then op from David Koblas <koblas@mips.com> is just what you need.
- Posted as Volume 26, Issue 80, it provides restrictions on who can execute the
- commands, what commands they can execute, and even what arguments are to be
- allowed to the commands. Just perfect for restricting mounting of CD-ROM
- drives or floppies as UNIX file systems (a task, alas, that requires super
- user-access).
- A variant on John Walker's settime for setting the clock on a Sun Sparcstation
- from a dial up access service was posted by John Rushford <rushpc!jjr@csn.org>
- as nisttime for Volume 26, Issue 81. This version is for System V flavors of
- UNIX and supports accessing both the NIST master clock at the National
- Institute of Standards and Technology and the USNO master clock at the Naval
- Observatory. The script will dial the respective clock, receive the correct
- time, and then update the systems clock.
- Copying boot tapes, or other multi-volume tapes under UNIX can be troublesome.
- You often have to spool them to disk first, and there isn't enough room. Thad
- Floryan <thad@btr. com> has provided tprobe-1.0 which can reveal a
- tapes-existing saveset layout, copy the tape using drives located anywhere on
- the network, and perform media conversions. Published as Volume 26, Issue 84,
- it even includes documentation.
- If your C is missing the library routine strftime then the version submitted
- by Arnold Robbins <arnold@skeeve.ATL.GA.US> will fit the bill. strftime is a
- format-string-controlled internal time representation to ASCII conversion
- filter. It supports locale for internationalization. strftime, and a date
- command wrapper, are Volume 26, Issue 85.
-
-
- Utilities Galore
-
-
- Utilities abound this time in comp. sources. misc.
- If you are tired of nohup leaving nohup.out files lying around, consider nhup
- from Gnanasekaran Swaminathan <gs4t@virginia.edu>. It works like nohup, as it
- runs the command in the background, immune from hangup and quit signals, but
- unlike nohup, if the input and output are not redirected, they are set to
- /dev/null and not a nohup. out file. nhup is Volume 33, Issue 80.
- pdcurses is a public domain Curses C library that is compatible with the UNIX
- System V 3.2 curses library. It is written to support most of the popular DOS
- and OS/2 C Compilers. This new version, 2.0, is almost a total rewrite of the
- prior 1.4 version, by a new maintainer, Mark Hessling <M.Hessling@gu.edu.au>.
- Posted as Volume 33, Issues 81-91, a dmake 3.8 flavor makefile is included for
- both DOS and OS/2. New in 2.0 include X/Open routines, Color support in System
- V format, OS/2 port, and many functions supported as both functions and
- macros.
- Farrell McKay <fbm@ptcburp.ptcbu.oz.au> released a bug-fixed version of his
- xcb-2.1 program for Volume 33, Issue 92. xcb provides easy access to the
- cut/paste buffers in the X server. New is WM_DELETE_WINDOW protocol support,
- and fixing of two major bugs.
- MetalBase, a simple database engine, portable between platforms (UNIX, MS-DOS,
- etc.) has been posted as mbase by Richard Parvin Jernigan
- <richid@owlnet.rice.edu> for Volume 33, Issues 119-126. This latest release,
- 5.0, has more field types, three-phase locking to ensure multi-user access
- without any system-based IPC, and internal caching to provide twice the speed
- and up for rebalancing indices. A conversion utility for MetalBase 4.0 and
- 4.1a relations is provided. Other features include runtime encryption on all
- platforms, creation of relations on-the-fly, and a flexible report writer.
- Lee Hounshell <tlhouns@srv.pacbell. com> has submitted his C++ object library
- var for Volume 33, Issues 127 and 128. var provides a "super-string" class
- with associate array capabilities. The var class does a pretty good job of
- offering a data object that assumes its "type" at runtime, based on context of
- use. Rarely will you need to declare ints, or longs, or doubles or char[] or
- even string objects! var does it all (or at least tries to).
- An alternative user interface for ftp was created by Mike Gleason
- <mgleason@cse.unl.edu>. ncftp was posted as Volume 34, Issues 14-16. It
- supports autologin as anonymous, access to sites in your .netrc file using a
- substring of their name, use of a pager to view remote directory listings, a
- transfer progress meter, activity logging, and much more. It's not as
- impressive as ftptool, but it doesn't require an X play either. A bug-fix
- patch (patch 1) was posted in Volume 34, Issue 20.
- Two C++ classes for outputting formatted numbers were contributed by Scott
- Kirkwood <kirkwood@qucis.queensu.ca>. The first, fformat for Volume 34, Issue
- 23, supports scaling and editing, truncation to fit using SI notation (K, M,
- etc.) for floating-point numbers. The second, iformat, in Issue 24 is for
- integer numbers and offers similar capabilities.
- A collection of X11 image processing and display utilities was contributed by
- John Cristy <cristy@eplrx7.es.duPont.com> for Volume 34, Issues 28-54 with
- patches in Issues 85-87, 88, 89, 98 and 118. The imagemagick utilities read
- and write MIFF images and has utilities to access GIF, JPEG, PostScript, PPM,
- RLE, SUN Raster, TIFF, Utah Raster, VICAR, X Bitmap, X Window Dump formats. It
- can display images, support animation, perform processing on images (scaling,
- rotation, color reduction, and more), and much more. Support for the JPEG4
- release is also included. (See the next item.)
- In addition, the Independent JPEG Group <jpeginfo@uunet.uu.net> has updated
- its JPEG software to version 4 in Volume 34, Issues 55-72 jpeg in this new
- version provides significant spe