home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Users Journal 1990 - 1995 / CUJ.iso / unix / 1993.txt < prev    next >
Encoding:
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.
  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.  
  30.  
  31.  
  32.  
  33.  
  34.  
  35.  
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69.  
  70.  
  71.  
  72.  
  73.  
  74.  
  75.  
  76.  
  77.  
  78.  
  79.  
  80.  
  81.  
  82.  
  83.  
  84.  
  85.  
  86.  
  87.  
  88.  
  89.  
  90.  
  91.  
  92.  
  93.  
  94.  
  95.  
  96.  
  97.  
  98.  
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111.  
  112.  
  113.  
  114.  
  115.  
  116.  
  117.  
  118.  
  119.  
  120.  
  121.  
  122.  
  123.  
  124.  
  125.  
  126.  
  127.  
  128.  
  129.  
  130.  
  131.  
  132.  
  133.  
  134.  
  135.  
  136.  
  137.  
  138.  
  139.  
  140.  
  141.  
  142.  
  143.  
  144.  
  145.  
  146.  
  147.  
  148.  
  149.  
  150.  
  151.  
  152.  
  153.  
  154.  
  155.  
  156.  
  157.  
  158.  
  159.  
  160.  
  161.  
  162.  
  163.  
  164.  
  165.  
  166.  
  167.  
  168.  
  169.  
  170.  
  171.  
  172.  
  173.  
  174.  
  175.  
  176.  
  177.  
  178.  
  179.  
  180.  
  181.  
  182.  
  183.  
  184.  
  185.  
  186.  
  187.  
  188.  
  189.  
  190.  
  191.  
  192.  
  193.  
  194.  
  195.  
  196.  
  197.  
  198.  
  199.  
  200.  
  201.  
  202.  
  203.  
  204.  
  205.  
  206.  
  207.  
  208.  
  209.  
  210.  
  211.  
  212.  
  213.  
  214.  
  215.  
  216.  
  217.  
  218.  
  219.  
  220.  
  221.  
  222.  
  223.  
  224.  
  225.  
  226.  
  227.  
  228.  
  229.  
  230.  
  231.  
  232.  
  233.  
  234.  
  235.  
  236.  
  237.  
  238.  
  239.  
  240.  
  241.  
  242.  
  243.  
  244.  
  245.  
  246.  
  247.  
  248.  
  249.  
  250.  
  251.  
  252.  
  253.  
  254.  
  255.  
  256.  
  257.  
  258.  
  259.  
  260.  
  261.  
  262.  
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270.  
  271.  
  272.  
  273.  
  274.  
  275.  
  276.  
  277.  
  278.  
  279.  
  280.  
  281.  
  282.  
  283.  
  284.  
  285.  
  286.  
  287.  
  288.  
  289.  
  290.  
  291.  
  292.  
  293.  
  294.  
  295.  
  296.  
  297.  
  298.  
  299.  
  300.  
  301.  
  302.  
  303.  
  304.  
  305.  
  306.  
  307.  
  308.  
  309.  
  310.  
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.  
  325.  
  326.  
  327.  
  328.  
  329.  
  330.  
  331.  
  332.  
  333.  
  334.  
  335.  
  336.  
  337.  
  338.  
  339.  
  340.  
  341.  
  342.  
  343.  
  344.  
  345.  
  346.  
  347.  
  348.  
  349.  
  350.  
  351.  
  352.  
  353.  
  354.  
  355.  
  356.  
  357.  
  358.  
  359.  
  360.  
  361.  
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370.  
  371.  
  372.  
  373.  
  374.  
  375.  
  376.  
  377.  
  378.  
  379.  
  380.  
  381.  
  382.  
  383.  
  384.  
  385.  
  386.  
  387.  
  388.  
  389.  
  390.  
  391.  
  392.  
  393.  
  394.  
  395.  
  396.  
  397.  
  398.  
  399.  
  400.  
  401.  
  402.  
  403.  
  404.  
  405.  
  406.  
  407.  
  408.  
  409.  
  410.  
  411.  
  412.  
  413.  
  414.  
  415.  
  416.  
  417.  
  418.  
  419.  
  420.  
  421.  
  422.  
  423.  
  424.  
  425.  
  426.  
  427.  
  428.  
  429.  
  430.  
  431.  
  432.  
  433.  
  434.  
  435.  
  436.  
  437.  
  438.  
  439.  
  440.  
  441.  
  442.  
  443.  
  444.  
  445.  
  446.  
  447.  
  448.  
  449.  
  450.  
  451.  
  452.  
  453.  
  454.  
  455.  
  456.  
  457.  
  458.  
  459.  
  460.  
  461.  
  462.  
  463.  
  464.  
  465.  
  466.  
  467.  
  468.  
  469.  
  470.  
  471.  
  472.  
  473.  
  474.  
  475.  
  476.  
  477.  
  478.  
  479.  
  480.  
  481.  
  482.  
  483.  
  484.  
  485.  
  486.  
  487.  
  488.  
  489.  
  490.  
  491.  
  492.  
  493.  
  494.  
  495.  
  496.  
  497.  
  498.  
  499.  
  500.  
  501.  
  502.  
  503.  
  504.  
  505.  
  506.  
  507.  
  508.  
  509.  
  510.  
  511.  
  512.  
  513.  
  514.  
  515.  
  516.  
  517.  
  518.  
  519.  
  520.  
  521.  
  522.  
  523.  
  524.  
  525.  
  526.  
  527.  
  528.  
  529.  
  530.  
  531.  
  532.  
  533.  
  534.  
  535.  
  536.  
  537.  
  538.  
  539.  
  540.  
  541.  
  542.  
  543.  
  544.  
  545.  
  546.  
  547.  
  548.  
  549.  
  550.  
  551.  
  552.  
  553.  
  554.  
  555.  
  556.  
  557.  
  558.  
  559.  
  560.  
  561.  
  562.  
  563.  
  564.  
  565.  
  566.  
  567.  
  568.  
  569.  
  570.  
  571.  
  572.  
  573.  
  574.  
  575.  
  576.  
  577.  
  578.  
  579.  
  580.  
  581.  
  582.  
  583.  
  584.  
  585.  
  586.  
  587.  
  588.  
  589.  
  590.  
  591.  
  592.  
  593.  
  594.  
  595.  
  596.  
  597.  
  598.  
  599.  
  600.  
  601.  
  602.  
  603.  
  604.  
  605.  
  606.  
  607.  
  608.  
  609.  
  610.  
  611.  
  612.  
  613. Designing an Extensible API in C
  614.  
  615.  
  616. Charles Mirho
  617.  
  618.  
  619. Charles Mirho is a consultant in New Jersey, specializing in Multimedia. He
  620. holds a Masters Degree in Computer Engineering from Rutgers University. He can
  621. be reached on CompuServe at: 70563,2671.
  622.  
  623.  
  624.  
  625.  
  626. Definition of an Extensible API
  627.  
  628.  
  629. An application program interface is the set of function calls that a software
  630. library exports to applications. These functions, along with their parameters,
  631. are usually prototyped in a header, or include file. In addition to
  632. prototypes, the header file also contains definitions for structures used by
  633. the functions, and #defines for flags and return values. All these components
  634. of the header file make up the complete definition of the API.
  635. An extensible API can accomodate growth in the library without requiring
  636. changes to existing applications (beyond a possible recompile). Listing 1
  637. contains a simple, extensible API.
  638. The function in Listing 1 is useful in GUI APIs. It defines a region of the
  639. display where a mouse click can be trapped. A typical call to the function
  640. might look something like
  641. #define ID_HELP_BUTTON 10
  642. int vertices[] = {10,20, 50,20, 50,40, 10,40};
  643. REGION HelpButton = {sizeof(vertices)/sizeof(int)/2,
  644. &vertices[0]};
  645. .
  646. .
  647. .
  648. DefineHotSpot (ID_HELP_BUTTON, &HelpButton);
  649. The value of ID_HELP_BUTTON varies depending on the application.
  650. ID_HELP_BUTTON also provides a unique ID for the region. The second parameter,
  651. &HelpButton, defines the boundary of the region as a set of vertices. Notice
  652. that the REGION structure is defined in API.H, the header file for the API.
  653. REGION contains a field for the number of vertices in the region, and a
  654. pointer to a list of vertices (coordinate pairs). Early versions of the
  655. library might only support rectangular hot spots (four vertices), but the API
  656. is extensible because more complex shapes can be used in the future without
  657. altering the prototype for DefineHotSpot. Compare Listing 1 to a
  658. non-extensible version of the same API in Listing 2.
  659. Since the region is always rectangular, only two vertices are required,
  660. specifying the upper-left and lower-right corners of the rectangle. While this
  661. function may seem cleaner and more intuitive at first glance, it is extremely
  662. confining. If future versions of the library must support regions with more
  663. than four vertices, then you must choose one of two undesirable alternatives:
  664. You can create an extensible version of DefineHotSpot. Now developers must
  665. learn two functions, since the old version of DefineHotSpot must be retained
  666. for compatibility. The result is a cluttered API.
  667. You must modify existing applications that use DefineHotSpot to support the
  668. new function API.
  669.  
  670.  
  671. Structured Parameters
  672.  
  673.  
  674. The extensible version of DefineHotSpot (Listing 1) uses a structured
  675. parameter, while the non-extensible version (Listing 2) passes all the
  676. parameters through the function prototype. Using structured parameters is one
  677. of the best ways to design an API that is more extensible. As the capabilities
  678. of the library expand, fields can be added to the structures without changing
  679. the function prototype. A good application of this technique is in functions
  680. that return information, such as
  681. int GetSystemInfo (SYSTEM_INFO
  682. * sinfo);
  683. This function is used to get information about devices in the computer system.
  684. The SYSTEM_INFO structure:
  685. typedef struct tagSYSTEM_INFO
  686. {
  687. int num_displays; /* number of attached displays */
  688. int num_printers; /* number of attached printers */
  689. int num_drives; /* number of attached disk drives */
  690. } SYSTEM_INFO;
  691. has fields to hold the important properties of the system. (I kept it short
  692. for clarity.)
  693. Later versions of the API can expand the structure to accomodate new additions
  694. to the system, such as tape drives, as in:
  695. typedef struct tagSYSTEM_INFO
  696. {
  697. int_num displays; /* number of attached displays */
  698. int_num_printers; /* number of attached printers */
  699. int_num_drives; /* number of attached disk drives */
  700. int_num_tapes; /* number of attached tape drives */
  701. } SYSTEM_INFO;
  702. Because the features of the system are passed through the API in the form of a
  703. structure, rather than as separate parameters, it is easy to add a field for
  704. tape drives.
  705.  
  706.  
  707.  
  708. The Size Field
  709.  
  710.  
  711. You can add even more flexibility to structured parameters with the size
  712. field. The size field holds the size, in bytes, of the structure containing
  713. it. When using a size field, you must make it the first field in the
  714. structure, as in
  715. typedef struct tagSYSTEM_INFO
  716. {
  717. int size; /* size of this structure */
  718. int num_displays; /* number of attached displays */
  719. int num_printers; /* number of attached printers */
  720. int num_drives; /* number of attached disk drives */
  721. int num_tapes; /* number of attached tape drives */
  722. } SYSTEM_INFO;
  723. The size field makes it possible for existing applications to use newer
  724. versions of the library without performing a recompile. This is especially
  725. useful on platforms that use dynamic linking, because dynamic link libraries
  726. are often packaged separately and sold directly to customers. Application
  727. developers often have no control over which version of the library customers
  728. are using.
  729. To see how the size field can save a recompile, look at the SYSTEM_INFO
  730. structure again. When the num_tapes field is added, the size of the structure
  731. changes. It would normally be necessary to recompile applications that use the
  732. structure so that static and dynamic allocations reserve the correct amount of
  733. storage. Otherwise, the newer library would write too much data into the
  734. structure parameter, corrupting memory. However, if the first field of the
  735. structure contains the structure's size, and you are careful to add fields
  736. only to the end of the structure, then the structure can be extended without
  737. the need to recompile existing applications. The library simply examines the
  738. size field to determine which version of the structure the application is
  739. passing. If the application is passing the older structure, the size will be
  740. smaller, and the library knows not to fill the extended fields. Listing 3
  741. contains an example.
  742. In Listing 3, the library keeps the declaration of the old SYSTEM_INFO
  743. structure as oSYSTEM_INFO. The oSYSTEM_INFO structure does not appear in the
  744. header file that applications use.
  745.  
  746.  
  747. Interpretation Flag
  748.  
  749.  
  750. Suppose the GetSystemInfo function is extended in the future to report details
  751. about particular devices in the system. You can use the same function to get
  752. the number of displays in the system, and details about the displays the
  753. system is using, as in:
  754. typedef struct tagDISPLAY_INFO
  755. {
  756. int size;
  757. /* size of this structure */
  758. int displayno;
  759. /* display to get info on */
  760. int xpixels;
  761. /* display width in pixels */
  762. int ypixels;
  763. /* display height in pixels */
  764. int bits_per_pixel;
  765. /* bits per pixel */
  766. int planes;
  767. /* video planes */
  768. } DISPLAY_INFO;
  769. You can insure that the GetDisplayInfo function will support this and any
  770. other device-specific structures that come along by changing the original
  771. prototype to
  772. int GetSystemInfo
  773. (int flag, unsigned char *info);
  774. GetDisplayInfo now accepts a byte-aligned pointer instead of a pointer to a
  775. specific structure. The function interpretes the pointer differently,
  776. depending of the value of the flag parameter. You call the function for
  777. general system information with
  778. /* API.H */
  779. #define GET_SYSTEM_INFO 1
  780. #define GET_DISPLAY_INFO 2
  781. .
  782. .
  783. /* application */
  784. SYSTEM_INFO sinfo = { sizeof(SYSTEM_INFO), 0, 0, 0 };
  785. .
  786. .
  787. GetSystemInfo (GET_SYSTEM_INFO, (unsigned char *)
  788. sinfo);
  789. For details on display devices, you call the function with
  790. /* application */
  791. DISPLAY_INFO dinfo = { sizeof (DISPLAY_INFO), 1, 0,
  792. 0, 0, 0};
  793. .
  794. .
  795. GetSystemInfo (GET_DISPLAY_INFO, (unsigned char *)
  796. dinfo);
  797.  
  798. Inside, the GetSystemInfo function would look something like Listing 4.
  799. Different structures describing entirely different things evolve differently,
  800. and so it is entirely possible for them to be the same size by coincidence.
  801. When different structures (as opposed to different versions of the same
  802. structure) are passed through the API as in Listing 4, the size field alone is
  803. not sufficient. The interpretation flag resolves any ambiguity.
  804.  
  805.  
  806. Variable-Sized Structures
  807.  
  808.  
  809. Variable-sized structures typically have a fixed-sized header portion and a
  810. variable-sized data portion. The header usually defines or limits the data in
  811. some way. The header and data are stored contiguously in memory, so that the
  812. data can be referenced as elements of the structure. This often makes the C
  813. code that manipulates the data easier to write and read. A variable-sized
  814. structure is also extensible because the data portion can be any size.
  815. The REGION structure in Listing 1 can be made variable-length by changing its
  816. definition to
  817. /* api.h */
  818. typedef struct tagREGION {
  819. int vertex_count;
  820. int vertices[1];
  821.  
  822. } REGION;
  823. int DefineHotSpot (int id,
  824. REGION *pRegion);
  825. Suppose you want the user to decide the shape of the region in which to trap
  826. mouse events. The number and values of the vertices in the region are not
  827. known at compile time. You first prompt the user for the number of vertices,
  828. then allocate a region of the proper size, as in
  829. /* application */
  830. REGION *pRegion;
  831.  
  832. printf ("Enter the number of
  833. vertices:\n");
  834. scanf {"%d", &cnt);
  835. pRegion = (REGION *) malloc
  836. (sizeof (REGION) +
  837. (2*cnt-1)*sizeof(int));
  838. The pointer pRegion points to a region large enough to hold cnt vertices. In
  839. the malloc statement, sizeof(REGION) allocates enough space for the header
  840. (the field vertex_count) and one vertex, since the typedef for REGION contains
  841. one vertex. Since each vertex is a pair of integers, cnt vertices requires
  842. 2*cnt integers. You thus allocate space for 2*cnt-1 integers in addition to
  843. the space already allocated for the base structure. You then cast the return
  844. value of malloc to a pointer to a REGION structure. From then on, you can
  845. refer to the vertices as members of the structure. A loop is used to read the
  846. vertex pairs, as in
  847. pRegion->vertex_count = cnt;
  848. for (i=0;i<cnt;i++)
  849. {
  850. fflush (stdin);
  851. printf ("Enter X,Y of vertex %d\n",
  852. i+1);
  853. scanf ("%d , %d",
  854. &pRegion->vertices [2*i],
  855. &pRegion->vertices [2*i+1]);
  856. }
  857. The region with all its vertices is now passed cleanly to the DefineHotSpot
  858. function
  859. DefineHotSpot (ID_HELP_BUTTON,
  860. pRegion);
  861.  
  862.  
  863. Templates
  864.  
  865.  
  866. Sometimes the arguments to a function are so unpredictable that even
  867. structured parameters are limiting. The classic example of this is the
  868. Standard C library function printf. The prototype for printf declares a single
  869. parameter, a string that acts as a template for optional arguments. The printf
  870. function scans the template for clues to the number and size of the optional
  871. arguments. This is an extremly powerful technique, since it allows the
  872. function to accept any number of arguments of any size, in any order.
  873. Consider, for example, a function that draws an arbitrary set of line
  874. segments. Each segment has its own attribute for width and color. Segments may
  875. or may not be connected. We create a simple language to tell the function how
  876. to draw one or more segments. The language describes the motion and attributes
  877. of an imaginary pen which moves across the drawing surface. Table 1 describes
  878. the Simple Drawing Language. Figure 1 shows sample output from the Simple
  879. Drawing Language. Figure 2 shows pen styles. Listing 5 contains an example of
  880. the Draw function using the Simple Drawing Language.
  881. Extending the API is as simple as extending the drawing language. For example,
  882. to support different line styles, the language is extended to include an s
  883. (for style) followed by a number 1-5.
  884.  
  885.  
  886. Callback Functions
  887.  
  888.  
  889. Callback functions provide a useful way for developers to enter the API. They
  890. provide developers with a means of extending the API without altering it.
  891. Suppose, for example, that an API included a function for copying one file to
  892. another, with optional compression, such as
  893. int zcopy (char *szSourceFile, char *szDestFile,
  894. int (*fnCompress)());
  895. This function takes two files as arguments. The first file is the source to
  896. copy from and the second is the destination to copy to. The third argument
  897. specifies an optional callback function to perform compression, so that the
  898. destination file takes up less space than the source. The callback function,
  899. if used, is provided by the developer who uses the library. The zcopy function
  900. calls the callback function repeatedly during the file copy. Developers are
  901. free to use any compression algorithm they desire. This makes the API
  902. extensible, since better compression algorithms can be developed and inserted
  903. without altering the API. Not only that, developers who use the library have a
  904. means of differentiating their products by offering better compression. The
  905. callback function resembles
  906. int fnCompress (unsigned char *pData, int *iSize)
  907. {
  908.  
  909. //code to perform compression here
  910. }
  911. The zcopy function passes to the function a buffer, pData, which contains the
  912. raw data from the source file and the size of the buffer. Function fnCompress
  913. is expected to compress the data in pData (possibly using intermediate
  914. buffers) and return the buffer and new size to the zcopy function.
  915. This example is slightly oversimplified. A commercial version of zcopy would
  916. require additional (possibly structured) parameters to specify things like the
  917. compression block size. This example is meant only to illustrate the utility
  918. of callback functions in extending the API.
  919.  
  920.  
  921. Conclusion
  922.  
  923.  
  924. Following simple guidelines when designing an API can save headaches down the
  925. road as the API expands to accomodate new features. Structured parameters,
  926. variable-sized structures, size fields, interpretation flags, templates, and
  927. callback functions are some of the ways to prepare an API for future growth.
  928. Figure 1 Sample output from the Simple Drawing Language
  929. Figure 2 Pen Styles
  930. Table 1 Simple drawing language
  931. p (lower p) - pick up the pen; subsequent moves do not mark the
  932.  drawing surface
  933. P (upper p) - put down the pen; subsequent moves mark the drawing
  934.  surface
  935. c set pen color to the value which follows: R=red, G=green,
  936.  B=blue, b=black
  937.  Example: cR - set pen to color red
  938. w set pen width to the value which follows: T=thin, M=medium, F=fat
  939.  Example: wF - set pen width to fat
  940. %x move to X coordinate specified by the next parameter
  941. %y move to Y coordinate specified by the next parameter
  942.  
  943. Listing 1 An extensible API
  944. /* API.H */
  945. typedef struct tagREGION
  946. {
  947. int vertex_count;
  948. int *vertices;
  949. } REGION;
  950. int DefineHotSpot (int id, REGION *pRegion);
  951.  
  952. /* End of File */
  953.  
  954.  
  955. Listing 2 A non-extensible version of the header file API.H in Listing 1
  956. /* API.H */
  957. int DefineHotSpot (int id, int x1, int y1, int x2, int y2);
  958. .
  959. .
  960. .
  961. /* Application */
  962. #define ID_HELP_BUTTON 10
  963.  
  964. DefineHotSpot (ID_HELP_BUTTON, 10,20,50,40);
  965. /* End of File */
  966.  
  967.  
  968. Listing 3 An example of how the size field can save a recompile
  969. int GetSystemInfo (SYSTEM_INFO *sinfo)
  970. {
  971. sinfo->num_displays = _getNumDisplays ();
  972. sinfo->num_printers = _getNumPrinters();
  973. sinfo->num_drives = _getNumDrives();
  974. if (sinfo->size == sizeof (oSYSTEM_INFO))
  975. {
  976. /* don't touch extended fields */
  977.  
  978. }
  979. if (sinfo->size == sizeof (SYSTEM_INFO))
  980. {
  981. /* fill extended fields */
  982. sinfo->num_tapes = _getNumTapes();
  983. }
  984. return 0;
  985. }
  986.  
  987. /* End of File */
  988.  
  989.  
  990. Listing 4 The function GetSystemInfo
  991. int GetSystemInfo (int flag, unsigned char *ptr)
  992.  
  993. int GetSystemInfo (int flag, unsigned char *ptr)
  994. {
  995. if (flag == GET_SYSTEM_INFO)
  996. {
  997. if ( (int)*ptr == sizeof (oSYSTEM_INFO))
  998. {
  999. oSYSTEM_INFO *sinfo= (oSYSTEM_INFO *)ptr;
  1000. /* don't touch extended fields */
  1001. sinfo->num_displays = _getNumDisplays();
  1002. sinfo->num_printers = _getNumPrinters();
  1003. sinfo->num_drives = _getNumDrives();
  1004. }
  1005. if ( (int)*ptr == sizeof (SYSTEM_INFO))
  1006. {
  1007. SYSTEM_INFO *sinfo = (SYSTEM_INFO *)ptr;
  1008. /* fill extended fields */
  1009. sinfo->num_displays = _getNumDisplays();
  1010. sinfo->num_printers = _getNumPrinters();
  1011. sinfo->num_drives = _getNumDrives();
  1012. sinfo->num_tapes = _getNumTapes();
  1013. }
  1014. }
  1015. if (flag == GET_DISPLAY_INFO)
  1016. {
  1017. DISPLAY_INFO *dptr = (DISPLAY_INFO *)ptr;
  1018. dptr->xpixels = _getDisplayWidth(dptr->displayno);
  1019. dptr->ypixels = _getDisplayHeight(dptr->displayno);
  1020. dptr->bits_per_pixel = _getDisplayBPPix(dptr->displayno);
  1021. dptr->planes = _getDisplayPlanes(dptr->displayno);
  1022. }
  1023. return 0;
  1024. }
  1025.  
  1026. /* End of File */
  1027.  
  1028.  
  1029. Listing 5 The Draw function using the Simple Drawing Language in Table 1
  1030. #include <stdio.h>
  1031.  
  1032. #define PENUP() pen_up=1
  1033. #define PENDOWN() pen_up=0
  1034. #define SETCOLOR(x) color=x
  1035. #define SETWIDTH(x) width=x
  1036. #define BLACK 0
  1037.  
  1038. #define RED 1
  1039. #define GREEN 2
  1040. #define BLUE 3
  1041. #define FAT 0
  1042. #define MEDIUM 1
  1043. #define THIN 2
  1044.  
  1045. #define TRUE 1
  1046. #define FALSE 0
  1047.  
  1048. int pen_up;
  1049. int color;
  1050. int width;
  1051. int x1, x2, y1, y2;
  1052. int gotx1, gotx2, goty1, goty2;
  1053.  
  1054. /* Function Draw - demonstrates use of the Simple Drawing Language
  1055. Does not actually draw lines, simple parses the first parameter and
  1056. prints commands on screen */
  1057.  
  1058. Draw (char *str, int val)
  1059. {
  1060. int *pval=&val; //points to optional parameters
  1061. //restore defaults
  1062. gotx1=goty1=gotx2=goty2=FALSE;
  1063. x1=x2=y1=y2=0;
  1064. pen_up=1;
  1065. color = BLACK;
  1066. width=MEDIUM;
  1067. //loop to parse the command string
  1068. while (TRUE)
  1069. {
  1070. switch (*str++)
  1071. {
  1072. case 'p': PENUP(); break; //pick pen up
  1073. case 'P': PENDOWN(); break; //put pen down
  1074. case 'c': //set color
  1075. switch (*str++)
  1076. {
  1077. case 'R': SETCOLOR(RED); printf ("Color set to red\n");break;
  1078. case 'G': SETCOLOR(GREEN); printf ("Color set to green\n");break;
  1079. case 'B': SETCOLOR(BLUE); printf ("Color set to blue\n");break;
  1080. case 'b': SETCOLOR(BLACK); printf ("Color set to black\n");break;
  1081. default: return -1;
  1082. } //End switch (on character)
  1083. break;
  1084. case 'w': //set width
  1085. switch (*str++)
  1086. {
  1087. case 'T': SETWIDTH(THIN); printf ("Width set to thin\n");break;
  1088. case 'M': SETWIDTH(MEDIUM); printf ("Width set to medium\n");break;
  1089. case 'F': SETWIDTH(FAT); printf ("Width set to fat\n");break;
  1090. default: return -1;
  1091. } //End switch (on character)
  1092. break;
  1093. case '%': //get next optional parameter
  1094. switch (*str++)
  1095. {
  1096. case 'x': //set x coordinate
  1097.  
  1098. if (gotx2) {x1=x2; x2=*pval++; break;}
  1099. if (!gotx1) {x1=*pval++; gotx1=TRUE; break;}
  1100. if (!gotx2) {x2=*pval++; gotx2=TRUE; break;}
  1101. break;
  1102. case 'y': //set y coordinate
  1103. if (goty2) {y1=y2; y2=*pval++; break;}
  1104. if (!goty1) {y1=*pval++; goty1=TRUE; break;}
  1105. if (!goty2) {y2=*pval++; goty2=TRUE; break;}
  1106. break;
  1107. default: return -1;
  1108. } //End switch (token)
  1109. //do we have enough info to draw the line?
  1110. if (gotx2 && goty2 && !pen_up)
  1111. {
  1112. printf ("Drawing line <%d,%d>-<%d,%d>\n", x1,y1,x2,y2);
  1113. x1 = x2;
  1114. y1 = y2;
  1115. goty2 = FALSE;
  1116. gotx2 = FALSE;
  1117. } //end if (got both coordinates - draw line)
  1118. break;
  1119. case '\0': //end of command string
  1120. return 0;
  1121. default: return -1;
  1122. } //End switch (on character)
  1123. } //End while (TRUE)
  1124. return 0;
  1125. } //End function (Draw)
  1126. /* End of File */
  1127.  
  1128.  
  1129.  
  1130.  
  1131.  
  1132.  
  1133.  
  1134.  
  1135.  
  1136.  
  1137.  
  1138.  
  1139.  
  1140.  
  1141.  
  1142.  
  1143.  
  1144.  
  1145.  
  1146.  
  1147.  
  1148.  
  1149.  
  1150.  
  1151.  
  1152.  
  1153.  
  1154.  
  1155.  
  1156.  
  1157.  
  1158.  
  1159.  
  1160.  
  1161. Using Wrappers to Improve Portability of Commercial Libraries
  1162.  
  1163.  
  1164. Kenneth E. Van Camp
  1165.  
  1166.  
  1167. Kenneth Van Camp is a Senior Programmer/Analyst for IMI Systems. He has been
  1168. developing professional software applications for over ten years, and holds a
  1169. B.S. in Mechanical Engineering from the State University of New York at Stony
  1170. Brook. He can be reached at: R.D. #1 Box 1255, East Stroudsburg, PA 18301.
  1171.  
  1172.  
  1173. You've seen the ads for them: the Ultimate Portability Libraries. One supports
  1174. Windows, PenPoint, and Presentation Manager. Another supports UNIX, MS-DOS,
  1175. and VMS. Each one is the "only" tool you'll ever need. Promise.
  1176. Well, maybe not. But you can improve the portability of software that uses
  1177. third-party libraries by isolating all library-specific code and writing
  1178. wrappers. Most programmers are familiar with wrappers as applied to functions,
  1179. but wrappers can also be written for all data structures, constants, and
  1180. global variables. In C++, this concept is known as encapsulation, but it works
  1181. equally well in C.
  1182.  
  1183.  
  1184. Why Use Wrappers
  1185.  
  1186.  
  1187. There are several compelling reasons to use wrappers:
  1188. Future portability
  1189. New version protection
  1190. Better parameter and return value checking
  1191. Programming for the lowest common denominator
  1192. Simplification
  1193. Enforced corporate standardization
  1194. Uniformity of naming standards
  1195. See the sidebar "Why Use Wrappers" for more information.
  1196.  
  1197.  
  1198. Wrapper Functions
  1199.  
  1200.  
  1201. In their simplest form, wrapper functions are a one-to-one match of library
  1202. functions. Wrappers simplify the interface to a library by combining multiple
  1203. function calls or eliminating unused parameters. If you later determine you
  1204. need a parameter you thought was unnecessary, it is a simple matter to write a
  1205. second wrapper with a more complete parameter list. The simpler wrapper can
  1206. then call the more complex one.
  1207. In C++, unused parameters can be given default values so a future need can be
  1208. easily accommodated. Alternatively, over-loading the wrapper can provide a
  1209. simple mechanism for dealing with multiple complexity levels.
  1210. One of the most important rules of a wrapper function is that none of its
  1211. parameters can be of a type declared by a third-party library. This means that
  1212. all data structures must be wrapped, too. C++ handles this by encapsulation.
  1213. Simply write a class that includes the library data structure as a private
  1214. member, and write access functions for any structure members that are required
  1215. in application code. While you are at it, consider including related wrapper
  1216. functions as members of the class, so they can pass the original data
  1217. structure to library functions without having to be a friend to the class.
  1218. Listing 1 shows an example of a C++ class that encapsulates the Windows 3.1
  1219. API WNDCLASS structure and a related API function.
  1220. In C, this behavior can be easily mimicked. For example, Listing 2 shows an
  1221. equivalent set of wrappers in C for the WNDCLASS structure. typedefs rename
  1222. the Windows structures, and several access functions (all beginning with the
  1223. prefix WCL_) allow the application programmer to define or retrieve values of
  1224. the structure members.
  1225. Notice the initialization function (WCL_init) used in place of a C++
  1226. constructor to initialize values to reasonable defaults. In this case, I
  1227. included several parameters to set structure members that are usually
  1228. initialized in any application. Additional access functions allow me to set or
  1229. retrieve the values of any other members. These can be added as the need
  1230. arises.
  1231. Once you have become used to C++ constructors, you quickly become dependent on
  1232. the idea of an initialization function for your data structures. The only
  1233. problem with using one in C is that the programmer must remember to call it
  1234. before using the data structure. The best approach to this is to write an
  1235. initialization function for every structure type, even if you don't need it,
  1236. and require that an initializer be called before using any data structure.
  1237. That way it is less likely to be forgotten.
  1238. A case can be made for an exit function for every data structure, analogous to
  1239. the C++ destructor. In practice, I have found this is rarely necessary and
  1240. merely places an undue burden on the C programmer.
  1241.  
  1242.  
  1243. Callback Functions
  1244.  
  1245.  
  1246. Sometimes, you must write a function with an interface determined by the
  1247. library in use. For instance in Windows, callback functions are commonly
  1248. written to handle messages for dialog boxes, for enumerations, and timer
  1249. processing. In each case, your application's function must process parameters
  1250. that are beyond its control.
  1251. It is possible to write a macro to wrap callback functions and give them an
  1252. interface of your choosing. Personally, I do not use this approach because I
  1253. find this type of macro leads to code that is confusing to read and debug.
  1254. Instead, I place all callback functions in a module of their own. The only
  1255. thing these functions do is load the parameter values into a data structure
  1256. and pass the structure to the real callback function, which is kept in the
  1257. normal application code.
  1258. The problem with this approach is that each callback function has a separate
  1259. wrapper, so each one must be rewritten if the library's callback interface
  1260. changes. For instance, one of the differences between the WIN16 and WIN32 APIs
  1261. is that some data was moved from the lParam to the wParam for several
  1262. messages. A translation function could handle this if you properly
  1263. encapsulated the parameters in a data structure, but every callback wrapper
  1264. must be modified to call the translation function.
  1265. In fact, it is not necessary to have a separate callback function for every
  1266. Windows dialog box. Since a window handle is provided to the callback
  1267. function, a single callback function can handle all dialog messages and
  1268. dispatch them to the appropriate function in your application code for the
  1269. window in use. The situation is the same for enumerations and timer
  1270. procedures.
  1271.  
  1272.  
  1273. Conclusions
  1274.  
  1275.  
  1276. Wrappers and encapsulating classes are a simple way to increase your
  1277. software's longevity. If you have experience working with several different
  1278. libraries, you may be able to put an even higher-level interface on your
  1279. wrappers -- one which removes the application programmer even farther from the
  1280. specifics of one library. This will give your application even greater
  1281. longevity as you declare your independence of any one library.
  1282. There is an initial investment in all of these techniques, but this investment
  1283. usually pays for itself long before the initial project ends. In later
  1284. projects, the payback will be nearly instantaneous. In addition, increased
  1285. reliability and standardization provide benefits that are difficult to
  1286. quantify but obvious to any experienced developer.
  1287. Why Use Wrappers
  1288.  
  1289.  
  1290. Future Portability
  1291.  
  1292.  
  1293.  
  1294. A simple but solid goal when developing software should be to remain as
  1295. independent as possible from third-party software, so that your project does
  1296. not depend on any one vendor or environment. However, you should not reinvent
  1297. the wheel and write your own libraries. Instead you should isolate all of your
  1298. library-specific code in a few modules, including all references to a
  1299. library's functions, data structures, classes, header files, constants, and
  1300. global variables.
  1301. When you isolate all system-dependent code in a few modules, porting to new
  1302. environments in the future might be as easy as rewriting the wrapper modules
  1303. to support a new library. There is no guarantee that your new library can be
  1304. mapped onto the function calls of the old library, but chances are most of it
  1305. will.
  1306. Don't be tempted to ignore this argument, even if you can write compatible
  1307. wrappers on future systems to make them emulate your current environment. This
  1308. can be a trap. No matter how much time you allocate for a future port, you
  1309. will never implement all of the options of your current library. By taking the
  1310. time to isolate your system-dependent code now, parameter checking can be
  1311. tightened in the future to prevent use of features that are not implemented in
  1312. all of your environments.
  1313. While you are wrapping third-party library functions, you will probably also
  1314. want to wrap the file- and memory-access functions that you use. Even though
  1315. the ANSI file-access functions may be all you need right now, there is a good
  1316. chance that a future environment (a network, Windows, OS/2, etc.) will require
  1317. you to use some special access functions. For memory access, you may want to
  1318. use a handle for major data structures to allow future porting to
  1319. virtual-memory environments. A simple, easy-to-maintain handle table allows
  1320. for future migration.
  1321.  
  1322.  
  1323. New-Version Protection
  1324.  
  1325.  
  1326. No one can predict how a future release of a commercial library will behave.
  1327. There will always be bug fixes and enhancements that change the behavior of
  1328. the library, for better or for worse. If your application depends on the
  1329. behavior of the old version, you may be able to correct for the changes in the
  1330. wrappers. For example, Microsoft changed the return value of the fputs
  1331. function in Microsoft C between versions 4.0 and 5.0. Although the new return
  1332. value is now an ANSI-standard function, no one expected fputs to change. An
  1333. application developed with a wrapper for fputs could probably have been
  1334. revised to accommodate this change with minimal effort.
  1335.  
  1336.  
  1337. Better Parameter and Return Value Checking
  1338.  
  1339.  
  1340. In a perfect world, programmers check the values of all parameters before
  1341. passing them to functions. They also check all function return values to catch
  1342. any errors. In the real world, many junior-level programmers don't know all of
  1343. the conditions they should check for. Even senior-level programmers may
  1344. overlook unlikely error conditions, especially under the stress of a deadline.
  1345. Wrapper functions can check for any unlikely conditions and call a global
  1346. error-reporting function. The application can determine where such error
  1347. messages should be directed, depending upon the current state.
  1348. For instance, when retrieving a database record you normally write application
  1349. code that handles only two conditions. If the call fails, it was most likely
  1350. caused by a missing record. In the wrapper, you can take care of checking for
  1351. unusual conditions like a corrupted database and branch to an appropriate
  1352. routine to shut down and perform an integrity check on the database.
  1353. This approach allows you to write your code checking software in increments.
  1354. As you gain experience with a library and discover new conditions that merit
  1355. checking, you can add these checks without modifying all of your application
  1356. code.
  1357.  
  1358.  
  1359. Programming for the Lowest Common Denominator
  1360.  
  1361.  
  1362. While checking parameters, you can also check for options that are not
  1363. available on all the platforms your software supports. If a future port
  1364. determines that an option should not be used, the wrapper can provide a
  1365. warning for programmers in the old environment as well as the new one. This
  1366. will stop future programmers from using options that can't be supported on all
  1367. systems.
  1368.  
  1369.  
  1370. Simplification
  1371.  
  1372.  
  1373. If you have six programmers on a team developing a Windows application, should
  1374. you train all six in Windows development? Not necessarily. By assigning two or
  1375. three members to write the wrappers and document the available options, you
  1376. can decrease the learning curve for the remaining team. The remaining team can
  1377. then focus on problems they are better qualified to solve. The team's database
  1378. experts, for instance, can concentrate on database issues.
  1379. Documentation for the wrappers can be as simple as, "This duplicates the
  1380. functionality of the xyz function in library ABC, with the following
  1381. changes..." However, the documentation for library ABC will change from one
  1382. release to another, but your wrapper's interface should not. If time permits,
  1383. you should write complete documentation for the wrapper. Otherwise, the
  1384. library's release number should be noted and the old manuals retained for
  1385. reference.
  1386. If you have the time to properly document your work, you might as well try to
  1387. simplify the application developer's job. If you find that calls to a library
  1388. function are always preceded by one or more "setup" functions, it may make
  1389. sense to combine the functions into a single wrapper call. Also, if you find
  1390. yourself always initializing data structure members to a certain value before
  1391. calling a function, this can easily be placed in the wrapper.
  1392.  
  1393.  
  1394. Enforced Corporate Standardization
  1395.  
  1396.  
  1397. By limiting parameter choices and function calls in the wrappers, you can make
  1398. a more consistent application. The look and feel of the application should not
  1399. be a decision left to junior-level programmers, but rather should be a matter
  1400. of corporate policy. Wrappers are the perfect place to enforce this policy.
  1401.  
  1402.  
  1403. Uniformity of Naming Standards
  1404.  
  1405.  
  1406. Instead of using whatever naming standards are used in a third-party library,
  1407. the wrappers can be written to conform to your company's naming standards.
  1408. This shortens the learning curve for new programmers, and makes code easier to
  1409. read. For instance, if your company does not normally use Hungarian notation,
  1410. calls to Windows API functions probably look out of place.
  1411.  
  1412. Listing 1 A C++ class that encapsulates the Windows 3.1 API WNDCLASS structure
  1413. and a related API function
  1414. #include "windows.h"
  1415.  
  1416. typedef int BOOL;
  1417. typedef WNDPROC MYWNDPROC;
  1418. typedef HINSTANCE MYHINSTANCE;
  1419. typedef HCURSOR MYHCURSOR;
  1420. typedef HBRUSH MYHBRUSH;
  1421.  
  1422. class MyWndClass
  1423. {
  1424.  
  1425. public:
  1426. MyWndClass (char *, MYWNDPROC, MYHINSTANCE, char *);
  1427. void SetCursor (MYHCURSOR hCursor)
  1428. { wndclass.hCursor = hCursor; }
  1429. void SetBackground (MYHBRUSH hbrBackground)
  1430. { wndclass.hbrBackground = hbrBackground; }
  1431. void SetMenu (char *szMenuName)
  1432. { wndclass.lpszMenuName = szMenuName; }
  1433. BOOL RegisterClass (void)
  1434. { return (::RegisterClass (&wndclass)); }
  1435.  
  1436. private:
  1437. // Default constructor is inaccessible.
  1438. MyWndClass (void);
  1439. WNDCLASS wndclass;
  1440. };
  1441.  
  1442. MyWndClass::MyWndClass (char *szAppName, MYWNDPROC
  1443. WndProc, MYHINSTANCE hInstance,
  1444. char *szMenuName)
  1445. {
  1446. wndclass.style = CS_HREDRAW CS_VREDRAW;
  1447. wndclass.lpfnWndProc = WndProc;
  1448. wndclass.cbClsExtra = 0;
  1449. wndclass.cbWndExtra = 0;
  1450. wndclass.hInstance = hInstance;
  1451. wndclass.hIcon = LoadIcon (NULL,
  1452. IDI_APPLICATION);
  1453. wndclass.hCursor = LoadCursor (NULL,
  1454. IDC_ARROW);
  1455. wndclass.hbrBackground =
  1456. GetStockObject (WHITE_BRUSH);
  1457. wndclass.lpszMenuName = szMenuName;
  1458. wndclass.lpszClassName = szAppName;
  1459. }
  1460.  
  1461. /* End of File */
  1462.  
  1463.  
  1464. Listing 2 The equivalent of Listing 1 using wrapper functions in C
  1465. #include "windows.h"
  1466.  
  1467. typedef int BOOL;
  1468. typedef WNDCLASS MYWCLASS;
  1469. typedef WNDPROC MYWNDPROC;
  1470. typedef HINSTANCE MYHINSTANCE;
  1471. typedef HCURSOR MYHCURSOR;
  1472. typedef HBRUSH MYHBRUSH;
  1473.  
  1474. void WCL_Init (MYWCLASS *wndclass, char *szAppName,
  1475. MYWNDPROC WndProc, MYHINSTANCE hInstance,
  1476. char *szMenuName)
  1477. {
  1478. wndclass->style = CS_HREDRAW CS_VREDRAW;
  1479. wndclass->lpfnWndProc = WndProc;
  1480. wndclass->cbClsExtra = 0;
  1481. wndclass->cbWndExtra = 0;
  1482. wndclass->hInstance = hInstance;
  1483. wndclass->hIcon = LoadIcon (NULL,
  1484.  
  1485. IDI_APPLICATION);
  1486. wndclass->hCursor = LoadCursor (NULL,
  1487. IDC_ARROW);
  1488. wndclass->hbrBackground =
  1489. GetStockObject (WHITE_BRUSH);
  1490. wndclass->lpszMenuName = szMenuName;
  1491. wndclass->lpszClassName = szAppName;
  1492. }
  1493.  
  1494. void WCL_SetCursor (MYWCLASS *wndclass,
  1495. MYHCURSOR hCursor)
  1496. {
  1497. wndclass->hCursor = hCursor;
  1498. }
  1499.  
  1500. void WCL_SetBackground (MYWCLASS *wndclass,
  1501. MYHBRUSH hbrBackground)
  1502. {
  1503. wndclass->hbrBackground = hbrBackground;
  1504. }
  1505.  
  1506. void WCL_SetMenu (MYWCLASS *wndclass,
  1507. char *szMenuName)
  1508. {
  1509. wndclass->lpszMenuName = szMenuName;
  1510. }
  1511.  
  1512. BOOL WCL_RegisterClass (MYWCLASS *wndclass)
  1513. {
  1514. return (RegisterClass (wndclass));
  1515. }
  1516.  
  1517. /* End of File */
  1518.  
  1519.  
  1520.  
  1521.  
  1522.  
  1523.  
  1524.  
  1525.  
  1526.  
  1527.  
  1528.  
  1529.  
  1530.  
  1531.  
  1532.  
  1533.  
  1534.  
  1535.  
  1536.  
  1537.  
  1538.  
  1539.  
  1540.  
  1541.  
  1542.  
  1543.  
  1544.  
  1545.  
  1546.  
  1547.  
  1548. Template Classes for the iostreams Library
  1549.  
  1550.  
  1551. Randal Kamradt
  1552.  
  1553.  
  1554. Randy has been programming since 1984, and is working for Bordart Automation
  1555. where he develops CD-ROM based applications for libraries. His special
  1556. interests include object-oriented programming/design and C/C++ programming in
  1557. general.
  1558.  
  1559.  
  1560. One of the nicer additions that C++ made to C is the iostreams facility. Even
  1561. if you only use C++ as "a better C," iostream provides an elegant method of
  1562. I/O and allows easy extensions for user-defined types. This ease of extension
  1563. only works one way, though, as creating new stream types that work with
  1564. existing code can be fairly complicated, and requires a complete understanding
  1565. of the streams hierarchy.
  1566. When I first got my hands on a C++ compiler I wanted to create a streams class
  1567. that would work with a compact disk. My compact disk driver provided basic
  1568. read routines, and I already had a class that I used as an interface to the
  1569. driver. To provide seamless integration with existing code I needed to create
  1570. new classes that fit in with the existing iostreams hierarchy. (See the
  1571. sidebar "iostreams Hierarchy" for a discussion of this class and virtual
  1572. inheritance.)
  1573. The first step was to copy the definitions of the ifstream, fstreambase, and
  1574. filebuf classes and rename them to cdstream, cdstreambase, and cdbuf classes.
  1575. (I was not concerned with output classes since I was working with CDs.) Then I
  1576. changed the open parameters and constructor parameters to suit the class I
  1577. used to interface with the CD driver. Within the cdbuf class I replaced the
  1578. file-handle integer with a pointer to the CD interface class, and added
  1579. appropriate new and delete statements in the open and close routines of cdbuf.
  1580. Finally, I replaced the calls to the global open, close, read, and seek with
  1581. ones that used the CD class pointer. After doing all that and then fixing the
  1582. few bugs I let creep in, I was convinced that there had to be a better way.
  1583. A few months after creating these classes I was faced with doing it again. By
  1584. this time we had a new version of Borland C++ with templates. After thinking
  1585. about it for a while, I decided that templates could be the better way I
  1586. wanted. By creating generic versions of the classes I had copied before, I
  1587. could create an endless number of different stream types for anything that
  1588. resembled a stream. In this article, I will present these template classes,
  1589. and a serial port stream class as an example. These classes were compiled and
  1590. tested with Borland C++ v3.1. The portability of the templates depends on
  1591. consistency in the iostreams facility across compilers.
  1592.  
  1593.  
  1594. Template Classes
  1595.  
  1596.  
  1597. To avoide duplicating classes unnecessarily, I created templates for the
  1598. classes. (See the sidebar "Templates" for more information.) The classes I
  1599. made templates of included filebuf, fstreambase, fstream, ifstream, and
  1600. ofstream, I call the template classes tbuf<T>, tstreambase<T>, tstream<T>,
  1601. itstream<T>, and otstream<T>. The <T> suffix indicates a templeate class. The
  1602. replaceable type is a basic class that has open, close, read, write, and seek
  1603. member functions. (I will call this class T when referrring to this
  1604. replaceable type.) When creating a class for an entity that does not have one
  1605. or more of these functions, you can either create a dummy function that does
  1606. nothing, or one that calls an error routine. See Listing 1 for all template
  1607. class definitions.
  1608. The first template class, tbuf<T> is the most complex. It contains one object
  1609. of the variable type, and controls the operation of that object. tbuf<T>'s
  1610. open, close, and seek member functions directly call T's open, close, and seek
  1611. member functions. The overflow and underflow member functions call read and
  1612. write, along with setting the various buffer pointers. It also has the ability
  1613. to attach an already open T to itself. tbuf<T> is derived from streambuf,
  1614. which provides it's interface. Some of the member functions of tbuf<T>
  1615. override the virtual functions of streambuf. Since ios contains a pointer to
  1616. streambuf, ios has access to these virtual functions, and is able to read and
  1617. write tbuf<T>.
  1618. The second template class, tstreambase<T> contains a tbuf<T>. It is derived
  1619. (virtually) from ios. In its constructor tstreambase<T> initializes ios with a
  1620. pointer to its tbuf<T> object. It can also open its tbuf<T> object if called
  1621. with the necessary parameters. Otherwise, it has an open, close, and attach
  1622. call that map directly to the tbuf<T> open, close, and attach member function.
  1623. The last set of template classes are tstream<T>, itstream<T>, and otstream<T>.
  1624. These are multiply derived from istream/ostream and tstreambase<T>. They are
  1625. shell classes that simply combine the capabilities of the two inherited
  1626. classes. The only thing necessary in the definition is the duplication of the
  1627. constructors, and an open and rdbuf member function, that calls the
  1628. tstreambase open and rdbuf member functions. The open function is redefined to
  1629. give default mode values to itstream<T> and otstream<T>. The rdbuf function is
  1630. redefined to avoid ambiguities with ios which contains its own rdbuf function.
  1631. Note that when creating a deep hierarchy, constructors need to be defined for
  1632. all classes, even if they don't change from the base class's constructor.
  1633. Duplicating numerous constructors, or constructors with long parameter lists,
  1634. can be a nuisance. There are four constructors for itstream<T>/otstream<T>
  1635. that are duplicates of the tstreambase<T> constructors:
  1636. tstream()
  1637. tstream(const char *name, int mode, int prot)
  1638. tstream(T &f)
  1639. tstream(T &f, char *buffer, int length)
  1640. The default constructor, tstream() initializes the buffer using default
  1641. parameters for buffer size. The stream is considered closed. tstream(const
  1642. char *name, int mode, int prot) initializes the buffer, and opens the named
  1643. stream. tstream(T &f) initializes the buffer with the T parameter. tstream(T
  1644. &f, char *buffer, int length) initializes the buffer with T, and sets the
  1645. buffer to the char * with the length specified. These four constructors are
  1646. duplicated in the three classes itstream<T>, otstream<T>, and tstream<T>. In
  1647. all of these cases, the parameters are passed on directly to tstreambase<T>'s
  1648. constructors.
  1649. The constructors for tstreambase<T> call the constructors either for the
  1650. default tbuf<T> constructor, or in the case of constructors tstream(T &f) and
  1651. tstream(T &f, char *buffer, int length) it passes the parameters to tbuf<T>'s
  1652. constructors. It then calls the ios init function to set ios pointer to its T
  1653. data member. For constructor tstream (const char *name, int mode, int prot),
  1654. it calls T's open function.
  1655. The tbuf<T> class has three constructors:
  1656. tbuf()
  1657. tbuf(T &f)
  1658. tbuf(T &f, char *buffer, int length)
  1659. tbuf(), the default constructor, builds a buffer of default size. The T stream
  1660. is considered closed. tbuf(T &f) builds a default buffer, but attaches T as
  1661. its stream. The T stream is considered open for read or write. tbuf(T &f, char
  1662. *buffer, int length) builds a buffer using the buffer and length parameters,
  1663. and attaches T as its stream.
  1664.  
  1665.  
  1666. SerialStream and ComBuffer classes
  1667.  
  1668.  
  1669. As an example of how to use these templates, I decided to use a serial port
  1670. stream. In MS-DOS you can access the serial port as a stream, but it is not
  1671. interrupt driven, so it is nearly useless without special drivers. The serial
  1672. port stream is divided into two classes, SerialStream, and ComBuffer. The
  1673. ComBuffer provides an interrupt-driven circular buffer for input, and passes
  1674. output directly to the port. The Serial-Stream class uses the ComBuffer class
  1675. to do its I/O, and has the correct interface to plug into the template. I
  1676. split the classes in two in case there was more then one stream per port.
  1677. However, ComBuffer needed to be mapped directly to a port.
  1678. The only instance of ComBuffer is a static global (see Listing 2 and Listing
  1679. 3) CommPorts which is an array of two, one for each port. Since it is a static
  1680. global, it is initialized before main. It uses the static data member initPort
  1681. to ensure the correct port number is initialized. In the constructor and
  1682. destructor for ComBuffer I included a print statement to visually assert the
  1683. order of the construction. ComBuffer is not a safe or complete class that
  1684. could be used anywhere in a program, so I made the definition private to the
  1685. module. I could alternatively have made the definition private to the
  1686. SerialStream class definition.
  1687. The SerialStream class uses ComBuffer to communicate with the physical ports,
  1688. via a pointer. When a SerialStream is opened, the name parameter is decoded to
  1689. give the port number. The port number is used as an index into the CommPorts
  1690. array, and the pointer to that index is saved. During reading and writing the
  1691. request is passed on to ComBuffer via that pointer. Only one character is read
  1692. or written at a time. This inefficiency does not concern me, since the
  1693. iostream should be unbuffered, and should only request one character at a time
  1694. anyway.
  1695. Any class that is to be used in the streams templates must meet certain
  1696. requirements. First it needs a default constructor. This constructor should
  1697. leave the stream in a closed state. It needs a open function that takes a
  1698. const char *, and two ints for parameters. This is perhaps the most extreme
  1699. restriction, as not all streams will be able to map these parameters. For
  1700. streams that take fewer parameters, as my SerialStream class does, it is easy
  1701. enough to ignore the rest. For a class that needs more information to get
  1702. started, this can be a problem. One alternative could be to use the name
  1703. parameter to pass in additional information:
  1704. x.open("COM1, INT=7, SPEED=2400",ios::in);
  1705. Although this appears sloppy, and requires character translation in the open
  1706. function, it is not without precedent. Another alternative is to access the T
  1707. class directly:
  1708. x.open("COM1",ios::in);
  1709. x.rdbuf()->fd()->setspeed(1200);
  1710. x.rdbuf()->fd()->setint(7);
  1711. The stream class must also define a close function that takes no parameters. A
  1712. read and write function that takes a char * and an int, as well as a seek
  1713. function that takes a long and a ios::seek_dir type must be present. Finally,
  1714. a const void * conversion operator needs to return the open/close state, as
  1715. the this pointer or a null pointer. The open, close, read, write, and seek
  1716. function can all be dummied up if not needed. If seek is dummied, you need to
  1717. make sure the stream is unbuffered.
  1718. I mentioned previously that the portability of the templates depends on how
  1719. similarly different libraries implement the internals of the iostreams
  1720. classes. This code was made with the Borland C++ v3.1 libraries in mind, and
  1721. might need to be changed for other implementations. In the header files,
  1722. Borland mentions minor differences with the AT&T library, so I assume that the
  1723. templates will work as well under AT&T and any other vendor that follows them.
  1724. If the internals of iostreams are not under discussion in the ANSI X6J16
  1725. commitee, then perhaps vendors should include a set of templates similar to
  1726. these in the C++ library to allow different streams types to be portable from
  1727. one implementation to another.
  1728. iostreams Hierarchy
  1729. To provide seamless integration with existing code you need to create new
  1730. classes that fit in with the existing iostreams hierarchy. To illustrate some
  1731. of the relationships, Figure 1 contains a diagram of the ifstream path of the
  1732. iostreams class hierarchy.
  1733. At the bottom of the hierarchy is the ifstream, ofstream, and fstream classes.
  1734. You would normally use these classes when dealing with files as streams. These
  1735. classes provide no new functions or data members, and each is completely
  1736. dependent on its base classes for its abilities. They are derived multiply
  1737. from fstreambase and one of the istream, ostream, or iostream classes.
  1738. The istream class gives ifstream all of its formatted input functionality. The
  1739. >> operator is defined for all basic types, and get, getline, and read are
  1740. also defined for reading. In addition, the control functions gcount, ignore,
  1741. peek, putback, seekg, and tellg are defined. You should use the istream class
  1742. whenever you create input functions for your user-defined types. The ostream
  1743. class is similar but for output streams. The ostream class defines the
  1744. functions flush, put, seekp, tellp, and write along with the < < operator. The
  1745. iostream class is a "shell" class that simply inherits istream and ostream.
  1746. All of these classes are virtually derived from the ios class. I will explain
  1747. more on this later.
  1748. fstreambase gives fstream the ability to open and close a file. fstreambase
  1749. defines the functions attach, close, open, rdbuf, and setbuf. It also contains
  1750. a single data member, a filebuf type. This filebuf type gives fstream the
  1751. ability to read and write, though not directly. The read/write capability is
  1752. given to istream/ostream through the virtual base class ios.
  1753. Both fstreambase and istream/ostream are derived from ios. Since it is a
  1754. virtual derivation, both of the classes share the same copy of ios's data
  1755. members. Among these members are a pointer to a streambuf type. fstreambase
  1756. gives ios a pointer to its filebuf object during initialization. During use,
  1757. istream/ostream uses this pointer to access the filebuf for reading and
  1758. writing. The ios class also has various format and state flags and numerous
  1759. functions for setting and reading these flags.
  1760. The filebuf class is the workhorse of the hierarchy. It is the one that
  1761. actually calls for reads and writes from the file. It also handles opening,
  1762. closing, seeking, and buffering. When an fstream is opened, the fstream-base
  1763. open function calls filebuf's open which calls the global open. The filebuf
  1764. class is derived from streambuf.
  1765.  
  1766. The streambuf class is an abstract type that allows ios to communicate with
  1767. different types of buffer classes. Since ios contains a pointer to a streambuf
  1768. type it can communicate with filebuf through virtual functions. The virtual
  1769. functions overflow, underflow, seekoff, setbuf, and sync provide ios and
  1770. istream/ostream the ability to read, write, and seek.
  1771. Templates and Virtual Functions
  1772. Template classes allow a generic class to be defined. Individual objects can
  1773. be declared from it by filling in type information in the individual
  1774. declarations. The classic template example is the array-class. Once the array
  1775. class template is defined somewhere, various types of arrays can be declared:
  1776. // an array of ints:
  1777. Array<int> ArrayOfInts;
  1778. // an array of doubles:
  1779. Array<double> ArrayOfDoubles;
  1780. The compiler will generate the code needed for access and initialization of
  1781. each of the above arrays. If the compiler/linker can optimize, it will
  1782. automatically remove similar code from different modules. Be aware that each
  1783. of these Array declarations will produce its own code. This may be more code
  1784. then you might want. There are ways around this, but that is the general
  1785. limitation of templates.
  1786. Virtual functions can be seen as alternatives to templates. While templates
  1787. provide generic source code, virtual functions provide generic object code.
  1788. Where templates are generally faster, virtual functions generally produce less
  1789. code. This trade off is not always true, and there are other disadvantages on
  1790. both sides. I have found however that virtual functions and templates often
  1791. complement one another, and that very good solutions can be found in the
  1792. mixture of the two. In the above example one might define:
  1793. // an array of anything derived from
  1794. // Numerics:
  1795. Array<Numerics *> ArrayOfNumbers;
  1796. Then if Numerics is used as a base class, anything derived from Numerics can
  1797. be used in this array, and Numerics provides the least-common-denominator for
  1798. operations available for ArrayOfNumbers.
  1799. Figure 1 The ifstream path of the iostreams class hierarchy
  1800.  
  1801. Listing 1 tstream.h -- template class definitions
  1802. #if !defined TSTREAM_H
  1803. #define TSTREAM_H
  1804.  
  1805. #include <iostream.h> // for base class definitions
  1806.  
  1807. // class tbuf<>. Derived publicly from streambuf to
  1808. // allow class ios which contains pointer to streambuf
  1809. // access to virtual functions. tbuf<> implements
  1810. // basic buffering, reading, writing and seeking on
  1811. // a stream. It also provides open, close, attach,
  1812. // and utility functions.
  1813. template <class T>
  1814. class tbuf : public streambuf {
  1815. public:
  1816. // openProtection provides a default parameter to the
  1817. // open functions to specify what protection a file
  1818. // will be created with. You can ignore this if it is
  1819. // not necessary
  1820. static const int openProtect;
  1821. // tbufSize specifies the default buffer size. It is
  1822. // set to 516 bytes.
  1823. static const int tbufSize;
  1824. // Default contructor. Make a buffer without a
  1825. // stream attached. mode has a dual meaning, if it
  1826. // is zero it means that any operation is allowable,
  1827. // and the stream should not be deleted when closing.
  1828. tbuf()
  1829. : stream(0), mode(0), opened(0)
  1830. {
  1831. makbuf();
  1832. }
  1833. // create buffer and attach to t. t is assumed to be
  1834. // already opened in read/write mode. t will not be
  1835. // deleted or closed when closing this buffer
  1836. tbuf(T &t)
  1837. : stream(&t), mode(0), opened(1)
  1838. {
  1839. makbuf();
  1840. }
  1841. // create buffer from parameters, and attach to t.
  1842. tbuf(T &t, char* b, int l)
  1843. : stream(&t), mode(0), opened(1)
  1844. {
  1845.  
  1846. setbuf(b, l);
  1847. }
  1848. // destroy buffer. If mode is not zero, t will be
  1849. // closed and deleted. Otherwise just flush the
  1850. // output buffer.
  1851. ~tbuf()
  1852. {
  1853. if(mode)
  1854. close();
  1855. else
  1856. overflow(EOF);
  1857. }
  1858. // return open status
  1859. int is_open() const
  1860. {
  1861. return opened;
  1862. }
  1863. // return reference to stream
  1864. T &fd() const
  1865. {
  1866. return *stream;
  1867. }
  1868. // open stream. mode must not be zero. stream will
  1869. // be closed and deleted when closing buffer.
  1870. tbuf *open(const char *name, int mode,
  1871. int prot = tbuf::openProtect);
  1872. // close buffer and optionally delete stream.
  1873. tbuf *close();
  1874. // attach stream to buffer. Stream is assumed to
  1875. // be opened in read/write mode.
  1876. tbuf *attach(T &);
  1877. // write buffer to stream and reset pointers.
  1878. virtual int overflow(int = EOF);
  1879. // read data into buffer and reset pointers.
  1880. virtual int underflow();
  1881. // sync input and output.
  1882. virtual int sync();
  1883. // seek to offset and flush output buffers.
  1884. virtual long seekoff(long, ios::seek_dir, int);
  1885. // set buffer. For unbuffered i/o set char * to 0.
  1886. virtual streambuf *setbuf(char *, int);
  1887. protected:
  1888. int putBackSize()
  1889. {
  1890. return (blen() > 8) ? 4 : 1;
  1891. }
  1892. void resetpg(int end = 0);
  1893. void makbuf()
  1894. {
  1895. setbuf(new char[tbufSize], tbufSize);
  1896. }
  1897. int unbuffered()
  1898. {
  1899. return streambuf::unbuffered() !base();
  1900. }
  1901. T *stream;
  1902. int mode;
  1903. short opened;
  1904. char lookAhead[2];
  1905.  
  1906. };
  1907.  
  1908. template <class T>
  1909. const int tbuf<T>::tbufSize = 516;
  1910. template <class T>
  1911. const int tbuf<T>::openProtect = 0;
  1912.  
  1913. // Attach an open stream to this buffer. When this
  1914. // buffer is closed don't try to close stream. If
  1915. // not yet buffered, try to create a buffer. Reset
  1916. // the put and get pointers. Return 0 on error, or
  1917. // this if OK.
  1918. template <class T>
  1919. tbuf<T>* tbuf<T>::attach(T &t)
  1920. {
  1921. if(opened) // if already opened, return 0
  1922. return 0;
  1923. stream = &t; // attach stream.
  1924. opened = 1; // set to opened.
  1925. mode = 0; // buffer doesn't own stream.
  1926. if(!base()) // if no buffer yet...
  1927. makbuf(); // try to make one.
  1928. else
  1929. resetpg(); // reset put and get pointers.
  1930. return this;
  1931. }
  1932.  
  1933. // Close buffer, and optionally stream attached to it.
  1934. // Flush buffer if data inside. Return 0 on error or
  1935. // this if OK.
  1936. template <class T>
  1937. tbuf<T>* tbuf<T>::close()
  1938. {
  1939. if(!*stream) // if stream closed,
  1940. opened = 0; // set opened off.
  1941. if(!opened) // if not on return 0.
  1942. return 0;
  1943. int ret = 0;
  1944. if(out_waiting()) // if output in buffer.
  1945. // flush output buffer.
  1946. ret = (overflow(EOF) == EOF) ? 1 : 0;
  1947. if(mode) // if mode is 0, don't
  1948. delete stream; // close or delete stream.
  1949. opened = 0; // set opened off.
  1950. return ret ? 0 : this;
  1951. }
  1952. // Write data from buffer to stream. This function
  1953. // is called when the buffer is full and we need to
  1954. // output more characters. The parameter c is the
  1955. // character that caused the overflow. If the
  1956. // stream is buffered, it will be placed in the
  1957. // buffer, otherwise it is written to the stream.
  1958. // If input char is EOF, don't write, just flush.
  1959. // Returns EOF on error, or 1 on success.
  1960. template <class T>
  1961. int tbuf<T>::overflow(int c)
  1962. {
  1963. if(!opened // check to see if stream
  1964. mode == 0 // is on and mode is out.
  1965.  
  1966. !(mode&ios::out))
  1967. return EOF;
  1968. if(unbuffered())
  1969. {
  1970. if(c ! = EOF)
  1971. { // if unbuffered,
  1972. char b = c; // write single char
  1973. if(stream->write(&b, 1) != 1)
  1974. return EOF;
  1975. }
  1976. }
  1977. else // else if buffered.
  1978. {
  1979. if(sync() != 0) // sync input and output
  1980. return EOF; // when writing
  1981. resetpg(1); // reset the put/get pointers
  1982. if(c != EOF)
  1983. {
  1984. sputc(c); // add c to the buffer
  1985. gbump(1); // move the get pointer
  1986. }
  1987. }
  1988. return 1; // return OK
  1989. }
  1990.  
  1991. // Open stream. If mode ios::ate (at end) is on,
  1992. // seek to end
  1993. template <class T>
  1994. tbuf<T>* tbuf<T>::open(const char* n, int m, int p)
  1995. {
  1996. if(opened !m) //if already on, or no mode,
  1997. return 0; // return error.
  1998. stream = new T; // make new stream pointer.
  1999. stream->open(n, m, p);// open stream.
  2000. if(!*stream) // if stream not open,
  2001. return 0; // return error.
  2002. opened = 1; // set to on.
  2003. mode = m; // remeber mode.
  2004. if((mode & ios::ate) &&
  2005. stream->seek(OL, ios::end) == EOF)
  2006. return 0; // seek to end if ios::ate.
  2007. resetpg(); // reset put/get pointers.
  2008. return this; // return OK.
  2009. }
  2010.  
  2011. // Set the buffer, reset the put/get pointers.
  2012. // Return 0 on error, this if OK.
  2013. template <class T>
  2014. streambuf* tbuf<T>::setbuf(char* b, int l)
  2015. {
  2016. if(!b) // turn off buffering.
  2017. {
  2018. streambuf::unbuffered(1);
  2019. return this;
  2020. }
  2021. if(opened && base())// check if stream is opened,
  2022. return 0; // , and no buffer yet.
  2023. setb(b, b+l, 0); // set buffer pointers.
  2024. resetpg(); // reset put/get pointers.
  2025.  
  2026. return this; // return OK.
  2027. }
  2028.  
  2029. // Seek to offset. dir indicates the relativity of
  2030. // the offset, either from beginning, current position,
  2031. // or end (ios::beg, ios::cur, ios::end).
  2032. // First make sure there's nothing in the buffer.
  2033. template <class T>
  2034. long tbuf<T>::seekoff(long off, ios::seek_dir dir,
  2035. int /* mode ignored */)
  2036. {
  2037. long loff = off;
  2038. int count = out_waiting(); // flush output first.
  2039. if(count)
  2040. {
  2041. if(stream->write(pbase(), count) != count)
  2042. return EOF;
  2043. }
  2044. else if(dir == ios::cur && // if relative seek,
  2045. (count = in_avail()) != 0)
  2046. loff -= count; // discount input.
  2047. resetpg(); // reset pointers.
  2048. return stream->seek(loff, dir);
  2049. }
  2050. // sync input and output buffer pointers. If output
  2051. // is waiting, write it, if input is waiting,
  2052. // back up to read it again
  2053. template <class T>
  2054. int tbuf<T>::sync()
  2055. {
  2056. if (!opened) // check if opened.
  2057. return EOF;
  2058. int count = out_waiting(); // check for output,
  2059. if(count) // in buffer.
  2060. {
  2061. if(stream->write(pbase(), count) != count)
  2062. return EOF; // write output.
  2063. resetpg(1);
  2064. }
  2065. else if(in_avail()) // check for input
  2066. { // in buffer
  2067. long pos = stream->seek(long(-in_avail()),
  2068. ios::cur);
  2069. setg(eback(), gptr(), gptr());
  2070. setp(gptr(), gptr()); // set up to re-read.
  2071. if(pos == EOF)
  2072. return EOF;
  2073. }
  2074. return 0;
  2075. }
  2076.  
  2077. // Read from stream to fill buffer. Must be opened and
  2078. // in input mode. If there is input in the buffer,
  2079. // return it. Otherwise call sync, read from stream,
  2080. // and set pointers. If the input is unbuffered,
  2081. // use the lookahead buffer.
  2082. template <class T>
  2083. int tbuf<T>::underflow()
  2084. {
  2085.  
  2086. if(!opened mode == 0 !(mode&ios::in))
  2087. return EOF; // check for correct mode.
  2088. if(in_avail()) // if available from buffer,
  2089. return *gptr()&0xFF; // don't bother.
  2090. int c;
  2091. int count;
  2092. if(unbuffered()) // if unbuffered.
  2093. {
  2094. count = stream->read(lookAhead, 1);
  2095. if(count == EOF) // read one char
  2096. { // into look ahead
  2097. c = EOF; // buffer.
  2098. setg(0, 0, 0);
  2099. }
  2100. else
  2101. {
  2102. c = lookAhead[0]&0xFF;
  2103. setg(lookAhead, lookAhead, lookAhead+1);
  2104. }
  2105. }
  2106. else // else buffered.
  2107. {
  2108. if(sync() != 0) // sync pointers.
  2109. return EOF;
  2110. int pb = putBackSize();
  2111. char *b = base(); // read into buffer.
  2112. count = stream->read(b+pb, blen()-pb);
  2113. if(count == EOF) // check read return.
  2114. return EOF;
  2115. setg(b, b+pb, b+pb+count);
  2116. setp(b+pb, b+pb); // set pointers.
  2117. if(count) // return first char.
  2118. c = *gptr()&0xFF;
  2119. }
  2120. if(!count)
  2121. c = EOF;
  2122. return c;
  2123. }
  2124.  
  2125. // reset the put and get pointers. If end is
  2126. // true set the end pointers to the end of
  2127. // the buffer.
  2128. template<class T>
  2129. void tbuf<T>::resetpg(int end)
  2130. {
  2131. char *buf = base(); // get the start of buffer.
  2132. char *sbuf = buf + (buf ? putBackSize() : 0);
  2133. char *ebuf; // set start and end pointers.
  2134. if(end)
  2135. ebuf = buf + blen();
  2136. else
  2137. ebuf = sbuf;
  2138. setp(sbuf, ebuf); // set put pointer
  2139. setg(buf, sbuf, sbuf);// set get pointer
  2140. }
  2141.  
  2142. // tstreambase is virtually derived from ios. ios
  2143. // does not define any functionality for tstreambase
  2144. // but allows communication between tstreambase and
  2145.  
  2146. // istream, ostream and stream classes. The functions
  2147. // defined in tstreambase typically call the same
  2148. // named function for its tbuf<> member and set ios
  2149. // status flags. When a tstreambase is constructed
  2150. // ios is initialized with a pointer to the tbuf<>
  2151. // member. ios see this pointer as a streambuf
  2152. // pointer, and only has access to tbuf<> through the
  2153. // virtual functions, overflow, underflow, sync, and
  2154. // seekoff.
  2155. template <class T>
  2156. class tstreambase : virtual public ios {
  2157. public:
  2158. // Default constructor. Make an unattached
  2159. // buffer, and initialize ios with it's pointer.
  2160. tstreambase()
  2161. : tbbuf()
  2162. {
  2163. ios::init(&tbbuf);
  2164. }
  2165. // Make a buffer attach to named stream, and open
  2166. // stream
  2167. tstreambase(const char* n, int m,
  2168. int p = tbuf<T>::openProtect)
  2169. : tbbuf()
  2170. {
  2171. ios::init(&tbbuf);
  2172. open(n, m, p);
  2173. }
  2174. // Make a buffer attached to already opened stream.
  2175. tstreambase(T &f)
  2176. : tbbuf(f)
  2177. {
  2178. ios::init(&tbbuf);
  2179. }
  2180. // Make a buffer using parameters, and attach
  2181. // to already opened stream.
  2182. tstreambase(T &f, char* b, int l)
  2183. : tbbuf(f, b, l)
  2184. {
  2185. ios::init(&tbbuf);
  2186. }
  2187. // Destroy buffer
  2188. ~tstreambase()
  2189. {
  2190. ;
  2191. }
  2192. // close attached stream and set ios flags.
  2193. void close()
  2194. {
  2195. if(tbbuf.close())
  2196. clear(ios::goodbit);
  2197. else
  2198. setstate(ios::failbit);
  2199. }
  2200. // set buffer and set ios flags.
  2201. void tstreambase::setbuf(char* b, int l)
  2202. {
  2203. if(tbbuf.setbuf(b, l))
  2204. clear(ios::goodbit);
  2205.  
  2206. else
  2207. setstate(ios::failbit);
  2208. }
  2209. // open stream and set ios flags.
  2210. void open(const char *, int,
  2211. int = tbuf<T>::openProtect);
  2212. // attach opened stream and set ios flags.
  2213. void attach(T &);
  2214. // return the buffer.
  2215. tbuf<T> *rdbuf()
  2216. {
  2217. return &tbbuf;
  2218. }
  2219. private:
  2220. tbuf<T> tbbuf;
  2221. };
  2222.  
  2223. // Attempt to open tbuf<>, and set ios flags
  2224. // accordingly. Opening an already open stream
  2225. // results in an error. If ios::app (append to
  2226. // file) is set, also set ios::out. If ios::out
  2227. // is set, and ios::app, ios::in, and ios::ate
  2228. // (start at end) are not set, set the ios::trunc bit.
  2229. template <class T>
  2230. void tstreambase<T>::open(const char *n, int m, int p)
  2231. {
  2232. if(m & ios::app)
  2233. m = ios::out;
  2234. else if((m & (ios::outios::ateios::appios::in))
  2235. == ios::out)
  2236. m = ios::trunc;
  2237. if(tbbuf.is_open())
  2238. clear(ios::failbit);
  2239. else if(tbbuf.open(n, m, p))
  2240. clear(ios::goodbit);
  2241. else
  2242. clear(ios::badbit);
  2243. }
  2244.  
  2245. // Attach an opened stream to buffer, and set the ios
  2246. // bits accordingly.
  2247. template <class T>
  2248. void tstreambase<T>::attach(T &f)
  2249. {
  2250. if(tbbuf.is_open())
  2251. setstate(ios::failbit);
  2252. else if(tbbuf.attach(f))
  2253. clear(ios::goodbit);
  2254. else
  2255. clear(ios::badbit);
  2256. }
  2257.  
  2258. // The itstream, otstream and tstream class are merely
  2259. // "shell" classes, and serve only to combine the
  2260. // functionality of it's base class. The only functions
  2261. // defined are the constructor and destructors, and
  2262. // open and rdbuf. There are no addition data members
  2263. // and all functions call directly the functions of the
  2264. // base class. The default open mode is ios::in for
  2265.  
  2266. // itstream, ios::out for otstream, and both for
  2267. // tstream.
  2268. template <class T>
  2269. class itstream : public tstreambase<T>,
  2270. public istream {
  2271. public:
  2272. itstream()
  2273. {
  2274. ;
  2275. }
  2276. itstream(const char* n, int m = ios::in,
  2277. int p = tbuf<T>::openProtect)
  2278. : tstreambase<T>(n, m ios::in, p)
  2279. {
  2280. ;
  2281. }
  2282. itstream(T &f)
  2283. : tstreambase<T>(f)
  2284. {
  2285. ;
  2286. }
  2287. itstream(T &f, char* b, int l)
  2288. : tstreambase<T>(f, b, l)
  2289. {
  2290. ;
  2291. }
  2292. ~itstream()
  2293. {
  2294. ;
  2295. }
  2296. tbuf<T> *rdbuf()
  2297. {
  2298. return tstreambase<T>::rdbuf();
  2299. }
  2300. void open(const char *n, int m = ios::in,
  2301. int p = tbuf<T>::openProtect)
  2302. {
  2303. tstreambase<T>::open(n, m ios::in, p);
  2304. }
  2305. };
  2306. template <class T>
  2307. class otstream : public tstreambase<T>,
  2308. public ostream {
  2309. public:
  2310. otstream()
  2311. {
  2312. ;
  2313. }
  2314. otstream(const char* n, int m = ios::out,
  2315. int p = tbuf<T>::openProtect)
  2316. : tstreambase<T>(n, m ios::out, p)
  2317. {
  2318. ;
  2319. }
  2320. otstream(T &f)
  2321. : tstreambase<T>(f)
  2322. {
  2323. ;
  2324. }
  2325.  
  2326. otstream(T &f, char* b, int l)
  2327. : tstreambase<T>(f, b, l)
  2328. {
  2329. ;
  2330. }
  2331. ~otstream()
  2332. {
  2333. ;
  2334. }
  2335. tbuf<T> *rdbuf()
  2336. {
  2337. return tstreambase<T>::rdbuf();
  2338. }
  2339. void open(const char *n, int m = ios::out,
  2340. int p = tbuf<T>::openProtect)
  2341. {
  2342. tstreambase<T>::open(n, m ios::out, p);
  2343. }
  2344. };
  2345.  
  2346. template <class T>
  2347. class tstream : public tstreambase<T>,
  2348. public iostream {
  2349. public:
  2350. tstream()
  2351. {
  2352. ;
  2353. }
  2354. tstream(const char *n, int m,
  2355. int p = tbuf<T>::openProtect)
  2356. : tstreambase<T>(n, m, p)
  2357. {
  2358. ;
  2359. }
  2360. tstream(T &f)
  2361. : tstreambase<T>(f)
  2362. {
  2363. ;
  2364. }
  2365. tstream(T &f, char *b, int l)
  2366. : tstreambase<T>(f, b, l), iostream()
  2367. {
  2368. ;
  2369. }
  2370. ~tstream()
  2371. {
  2372. ;
  2373. }
  2374. tbuf<T> *rdbuf()
  2375. {
  2376. return tstreambase<T>::rdbuf();
  2377. }
  2378. void open(const char *n, int m,
  2379. int p = tbuf<T>::openProtect)
  2380. {
  2381. tstreambase<T>::open(n, m, p);
  2382. }
  2383. };
  2384. #endif
  2385.  
  2386. /* End of File */
  2387.  
  2388.  
  2389. Listing 2 tserial.h -- serial stream class definitions
  2390. #if !defined SERIAL_H
  2391. #define SERIAL_H
  2392.  
  2393. #include <iostream.h>
  2394.  
  2395. class ComBuffer; // forward declaration
  2396.  
  2397. // This class represents everything needed for the
  2398. // template parameter. It has a default constructor,
  2399. // open, close, read, write, seek, and status.
  2400. class SerialStream {
  2401. public:
  2402. // Default constructor. Initialize an unopened stream
  2403. SerialStream()
  2404. : port(-1)
  2405. {
  2406. }
  2407. // Destructor. calls close.
  2408. ~SerialStream()
  2409. {
  2410. close();
  2411. }
  2412. // The open function sets up the default serial port
  2413. // parameters, and hook up the interrupts. The name
  2414. // is check for "COM1" and "COM2". mode and prot are
  2415. // ignored.
  2416. int open(const char *name, int mode, int prot);
  2417. // Read fills buf with len characters from the
  2418. // circular buffer. This class and any class that
  2419. // cannot seek should be unbuffered, so read typically
  2420. // asks for one character at a time.
  2421. int read(char *buf, size_t len);
  2422. // Write sends the characters in buf to the port.
  2423. int write(char *buf, size_t len);
  2424. // Seek is a dummy function and always retruns error.
  2425. long seek(long, ios::seek_dir) { return -1; }
  2426. // close set the port number to -1 for open testing.
  2427. int close();
  2428. // This function is the status function.
  2429. operator const void *() { return port == -1 ? 0 :
  2430. this; }
  2431. private:
  2432. int port;
  2433. ComBuffer *buffPtr;
  2434. };
  2435.  
  2436. #endif
  2437.  
  2438. /* End of File */
  2439.  
  2440.  
  2441. Listing 3 tserial.cpp -- serial port stream classes
  2442. #include <iostream.h>
  2443. #include "tserial.h"
  2444. #include <string.h>
  2445.  
  2446. #include <dos.h>
  2447. #include <bios.h>
  2448. #include <conio.h>
  2449.  
  2450. // interrupt functions.
  2451. typedef void interrupt far intFunc(...);
  2452.  
  2453. // Programmed Interrupt Controller constants.
  2454. const PICO : 0x20;
  2455. const PIC1 : 0x21;
  2456. const EOI : 0x20;
  2457.  
  2458. // Offsets from port address.
  2459. const InterruptEnable = 1,
  2460. InterruptIdent = 2,
  2461. ModemControl = 4,
  2462. LineStatus = 5,
  2463. ModemStatus = 6;
  2464.  
  2465. // Value for the modem control register.
  2466. const ModemControlValue = 11;
  2467.  
  2468. // A serial port circular buffer class. There
  2469. // are only two instances of this class, one
  2470. // for each port COM1, and COM2. They are
  2471. // static globals, and are initailized before
  2472. // main(). They contain a circular buffer,
  2473. // the port number and port address, and the
  2474. // interrupt vector they replace, so that
  2475. // they can be restored at destruction. This
  2476. // is not a complete or robust class, and is
  2477. // private to this module. It is intended
  2478. // only for use by SerialStream.
  2479. class ComBuffer {
  2480. public:
  2481. // Size of circular buffer
  2482. enum{ BufSize = 0x100 };
  2483. // Default constructor.
  2484. ComBuffer();
  2485. ~ComBuffer();
  2486. // Set up vector, and enables interrupts
  2487. void hookInterrupt();
  2488. // Get a single character from the buffer
  2489. int getc();
  2490. // Send a single character to the port
  2491. void putc(int c);
  2492. // Read the line status register.
  2493. int status() const
  2494. {
  2495. return ::inp(portAddr+LineStatus);
  2496. }
  2497. // Check if characters are available
  2498. int avail() const
  2499. {
  2500. return in != out;
  2501. }
  2502. // Receive a character from port. This function
  2503. // is called by the interrupt routine.
  2504. void receive();
  2505.  
  2506. private:
  2507. // The mask needed for setting the PIC.
  2508. int interruptMask(int port) const
  2509. {
  2510. return 1 << (4 - port);
  2511. }
  2512. // The interrupt number for each port.
  2513. int interruptVec(int port) const
  2514. {
  2515. return 12 - port;
  2516. }
  2517. char *in, *out, *buff; // The circular buffer
  2518. int port; // The port number 0-1
  2519. int portAddr; // The port address
  2520. intFunc *oldVector; // The previos vector
  2521. static int initPort;
  2522. };
  2523.  
  2524. // initPort is used by the constructor to initialize
  2525. // the ports in sequence.
  2526. int ComBuffer::initPort = 0;
  2527. // Only two ports are set up here. If you add more
  2528. // you need to identify the port addresses and
  2529. // interrupt vectors.
  2530. static ComBuffer CommPorts[2];
  2531.  
  2532. // Initialize the buffers. Allocate the circular
  2533. // buffer, set the in/out pointers, set the port
  2534. // and port address values, and clear the old
  2535. // vector value.
  2536. ComBuffer::ComBuffer()
  2537. : buff(new char[BufSize]), port(initPort++),
  2538. oldVector(0)
  2539. {
  2540. // this line is for sceptics to see initializing.
  2541. cout << "initializing port #" << port << endl;
  2542. portAddr = port ? 0x2f8 : 0x3f8;
  2543. in = out = buff;
  2544. }
  2545.  
  2546. // Delete the circular buffer. If the old vector
  2547. // is a valid address, restore it.
  2548. ComBuffer::~ComBuffer()
  2549. {
  2550. // another line for sceptics.
  2551. cout << "de-initializing port #" << port << endl;
  2552. if(oldVector)
  2553. ::setvect(interruptVec(port),oldVector);
  2554. delete[] buff;
  2555. }
  2556.  
  2557. // These two routines reset to PIC, and put
  2558. // the new character in the buffer
  2559. void interrupt far comInterrupt0(...)
  2560. {
  2561. ::outp(PICO,EOI);
  2562. CommPorts[0].receive();
  2563. }
  2564. void interrupt far comInterruptl(...)
  2565.  
  2566. {
  2567. ::outp(PICO,EOI);
  2568. CommPorts[1].receive();
  2569. }
  2570.  
  2571. // This function sets up the interrupts, and
  2572. // enables the PIC
  2573. void
  2574. ComBuffer::hookInterrupt()
  2575. {
  2576. if(oldVector==0)
  2577. oldVector = ::getvect(interruptVec(port));
  2578. switch(port)
  2579. {
  2580. case 0:
  2581. ::setvect(interruptVec(port),comInterrupt0);
  2582. break;
  2583. case 1:
  2584. ::setvect(interruptVec(port),comInterrupt1);
  2585. break;
  2586. default:
  2587. return;
  2588. }
  2589. ::outp(portAddr+ModemControl,
  2590. ::inp(portAddr+ModemControl)ModemControlValue);
  2591. ::outp(PIC1,::inp(PIC1) & ~interruptMask(port));
  2592. ::outp(portAddr+InterruptEnable,1);
  2593. ::outp(PICO, EOI);
  2594. ::inp(portAddr);
  2595. ::inp(portAddr+InterruptIdent);
  2596. ::inp(portAddr+ModemStatus);
  2597. status(); // clear status reg.
  2598. }
  2599.  
  2600. // Receive a byte from the port and place in the
  2601. // circular buffer, called from the interrupt
  2602. // handler.
  2603. void
  2604. ComBuffer::receive()
  2605. {
  2606. if(in == buff + BufSize)
  2607. in = buff; // circular buffer
  2608. *in++ = ::inp(portAddr);
  2609. }
  2610.  
  2611. // Get a character from the circular buffer.
  2612. int
  2613. ComBuffer::getc()
  2614. {
  2615. while(!avail())
  2616. if(::kbhit())
  2617. return -1;
  2618. if(out == buff + BufSize)
  2619. out = buff; // circular buffer
  2620. return *out++;
  2621. }
  2622.  
  2623. // Send a character directly to the port
  2624. void
  2625.  
  2626. ComBuffer::putc(int c)
  2627. {
  2628. while((status() & 0x20) == 0)
  2629. if(::kbhit())
  2630. return;
  2631. ::outp(portAddr, c);
  2632. }
  2633.  
  2634. const default_init =_COM_1200_COM_CHR8
  2635. _COM_STOP1_COM_NOPARITY;
  2636.  
  2637. // The open function needs to translate the name
  2638. // to the port number, initialize the port, and
  2639. // enable interrupts.
  2640. int SerialStream::open(const char *name, int, int)
  2641. {
  2642. if(::stricmp(name,"COM1") == 0)
  2643. port = 0;
  2644. else if(::stricmp(name, "COM2") == 0)
  2645. port = 1;
  2646. else
  2647. return -1;
  2648. unsigned int stat = :: bios_serialcom(
  2649. _COM_INIT,port,default_init);
  2650. buffPtr = CommPorts+port;
  2651. buffPtr->hookInterrupt();
  2652. if(stat & 0xFF00)
  2653. return -1;
  2654. return 0;
  2655. }
  2656.  
  2657. // The close routine sets the port number to -1
  2658. // to indicate it's closed
  2659. int
  2660. SerialStream::close()
  2661. {
  2662. port = -1;
  2663. return 0;
  2664. }
  2665.  
  2666. // The read routine repeatedly calls ComBuffer::getc
  2667. // to fill it's buffer. len should always be 1.
  2668. int SerialStream::read(char *b, size_t len)
  2669. {
  2670. for(int i = 0; i < len; i++)
  2671. b[i] = buffPtr->getc();
  2672. return i;
  2673. }
  2674.  
  2675. // The write routine repeatedly calls ComBuffer::putc
  2676. // to empty it's buffer. len should alway be 1.
  2677. int SerialStream::write(char *b, size_t len)
  2678. {
  2679. for(int i = 0; i < len; i++)
  2680. buffPtr->putc(b[i]);
  2681. return i;
  2682. }
  2683.  
  2684. /* End of File */
  2685.  
  2686.  
  2687.  
  2688.  
  2689.  
  2690.  
  2691.  
  2692.  
  2693.  
  2694.  
  2695.  
  2696.  
  2697.  
  2698.  
  2699.  
  2700.  
  2701.  
  2702.  
  2703.  
  2704.  
  2705.  
  2706.  
  2707.  
  2708.  
  2709.  
  2710.  
  2711.  
  2712.  
  2713.  
  2714.  
  2715.  
  2716.  
  2717.  
  2718.  
  2719.  
  2720.  
  2721.  
  2722.  
  2723.  
  2724.  
  2725.  
  2726.  
  2727.  
  2728.  
  2729.  
  2730.  
  2731.  
  2732.  
  2733.  
  2734.  
  2735.  
  2736.  
  2737.  
  2738.  
  2739.  
  2740.  
  2741.  
  2742.  
  2743.  
  2744.  
  2745.  
  2746.  
  2747.  
  2748.  
  2749. A Safer setjmp in C++
  2750.  
  2751.  
  2752. Philip J. Erdelsky
  2753.  
  2754.  
  2755. Philip J. Erdelsky is an R&D engineer with over eight years experience in C
  2756. programming. He can be reached at Data/Ware Development, Inc., 9449 Carroll
  2757. Park Dr., San Diego, CA 92121, or on Compuserve at 75746,3411.
  2758.  
  2759.  
  2760. Calling setjmp to mark a place in your C program and then calling longjmp to
  2761. return to it, even from a deeply-nested function call, is considered useful
  2762. but slightly hazardous. If control has left the function that called setjmp,
  2763. longjmp will usually crash. Yet the careful use of this perverse pair is an
  2764. efficient way to transfer control when an error detected in a low-level
  2765. function must be handled at a much higher level. When using the alternative,
  2766. passing the error condition laboriously up the chain of functions, testing a
  2767. flag at each step, you must be careful not to go flying past an intermediate
  2768. function's garbage collection, leaving memory blocks unreleased, files open,
  2769. and motors running.
  2770. Surprisingly, setjmp and longjmp are still available in C++, and they still
  2771. work the same way. However, C++ provides a way around the principal danger of
  2772. setjmp and longjmp.
  2773.  
  2774.  
  2775. Technique
  2776.  
  2777.  
  2778. The most important part of this technique is to encapsulate the jmp_buf buffer
  2779. in a class object that has a constructor and a destructor:
  2780. #include <setjmp.h>
  2781.  
  2782. class error
  2783. {
  2784. error *previous;
  2785. public:
  2786. static error *current;
  2787. jmp_buf jmpbuf;
  2788. error() { previous = current; current = this; }
  2789. ~error() { current = previous; }
  2790. };
  2791.  
  2792. error *error::current;
  2793. The class has one static member called error::current, that points to the
  2794. current buffer, the one an error exit will use to make its escape.
  2795. If a function needs to handle an error discovered at a lower level, just begin
  2796. it this way:
  2797. function f(...)
  2798. {
  2799. <variable declarations>
  2800. error error_x;
  2801. // calls constructor for
  2802. // error_x
  2803. if (setjmp(error_x.jmpbuf))
  2804. {
  2805. <garbage collection>
  2806. return;
  2807. // calls destructor for
  2808. // error_x
  2809. }
  2810. <function code>
  2811. <garbage collection>
  2812. // destructor for error_x also
  2813. // called here
  2814. }
  2815. When the buffer error_x is created, its constructor is called. The constructor
  2816. code links the buffer to a chain of previous error buffers, if any, and makes
  2817. it the one pointed to by error::current. The if statement puts the context
  2818. into its jmpbuf member and returns a zero, so processing continues with the
  2819. function code.
  2820. When a lower level function detects an error, it calls
  2821. longjmp (error::current->jmpbuf,1);
  2822. This brings control back to the garbage-collection routines after the if
  2823. statement. When the return statement is executed, the destructor for error_x
  2824. is called, the buffer is removed from the chain, and any subsequent longjmp
  2825. (error::current->jmpbuf, 1) will use the previous buffer, not this one.
  2826. Moreover, if control returns from the function f without detecting an error,
  2827. the destructor for error_x is called automatically. These are the principal
  2828. safety features of this technique.
  2829. Actually, an important part of the garbage collection is performed
  2830. automatically by C++. The destructors for other variables, if any, are called
  2831. automatically.
  2832. If garbage collection is needed in some intermediate function, you have to be
  2833. a little more careful. You might put something like this at the head of the
  2834. intermediate function:
  2835. error error_x;
  2836.  
  2837. if (setjmp (error_x.jmpbuf))
  2838. {
  2839. error *p =
  2840. error:: current->previous;
  2841. <garbage collection>
  2842. error::~error(error_x);
  2843. <calls on other destructors>
  2844. longjmp (p->jmpbuf,1);
  2845. }
  2846. You can safely reuse the name error_x because each is local to its own
  2847. function.
  2848. Here is where you run into a little difficulty. C++ is very good about calling
  2849. destructors for local objects when control leaves their scope, but not when
  2850. exit is made via longjmp. Therefore, the destructors for error_x and other
  2851. local variables, if any, must be called explicitly before bailing out.
  2852. You can avoid this drudgery if you are willing to sacrifice portability and
  2853. are using a compiler like Borland C++ 2.0 that uses negative frame addressing.
  2854. Just use the function declare_error, followed by an immediate return, as shown
  2855. in Listing 1.
  2856. Negative frame addressing makes it possible for a carefully coded (but
  2857. admittedly nonportable) function like declare_error to find the return address
  2858. and patch it so that when the following return statement is executed, the
  2859. destructors for error_x, and other local variables, if any, are called, and
  2860. then return is made, not to calling function, but to the special function
  2861. error_exit, which then calls longjmp. Of course, this involves some additional
  2862. runtime overhead, but error exits are not common and need not be fast.
  2863. The function declare_error, followed by an immediate return statement, can
  2864. also be called when the error is first discovered, if the function from which
  2865. it is called needs to call destructors for some of its local variables.
  2866. Even if you have to do this for every intermediate function, you can still
  2867. save a lot of code if each intermediate function is called from many places.
  2868. Occasionally, an error occurs in a non-function block and needs to be handled
  2869. in a larger enclosing block. This technique doesn't work so well because there
  2870. is no practical way for declare_error to find the block exit. However, the old
  2871. break and goto statements will usually suffice in such cases.
  2872.  
  2873.  
  2874. Limitations
  2875.  
  2876.  
  2877. This technique isn't foolproof. It can still fail if an error is declared in a
  2878. function called by a constructor or initializer for a variable declared before
  2879. error_x:
  2880. f(...)
  2881. {
  2882. class something old;
  2883. int x = g();
  2884. error error_x;
  2885. if (setjmp (error_x.jmpbuf))
  2886. {
  2887. declare_error();
  2888. return;
  2889. }
  2890. If an error is declared in g, control will pass to a higher level, and the
  2891. destructor for old won't get called.
  2892. Don't even think of trying to get around this by declaring error_x first. If
  2893. an error is declared in g, an attempt will be made to pass control with an
  2894. uninitialized error_x.jmpbuf.
  2895. If memory deallocation is the only garbage collection involved, a more
  2896. sophisticated memory allocator should be able to straighten things out, but
  2897. such a technique is beyond the scope of this article.
  2898. This is a safer setjmp, about the safest available in C++. For a much safer
  2899. setjmp, you have to use other languages that may keep you from doing what you
  2900. really want to do.
  2901.  
  2902. Listing 1 An example of using negative frame addressing via the declare_error
  2903. function in Borland C++ to find a return address
  2904. static void near error_return(void)
  2905. {
  2906. longjmp(error::current->jmpbuf, 1);
  2907. }
  2908.  
  2909. void declare_error(void)
  2910. {
  2911. _AX = (int) error_return;
  2912. asm MOV BP,[BP]; // move frame pointer up one level
  2913. asm MOV [BP+2],AX; // patch return address
  2914. #if defined(__MEDIUM__) defined(__LARGE__) defined(__HUGE__)
  2915. asm MOV [BP+4],CS; // if return address is far, patch segment, too
  2916. #endif
  2917. }
  2918.  
  2919. error error_x;
  2920. if (setjmp(error_x.jmpbuf))
  2921. {
  2922. <garbage collection>
  2923. declare_error();
  2924. return;
  2925.  
  2926. }
  2927. /* End of File */
  2928.  
  2929.  
  2930.  
  2931.  
  2932.  
  2933.  
  2934.  
  2935.  
  2936.  
  2937.  
  2938.  
  2939.  
  2940.  
  2941.  
  2942.  
  2943.  
  2944.  
  2945.  
  2946.  
  2947.  
  2948.  
  2949.  
  2950.  
  2951.  
  2952.  
  2953.  
  2954.  
  2955.  
  2956.  
  2957.  
  2958.  
  2959.  
  2960.  
  2961.  
  2962.  
  2963.  
  2964.  
  2965.  
  2966.  
  2967.  
  2968.  
  2969.  
  2970.  
  2971.  
  2972.  
  2973.  
  2974.  
  2975.  
  2976.  
  2977.  
  2978.  
  2979.  
  2980.  
  2981.  
  2982.  
  2983.  
  2984.  
  2985.  
  2986.  
  2987.  
  2988.  
  2989. The UUCP g Protocol
  2990.  
  2991.  
  2992. Ian Lance Taylor
  2993.  
  2994.  
  2995. Ian Lance Taylor took a course in COBOL at the Cambridge Rindge and Latin High
  2996. School, from which he graduated in 1982. Since then he has written an ANSI C
  2997. library for a comuter system which nobody has ever heard of, has ported the
  2998. DECUS version of Zork from Fortran to C, has written a complete (UNIX only)
  2999. UUCP package, and has read Knuth from cover to cover. He works for Cygnus
  3000. Support, a company dedicated to supporting free software. He can be reached at
  3001. ian@airs.com.
  3002.  
  3003.  
  3004.  
  3005.  
  3006. Introduction
  3007.  
  3008.  
  3009. The UUCP suite of programs is widely used in the UNIX world to transfer mail
  3010. and news between computers. UUCP implementations are also available for many
  3011. types of personal computers. This article is a detailed description of the
  3012. original, and most frequently used, UUCP transport-layer protocol.
  3013. The basic UUCP service is unattended file transfer over telephone lines or a
  3014. network connection. Other programs build on that base to provide remote
  3015. program execution. The first UUCP implementation was written by Mike Lesk at
  3016. AT&T in 1976. UUCP has been rewritten and enhanced several times since then,
  3017. most notably by Peter Honeyman, David A. Nowitz, and Brian E. Redman in 1983,
  3018. who wrote what is known as HoneyDanBer UUCP. I recently rewrote the program
  3019. suite from scratch, in order to distribute it in sourcecode form under the GNU
  3020. Public License. My implementation is modestly entitled Taylor UUCP.
  3021. UUCP is a point-to-point protocol, which means that it is used between two
  3022. computers at a time. The protocol has two layers, a session layer and a
  3023. transport layer. The two computers use the session layer to identify each
  3024. other and to agree on which files should be transferred. The session layer
  3025. relies on the transport layer to provide error-free data transfer across the
  3026. communication link.
  3027. UUCP uses several different transport-layer protocols, which are optimized for
  3028. different types of communication links. Not all UUCP implementations support
  3029. every protocol. When two computers begin a UUCP session, they negotiate which
  3030. transport layer protocol to use. The transport protocols are known by single
  3031. character names. The most common ones are g, f, t, and e.
  3032. In the article I am going to discuss the g protocol. It is the original UUCP
  3033. transport-layer protocol, and it is the only one supported by all UUCP
  3034. implementations. It is based on a packet-driver protocol originally written by
  3035. G.L. Chesson at Bell Labs. It is intended to work over unreliable connections,
  3036. such as phone lines, but it requires an eight-bit transparent connection. If
  3037. any special characters cannot be sent over the line, such as XON or XOFF, it
  3038. will not work.
  3039. There is not enough room here to present the complete g protocol
  3040. implementation used by Taylor UUCP, so this article merely shows the high
  3041. points. Listing 1 shows a number of typedefs, macro definitions, variables,
  3042. and prototypes that are used in the later code. The complete Taylor UUCP code
  3043. is available for anonymous FTP from various sites, including ftp.uu.net.
  3044. Since the g protocol works over noisy phone lines, it has to be prepared for
  3045. data to be modified, or even lost, as it is sent from one computer to the
  3046. other. (Some types of network connections can also duplicate data. The g
  3047. protocol does not always handle this correctly).
  3048. This protocol provides flow control. The computer sending data is permitted to
  3049. send only a certain number of bytes to the receiving computer before it must
  3050. wait for an acknowledgement. That prevents a fast computer from overwhelming a
  3051. slower computer with more data than the latter can handle.
  3052. The g protocol communicates in packets. There are two types of packets,
  3053. control packets and data packets. The header of a data packet tells the
  3054. receiver how much data is present. It also has a checksum which is used to
  3055. detect transmission errors.
  3056.  
  3057.  
  3058. Packet Header
  3059.  
  3060.  
  3061. All packets begin with a six-byte header. Control packets have no attached
  3062. data, and are therefore exactly six bytes long. Data packets contain data
  3063. following the header bytes. The header bytes are shown in Figure 1.
  3064. The first header byte is always the ASCII character DLE, which is octal 020 or
  3065. control-P. If there is a transmission error, this byte can be used to locate
  3066. the start of the next header.
  3067. The second header byte is known as K. For a control packet, the value is
  3068. always 9. For a data packet, K indicates how many bytes of data follow the
  3069. six-byte header. The amount of data is 2K+4. The K value is always between 1
  3070. and 8, which means that the amount of data in a single packet can be any power
  3071. of 2 between 32 (21+4) and 4,096 (28+4).
  3072. The third and fourth header bytes form a two-byte (16-bit) checksum. The third
  3073. byte is CHECKLOW and the fourth byte is CHECKHIGH. The full checksum is
  3074. (CHECKHIGH << 8) CHECKLOW
  3075. The checksum differs for control and data packets, and is described further
  3076. below.
  3077. The fifth header byte is the control byte. It is composed of three bit fields,
  3078. known as TT (2 bits), XXX (3 bits), and YYY (3 bits). The value of the control
  3079. byte is
  3080. (TT << 6) (XXX << 3) YYY
  3081. TT indicates the type of the packet:
  3082. 0 -- control packet
  3083. 1 -- alternate data channel
  3084. 2 -- data packet
  3085. 3 -- short data packet
  3086. The meanings of XXX and YYY depend upon the type of packet, and are described
  3087. further below. The alternate-data-channel packet type (with a TT value of 1)
  3088. is not used by UUCP.
  3089. The sixth and last header byte is a simple check on the validity of the
  3090. header. It is the exclusive-OR of the second through fifth header bytes (the
  3091. first byte is always DLE, and so does not have to be checked further). If the
  3092. exclusive-OR check fails, the header data is invalid and is ignored.
  3093.  
  3094.  
  3095. Windows and Flow Control
  3096.  
  3097.  
  3098. Each data packet has a packet sequence number, which is the XXX value. The
  3099. packet sequence number is always between 0 and 7. At the beginning of the
  3100. session it starts at 1, goes up to 7, then wraps around to 0. The sequence
  3101. numbers for incoming and outgoing packets are independent. After one computer
  3102. sends out packet 7, the next packet it sends out is packet 0, regardless of
  3103. how many intervening packets it has received.
  3104. The packet sequence number is used to detect data loss. If the packet
  3105. following packet 5 is not packet 6, then information has been lost. Some
  3106. protocols, such as the Internet TCP protocol, permit packets to arrive out of
  3107. order, but the g protocol considers this an error. Control packets have no
  3108. sequence number, so there is no way to detect a lost control packet.
  3109. The packet sequence number is also used for flow control. During protocol
  3110. initialization each computer announces a window size. The window announced by
  3111. one computer is the number of packets the other computer may send before it
  3112. sees an acknowledgement.
  3113. There are two ways to send an acknowledgement. One is the YYY field of a data
  3114. packet. The other is the YYY field of an RR control packet. (Control packets
  3115. are described further below.) The two methods allow the acknowledgement to be
  3116. either combined with data or not combined, depending on whether the
  3117. acknowledging computer has any data to send. In either case, the
  3118. acknowledgement gives the last packet number which was correctly received.
  3119. Every packet must be acknowledged, and every packet must be acknowledged in
  3120. order.
  3121. For example, suppose that computer A has announced a window size of 3, and
  3122. that at some point during the conversation A has sent an acknowledgement for
  3123. packet 6. This means that computer B may send 3 more packets--namely packets
  3124. 7, 0, and 1--but it may not send packet 2 until it has received an
  3125. acknowledgement for packet 7. Note that the window size announced by A is a
  3126. restriction on which packets B is permitted to send.
  3127. The window size may range from 1 to 7. A window size of 1 means that each
  3128. packet must be acknowledged before the next one is sent. To see why the window
  3129. size may not be 8, suppose that computer A has just sent packet 0, and
  3130. received an acknowledgement for it. If the window size were 8, A could then
  3131. send packets 1, 2, 3, 4, 5, 6, 7, and 0. If all eight packets were lost, B
  3132. might time out, assume that its acknowledgement was lost, and acknowledge
  3133. packet 0 again. When A saw the acknowledgement, it would not know whether the
  3134. eight packets it sent were lost or whether B saw them all but all the
  3135. acknowledgements but the last one were lost. With a maximum window size of 7,
  3136. each packet acknowledgement is unambiguous.
  3137. The original UUCP implementation always used a window size of 3, and some
  3138. implementations have followed its lead. Many newer implementations default to
  3139. the maximum window size of 7.
  3140. The window size prevents a fast computer from overwhelming a slow computer. If
  3141. there were no way to slow down the fast computer by forcing it to wait for an
  3142. acknowledgement, it could eventually fill the input buffers of the slow
  3143. computer and cause data to be lost. The problem would be detected, since there
  3144. would be checksum failures, but the time required for the computers to get
  3145. back in sync would slow the overall data transfer rate drastically.
  3146. Many systems rely on XON/XOFF handshaking for flow control. Data transmission
  3147. stops when an XOFF character (control-S) is received, and starts again when an
  3148. XON character (control-Q) is received. This does not work with the g protocol,
  3149. which requires an eight-bit transparent communication line. (For example, one
  3150. of the checksum bytes in the header might be XON or XOFF.)
  3151.  
  3152. Some modern error-correcting modems and serial ports can use various forms of
  3153. hardware handshaking for flow control. This does not make the g protocol
  3154. useless, since error detection is still necessary for the connection between
  3155. the modem and the computer. In any case, a large window is relatively
  3156. efficient even when it is not needed.
  3157.  
  3158.  
  3159. Data Packets
  3160.  
  3161.  
  3162. A data packet is indicated by a TT value of 2 or 3. The K value must be
  3163. between 1 and 8, for a packet size between 32 and 4,096 bytes. This is the
  3164. number of bytes which follow the header. For example, a header with a K value
  3165. of 2 indicates a total packet size of six header bytes plus 64 data bytes for
  3166. a total of 70 bytes.
  3167. During protocol initialization, each side announces the largest data packet it
  3168. is prepared to receive. The original UUCP implementation always set the
  3169. maximum data-packet size to just 64 bytes. Many later implementations have
  3170. followed suit. While this small packet size was once reasonable, modern
  3171. high-speed error-correcting modems are much more efficient when larger packet
  3172. sizes are used.
  3173. Not every file contains a multiple of 32 bytes, which is the minimum packet
  3174. size. Therefore, there has to be a way to transfer fewer bytes in a single
  3175. packet. This is done with a short data packet, with a TT value of 3. The K
  3176. value always indicates the physical number of bytes that are sent over the
  3177. communication link. However, a short data packet contains a smaller number of
  3178. logical bytes.
  3179. If the first data byte of a short data packet is less than 128, then that byte
  3180. is subtracted from the physical packet length to get the logical packet
  3181. length. The real data starts with the second byte. Otherwise a 15-bit value is
  3182. formed with the low-order seven bits of the first byte and the complete second
  3183. byte. The logical packet length is this value subtracted from the physical
  3184. packet length, and the real data starts with the third byte. The logical
  3185. length is given using subtraction because, if the logical length is only one
  3186. byte less than the physical length, there is only a single byte available to
  3187. specify the logical length.
  3188. The data-packet layout is designed so that the data can be manipulated as a
  3189. block, without having to consider each individual byte (other than when
  3190. computing the checksum). Some other communications protocols, such as Zmodem,
  3191. use escape characters embedded within the data. This forces the code to
  3192. examine each data byte and make a decision about it, which is less efficient.
  3193. Listing 2 shows the fgsenddata function, which sends out a data packet. The
  3194. zdata argument points to the data, and the six bytes before zdata are
  3195. available to hold the header (ensuring that these six bytes are available
  3196. saves a call to memcpy in the common case). The cdata argument is the length
  3197. of the data in zdata. If the packet size is larger than 64, then this code
  3198. assumes that it is dealing with a newer UUCP implementation which will be able
  3199. to handle different packet sizes within the same session.
  3200. Listing 2 shows a call to the igchecksum function, which is shown in Listing
  3201. 3. Note that every byte sent with the data packet participates in the
  3202. checksum, even though the values of some of the bytes may be unimportant in a
  3203. short data packet.
  3204. The g protocol checksum is an ad hoc hashing scheme. It is the most
  3205. time-consuming part of the entire protocol, and it is not even particularly
  3206. good at detecting errors. A cyclic redundancy check would be more efficient to
  3207. compute and would also catch more errors. For a brief but interesting
  3208. discussion of this, see the book Design and Validation of Computer Protocols,
  3209. by Gerard J. Holzmann. Note that after the checksum is computed it is
  3210. manipulated still further before being placed in the packet header, as shown
  3211. in Listing 2.
  3212.  
  3213.  
  3214. Control Packets
  3215.  
  3216.  
  3217. A control packet is indicated by a TT value of 0 and a K value of 9. The XXX
  3218. value indicates the type of control packet:
  3219. 0 -- not used
  3220. 1 -- CLOSE
  3221. 2 -- RJ
  3222. 3 -- SRJ
  3223. 4 -- RR
  3224. 5 -- INITC
  3225. 6 -- INITB
  3226. 7 -- INITA
  3227. A CLOSE packet is sent when the conversation is finished, and the g protocol
  3228. is shutting down. It is also sent if some fatal error is forcing one computer
  3229. to drop the connection. The YYY value is ignored.
  3230. The RJ control is also known as NACK. It means that there was some problem
  3231. with a received packet, such as a checksum error, and that data must be
  3232. resent. The YYY value is the last successfully received packet. Every
  3233. subsequent packet must be resent.
  3234. The SRJ control is actually not used by UUCP. Most UUCP programs will not
  3235. recognize it. It is supposed to request retransmission of just packet YYY.
  3236. The RR control is also known as ACK. It is used to acknowledge a data packet
  3237. without sending a data packet in return. (Acknowledgements were discussed
  3238. above in the section on windows and flow control). The YYY value is the packet
  3239. being acknowledged.
  3240. The INIT controls are used during protocol initialization. When the protocol
  3241. starts up, each computer exchanges pairs of each type of INIT packet. The
  3242. computer which placed the call sends an INITA, then the other computer
  3243. responds with an INITA, and similarly for INITB and INITC. The YYY value for
  3244. an INITA or INITC packet is the window size that the other computer should
  3245. use. The YYY value for an INITB packet indicates the packet size that the
  3246. other computer should use. This is encoded as one less than the K value for
  3247. the desired packet size, so that it is a value from 0 to 7. The initialization
  3248. exchange is not a negotiation. Each computer simply makes a request. Both
  3249. computers may wind up using different window and packet sizes.
  3250. Listing 4 shows the code to send a control packet, and shows how the checksum
  3251. value is computed.
  3252.  
  3253.  
  3254. Error Handling
  3255.  
  3256.  
  3257. Error handling is an important aspect of any communications protocol. The bulk
  3258. of the Taylor UUCP implementation is devoted to it, although there is
  3259. unfortunately not enough space to present the actual code. A protocol must be
  3260. able to detect communication errors to ensure that it receives correct data.
  3261. After an error occurs, the protocol must be able to quickly recover and
  3262. continue transmitting information.
  3263. There is no way to ensure completely reliable data transfer across telephone
  3264. lines. They can, in theory, arbitrarily corrupt data. In practice, however,
  3265. telephone lines are fairly reliable. A fairly simple checksum is thus adequate
  3266. to detect data corruption. The g protocol relies on checksums for error
  3267. detection, and negative acknowledgements and timeouts for error recovery.
  3268. An error in a packet header can be detected by an invalid exclusive-OR byte or
  3269. an invalid K value. After an error, the receiving computer must start looking
  3270. for a new DLE byte. It must start looking at the second byte of the header. It
  3271. may not skip the rest of the header, and it may certainly not skip the entire
  3272. packet. If data were lost, then the start of the next packet might appear in
  3273. the skipped data. Another DLE byte might be found that is actually part of the
  3274. data in the corrupted packet, but the exclusive-OR byte should prevent it from
  3275. being treated as a legitimate packet header.
  3276. An error in packet data is detected by an invalid checksum. If an invalid
  3277. packet is seen, the receiving computer will send an RJ packet to tell the
  3278. sending computer that the packet must be resent. The receiver must then start
  3279. looking for a new DLE byte. Once again, it must not skip the packet data,
  3280. since the checksum error might have been caused by lost data. The next packet
  3281. header might be somewhere within the bytes which were assumed to be the data
  3282. for the erroneous packet.
  3283. An RJ packet includes the sequence number of the last correctly-received
  3284. packet. All subsequent packets must be resent. This is referred to as a
  3285. go-back-N protocol. After an error, the sending computer must resend all the
  3286. packets from a particular point, rather than just resend the erroneous packet.
  3287. An SRJ packet could be used to request just the erroneous packet, but most
  3288. UUCP implementations do not implement it. A go-back-N protocol can be less
  3289. efficient than just resending a single packet, but it is easier to implement.
  3290. There are some concerns with a go-back-N protocol. If computer A is
  3291. temporarily overloaded with work, it might not find time to process its input
  3292. buffers, and data might be lost. When it notices the loss, it will send an RJ
  3293. packet. Computer B is permitted to respond with an entire window full of data,
  3294. but if it does computer A might simply get overloaded again. Taylor UUCP uses
  3295. a simple slow-start algorithm, which temporarily shrinks B's sending window to
  3296. a single packet and opens it up slowly as each acknowledgement is received.
  3297. Part of this code can be seen at the end of Listing 2.
  3298. A lost packet will cause the next packet to be seen as an out-of-order packet,
  3299. a case which requires a bit of thought. If A detects an invalid packet, it
  3300. will send an RJ packet. It will then expect to see another copy of the invalid
  3301. packet. However, B will probably have already sent the next few packets. It
  3302. may even have sent an entire window full of packets. If A sends an RJ packet
  3303. for each out-of-order packet, B will see a sequence of RJ packets all
  3304. requesting the same invalid packet, which it will have to send multiple times.
  3305. Taylor UUCP simply ignores out-of-order packets, and relies on a timeout to
  3306. detect completely lost packets. More sophisticated approaches are possible.
  3307. Although phone lines cannot duplicate data, certain types of network
  3308. connections can. This can confuse the g protocol. Suppose that the window size
  3309. is 7, and that A sends out packets 4, 5, 6, 7, 0, 1, 2. After A received an
  3310. acknowledgement for packet 4, it will send out packet 3. If packet 4 is
  3311. duplicated, and the duplicate arrives at B just after packet 3 arrives, B will
  3312. see the wrong data. This is a potential problem for any windowing protocol. It
  3313. is generally avoided by making the window large enough to ensure that any
  3314. duplicate packets will arrive before a complete window is sent. For example,
  3315. the sequence number used by the Internet TCP protocol is a full 32 bits.
  3316.  
  3317.  
  3318. The UUCP Session Layer
  3319.  
  3320.  
  3321. The g protocol is used by the UUCP session layer to transmit UUCP commands and
  3322. data files. UUCP commands are simple strings. When using the g protocol, they
  3323. are sent as a sequence of data packets (not short data packets). The last data
  3324. byte of the last packet in the sequence is a null byte, and the string is
  3325. itself terminated by a null byte. Normally only one or two packets are
  3326. required.
  3327. UUCP sends a file by simply sending the data in a sequence of data packets or
  3328. short data packets. At the end of the file a short data packet is sent with a
  3329. logical packet length of zero bytes.
  3330. References
  3331. Holzmann, Gerard J. 1991. Design and Validation of Computer Protocols.
  3332. Englewood Cliffs, NJ: Prentice-Hall.
  3333. Figure 1 UUCP g data-packet header bytes
  3334. DLE K CHECK CHECK CONTROL XOR
  3335.  LOW HIGH
  3336.  
  3337.  
  3338. Listing 1 Various typedefs, macro definitions, variables, and prototypes
  3339.  
  3340. /* The boolean type. */
  3341. typedef int boolean;
  3342. #define TRUE (1)
  3343. #define FALSE (0)
  3344.  
  3345. /* Names for the bytes in the frame header. */
  3346. #define IFRAME_DLE (0)
  3347. #define IFRAME_K (1)
  3348. #define IFRAME_CHECKLOW (2)
  3349. #define IFRAME_CHECKHIGH (3)
  3350. #define IFRAME_CONTROL (4)
  3351. #define IFRAME_XOR (5)
  3352.  
  3353. /* Length of the frame header. */
  3354. #define CFRAMELEN (6)
  3355.  
  3356. /* Macros to break apart the control bytes. */
  3357. #define CONTROL_TT(b) ((int)(((b) >> 6) & 03))
  3358. #define CONTROL_XXX(b) ((int)(((b) >> 3) & 07))
  3359. #define CONTROL_YYY(b) ((int)((b) & 07))
  3360.  
  3361. /* DLE value. */
  3362. #define DLE ('\020')
  3363.  
  3364. /* Get the length of a packet given a pointer to the header. */
  3365. #define CPACKLEN(z) (1 << ((z)[IFRAME_K] + 4))
  3366.  
  3367. /* <k> field value for a control message. */
  3368. #define KCONTROL (9)
  3369.  
  3370. /* Get the next sequence number given a sequence number. */
  3371. #define INEXTSEQ(i) ((i + 1) & 07)
  3372.  
  3373. /* Compute i1 - i2 modulo 8. */
  3374. #define CSEQDIFF(i1, i2) (((i1) + 8 - (i2)) & 07)
  3375.  
  3376. /* Packet types. These are from the TT field. */
  3377. #define CONTROL (0)
  3378. #define ALTCHAN (1)
  3379. #define DATA (2)
  3380. #define SHORTDATA (3)
  3381.  
  3382. /* Control types. These are from the XXX field if the type
  3383. (tt field) is CONTROL. */
  3384. #define CLOSE (1)
  3385. #define RJ (2)
  3386. #define SRJ (3)
  3387. #define RR (4)
  3388. #define INITC (5)
  3389. #define INITB (6)
  3390. #define INITA (7)
  3391.  
  3392. /* Maximum amount of data in a single packet. */
  3393. #define CMAXDATAINDEX (8)
  3394. #define CMAXDATA (1 << (CMAXDATAINDEX + 4))
  3395.  
  3396.  
  3397. /* Maximum window size. */
  3398. #define CMAXWINDOW (7)
  3399.  
  3400. /* The timeout to use when waiting for a packet.
  3401. Protocol parameter ''timeout''. */
  3402. #define CTIMEOUT (10)
  3403.  
  3404. /* The number of times to retry waiting for a packet.
  3405. Each time the timeout fails we send a copy of our
  3406. last data packet or a reject message for the packet
  3407. we expect from the other side, depending on whether
  3408. we have any unacknowledged data. This is the number
  3409. of times we try doing that and then waiting again.
  3410. Protocol parameter ''retries''. */
  3411. #define CRETRIES (6)
  3412.  
  3413. /* Next sequence number to send. */
  3414. int iGsendseq = 1;
  3415.  
  3416. /* Last sequence number that has been acked. */
  3417. int iGremote_ack = 0;
  3418.  
  3419. /* Last sequence number to be retransmitted. */
  3420. int iGretransmit_seq = -1;
  3421.  
  3422. /* Last sequence number we have received. */
  3423. static int iGrecseq;
  3424.  
  3425. /* Remote segment size (set during protocol initialization).
  3426. This is one less than the value in a packet header. */
  3427. int iGremote_segsize;
  3428.  
  3429. /* Remote packet size (set based on iGremote_segsize). */
  3430. int iGremote_packsize;
  3431.  
  3432. /* Remote window size (set during handshake). */
  3433. static int iGremote_winsize;
  3434.  
  3435. /* Timeout (seconds) for receiving a data packet.
  3436. Protocol parameter ''timeout''. */
  3437. static int cGtimeout = CTIMEOUT;
  3438.  
  3439. /* Maximum number of timeouts when receiving a data packet
  3440. or acknowledgement. Protocol parameter ''retries''. */
  3441. static int cGretries = CRETRIES;
  3442.  
  3443. /* Get a packet. This is called to wait for a packet to come
  3444. in when there is nothing to send. If freturncontrol is
  3445. TRUE, this will return after getting any control packet.
  3446. Otherwise, it will continue to receive packets until a
  3447. complete file or a complete command has been received.
  3448. The timeout and the number of retriesare arguments.
  3449. The function returns FALSE if an error occurs or if
  3450. cretries timeouts of ctimeout seconds were exceeded. */
  3451. boolean fgwait_for_packet (boolean freturncontrol,
  3452. int ctimeout, int cretries);
  3453.  
  3454. /* Send data to the other system. If the fdoread argument
  3455. is true, this will also read data into abPrecbuf; fdoread
  3456.  
  3457. is passes as TRUE if the protocol expects data to be
  3458. coming back, to make sure the input buffer does not fill
  3459. up. Returns FALSE on error. */
  3460. boolean fsend_data (const char *zsend, int csend,
  3461. boolean fdoread);
  3462. /* End of File */
  3463.  
  3464.  
  3465. Listing 2 The fgsenddata function sends out a data packet.
  3466. boolean
  3467. fgsenddata (zdata, cdata)
  3468. char *zdata;
  3469. int cdata;
  3470. {
  3471. char *z;
  3472. int itt, iseg, csize;
  3473. unsigned short icheck;
  3474.  
  3475. itt = DATA;
  3476. csize = iGremote_packsize;
  3477. iseg = iGremote_segsize + 1;
  3478.  
  3479. if (cdata < iGremote_packsize)
  3480. {
  3481. /* If the remote packet size is larger than 64,
  3482. the default, we can assume they can handle a
  3483. smaller packet as well, which will be more
  3484. efficient to send. */
  3485. if (iGremote_packsize > 64)
  3486. {
  3487. /* The packet size is 1 << (iseg + 4). */
  3488. iseg = 1;
  3489. csize = 32;
  3490. while (csize < cdata)
  3491. {
  3492. csize <<= 1;
  3493. ++iseg;
  3494. }
  3495. }
  3496.  
  3497. if (csize != cdata)
  3498. {
  3499. int cshort;
  3500.  
  3501. /* We have to move the data within the packet,
  3502. unfortunately. It only happens once per
  3503. file transfer. It would also be nice if we
  3504. computed the checksum as we move. We zero
  3505. out the unused bytes.
  3506. */
  3507. itt = SHORTDATA;
  3508. cshort = csize - cdata;
  3509. if (cshort <= 127)
  3510. {
  3511. memmove (zdata + 1, zdata, cdata);
  3512. zdata[0] = (char) cshort;
  3513. memset (zdata + cdata + 1, 0, cshort - 1);
  3514. }
  3515. else
  3516.  
  3517. {
  3518. memmove (zdata + 2, zdata, cdata);
  3519. zdata[0] = (char) (0x80 (cshort & 0x7f));
  3520. zdata[1] = (char) (cshort >> 7);
  3521. memset (zdata + cdata + 2, 0, cshort - 2);
  3522. }
  3523. }
  3524. }
  3525.  
  3526. z = zdata - CFRAMELEN;
  3527.  
  3528. z[IFRAME_DLE] = DLE;
  3529. z[IFRAME_K] = (char) iseg;
  3530.  
  3531. icheck = (unsigned short) igchecksum (zdata, csize);
  3532.  
  3533. /* Wait until there is room in the receiver's window
  3534. for us to send the packet. We do this now so that
  3535. we send the correct value for the last packet
  3536. received. Note that if iGsendseq == iGremote_ack,
  3537. this means that the sequence numbers are actually
  3538. 8 apart, since the packet could not have been
  3539. acknowledged before it was sent; this can happen
  3540. when the window size is 7. */
  3541. while (iGsendseq == iGremote_ack
  3542.  CSEQDIFF (iGsendseq, iGremote_ack) > iGremote_winsize)
  3543. {
  3544. if (! fgwait_for_packet (TRUE, cGtimeout,
  3545. cGretries))
  3546. return FALSE;
  3547. }
  3548.  
  3549. /* Ack all packets up to the next one, since the UUCP
  3550. protocol requires that all packets be acked in
  3551. order. */
  3552. while (CSEQDIFF (iGrecseq, iGlocal_ack) > 1)
  3553. {
  3554. iGlocal_ack = INEXTSEQ (iGlocal_ack);
  3555. if (! fgsend_control (RR, iGlocal_ack))
  3556. return FALSE;
  3557. }
  3558. iGlocal_ack = iGrecseq;
  3559.  
  3560. z[IFRAME__CONTROL] = (char) ((itt << 6) (iGsendseq << 3) iGrecseq);
  3561.  
  3562. iGsendseq = INEXTSEQ (iGsendseq);
  3563.  
  3564. icheck = ((unsigned short)
  3565. ((0xaaaa - (icheck ^ (z[IFRAME_CONTROL] & 0xff))) & 0xffff));
  3566. z[IFRAME_CHECKLOW] = (char) (icheck & 0xff);
  3567.  
  3568. z[IFRAME_CHECKHIGH] = (char) (icheck >> 8);
  3569.  
  3570. z[IFRAME_XOR] = (char) (z[IFRAME_K] ^ z[IFRAME_CHECKLOW]
  3571. ^ z[IFRAME_CHECKHIGH] ^ z[IFRAME_CONTROL]);
  3572.  
  3573. /* If we're waiting for acks of retransmitted
  3574. packets, then don't send this packet yet. The
  3575. other side may not be ready for it yet. Instead,
  3576.  
  3577. code infggot_ack will send the outstanding packets
  3578. when an ack is received. */
  3579. if (iGretransmit_seq != -1)
  3580. return TRUE;
  3581.  
  3582. return fsend_data (z, CFRAMELEN + csize, TRUE);
  3583. }
  3584.  
  3585. /* End of File */
  3586.  
  3587.  
  3588. Listing 3 The igchecksum function
  3589. int
  3590. igchecksum (z, c)
  3591. register const char *z;
  3592. register int c;
  3593. {
  3594. register unsigned int ichk1, ichk2;
  3595.  
  3596. ichk1 = 0xffff;
  3597. ichk2 = 0;
  3598.  
  3599. do
  3600. {
  3601. register unsigned int b;
  3602.  
  3603. /* Rotate ichk1 left. */
  3604. if ((ichk1 & 0x8000) == 0)
  3605. ichk1 <<= 1;
  3606. else
  3607. {
  3608. ichk1 <<= 1;
  3609. ++ichk1;
  3610. }
  3611.  
  3612. /* Add the next character to ichk1. */
  3613. b = *z++ & 0xff;
  3614. ichk1 += b;
  3615.  
  3616. /* Add ichk1 xor the character position in
  3617. the buffer counting from the back to ichk2. */
  3618. ichk2 += ichk1 ^ c;
  3619.  
  3620. /* If the character was zero, or adding it to
  3621. ichk1 caused an overflow, xor ichk2 to ichk1. */
  3622. if (b == 0 (ichk1 & 0xffff) < b)
  3623. ichk1 ^= ichk2;
  3624. }
  3625. while (--c > 0);
  3626.  
  3627. return ichk1 & 0xffff;
  3628. }
  3629.  
  3630. /* End of File */
  3631.  
  3632.  
  3633. Listing 4 This listing shows the code or sending a
  3634. static boolean
  3635. fgsend control (ixxx, iyyy)
  3636.  
  3637. int ixxx;
  3638. int iyyy;
  3639. {
  3640. char ab[CFRAMELEN];
  3641. int ict1;
  3642. unsigned short icheck;
  3643.  
  3644. ab[IFRAME_DLE] = DLE;
  3645. ab[IFRAME_K] = KCONTROL;
  3646.  
  3647. ict1 = (CONTROL << 6) (ixxx << 3) iyyy;
  3648. icheck = (unsigned short) (0xaaaa - ict1);
  3649. ab[IFRAME_CHECKLOW] = (char) (icheck & 0xff);
  3650. ab[IFRAME_CHECKHIGH] = (char) (icheck >> 8);
  3651.  
  3652. ab[IFRAME_CONTROL] = (char) ict1;
  3653.  
  3654. ab[IFRAME_XOR] = (char) (ab[IFRAME_K]
  3655. ^ ab[IFRAME_CHECKLOW]
  3656. ^ ab[IFRAME_CHECKHIGH]
  3657. ^ ab[IFRAME_CONTROL]);
  3658.  
  3659. return fsend_data (ab, CFRAMELEN, TRUE);
  3660. }
  3661.  
  3662. /* End of File */
  3663.  
  3664.  
  3665.  
  3666.  
  3667.  
  3668.  
  3669.  
  3670.  
  3671.  
  3672.  
  3673.  
  3674.  
  3675.  
  3676.  
  3677.  
  3678.  
  3679.  
  3680.  
  3681.  
  3682.  
  3683.  
  3684.  
  3685.  
  3686.  
  3687.  
  3688.  
  3689.  
  3690.  
  3691.  
  3692.  
  3693.  
  3694.  
  3695.  
  3696.  
  3697.  
  3698.  
  3699.  
  3700. An Essential String Function Library
  3701.  
  3702.  
  3703. William Smith
  3704.  
  3705.  
  3706. William Smith is the engineering manager at Montana Software, a software
  3707. development company specializing in custom applications for MS-DOS and
  3708. Windows. You may contact him by mail at P.O. Box 663, Bozeman, MT 59771-0663.
  3709.  
  3710.  
  3711. The include file <string.h> in the Standard C library defines 22 functions for
  3712. manipulating character strings. Seventeen of these functions begin with the
  3713. prefix str and another five begin with the prefix mem. The functions that
  3714. begin with the prefix str work on null-terminated strings. They accomplish
  3715. such critical tasks as finding the length of a string, concatenating strings,
  3716. or comparing strings, to name just a few of the tasks. The functions that
  3717. begin with mem work on any buffer of memory. These functions do not interpret
  3718. a null character as a terminator.
  3719. At first, the functions in <string.h> appear to offer a broad smorgasbord of
  3720. functionality. I originally expected them to satisfy most string-processing
  3721. requirements I would encounter. In actuality, I repeatedly encountered
  3722. situations where what I needed to do I could not accomplish with a single call
  3723. to one of the Standard C functions. But they are good building blocks. I found
  3724. myself using and grouping them to accomplish what I really needed.
  3725. Most of the string processing tasks I am faced with center around manipulating
  3726. text data for input and output. I nearly always have to parse and convert some
  3727. text script file or user input into a data structure and vice versa. Over
  3728. time, in just about every program I wrote, the specific needs for text
  3729. processing started to repeat themselves. I frequently needed to delete and
  3730. insert strings, or trim leading and trailing tabs and spaces from text. These
  3731. and many other requirements were common to nearly every project I would work
  3732. on. After almost ten years of programing in C, a group of about an additional
  3733. 20 functions has precipitated and become a crucial part of my C function
  3734. library. I am going to share with you the most recent incarnation of my bare
  3735. bones but essential string function library. These functions complement the
  3736. Standard C functions defined in <string. h>
  3737.  
  3738.  
  3739. Dynamic Memory Issues
  3740.  
  3741.  
  3742. When writing string functions, you can go in a couple of different directions
  3743. with regards to dynamic memory. You can dynamically allocate memory to store
  3744. the string that results from a function's execution of a task. This approach
  3745. allows you to avoid modification of the original string passed to the
  3746. function. For example, when performing search and replace, you can use dynamic
  3747. memory to store the string that contains the modifications. The function can
  3748. then leave the original string unchanged. However, when using this approach,
  3749. the programmer must keep track of memory allocation and make sure to release
  3750. the allocated memory eventually. This can be a challenge in certain
  3751. situations. Use of dynamic memory may be more suitable in C++. C++ is better
  3752. organized to provide object creation and deletion. This helps with dynamic
  3753. memory management and relieves some of the burden on the programmer.
  3754. Since the functions presented here are pure Standard C, I choose to avoid
  3755. dynamic memory. In fact, I also choose to avoid creating buffers on the stack
  3756. as a scratch or work space. Some of the editing functions require a temporary
  3757. work space, but I get around this by using the memmove function defined in
  3758. <string.h>. memmove provides safe memory copying of overlapping buffers. You
  3759. would need a temporary copy of the source buffer to do it yourself. Although
  3760. convenient, using memmove has the disadvantage of being more costly with
  3761. respect to processor time. This varies from system to system, but generally
  3762. there are usually faster ways to accomplish the same task as memmove.
  3763. Modifying the functions to avoid the use of memmove can wring a bit more
  3764. performance and efficiency out of them.
  3765. In the future, it might be worthwhile to create object wrappers in C++ for
  3766. these string functions. For now, I will leave them in standard C. This means
  3767. that all the functions assume that the strings that you pass to them are
  3768. NULL-terminated. The functions also assume that the strings are pointers to
  3769. memory areas that are large enough to accommodate the resulting string
  3770. generated by the function. The burden of avoiding buffer overflows rests on
  3771. the programer.
  3772.  
  3773.  
  3774. Implementation
  3775.  
  3776.  
  3777. I break the string functions up into two categories. I group functions for
  3778. extracting or finding a substring in a string into the file named STR_NGET.C
  3779. (Listing 1 and Listing 2). The second group, in STR_EDIT.C (Listing 3 and
  3780. Listing 4), contains functions that I use for editing strings.
  3781.  
  3782.  
  3783. Functions for Getting Substrings
  3784.  
  3785.  
  3786. STR_NGET. C contains the functions str_nleft, str_nmid, str_nright and
  3787. str_rstr. The first three functions extract a specified number of characters
  3788. from a string. These functions modify the string itself by moving the desired
  3789. characters into it. str_nleft extracts the n left- most characters. str_nright
  3790. extracts the n right-most characters. str_nmid extracts n characters from a
  3791. specified position.
  3792. str_rstr resembles the function strstr defined in <string.h>. But instead of
  3793. finding the first occurrence of a substring, str_rstr finds the last
  3794. occurrence of the substring. The relationship between strstr and str_rstr is
  3795. analogous to the relationship between strchr and strrchr. I have seen a
  3796. function called strrstr in some libraries that come with commercial compilers.
  3797. It is equivalent to my function str_rstr. It is not a part of the standard.
  3798.  
  3799.  
  3800. Functions for Editing Strings
  3801.  
  3802.  
  3803. All the 13 functions in the file STR_EDIT.C do some type of modification or
  3804. editing to a string. The functionality ranges from the simple padding of
  3805. strings to a fixed length for justification to complete search and replace.
  3806. str_center, str_ljust, and str_rjust justify strings. These functions first
  3807. trim leading and trailing spaces and tabs from a string. They then move the
  3808. string so it is either centered, left-justified, or right-justified within a
  3809. specified length.
  3810. The trimming functions, str_ltrim, str_rtrim and str_trim execute the trimming
  3811. tasks required by the justification functions mentioned above. These functions
  3812. trim all characters from the end or ends of a string that match a list of
  3813. characters to trim.
  3814. The function str_delete removes a specified number of characters from a string
  3815. starting at a designated location within the string. The function str_insert
  3816. inserts a string into a string at a designated location in the string. The
  3817. function str_rplc uses both str_delete and str_insert implement a search and
  3818. replace capability. str_mrplc does search and replace for all matches.
  3819. str_rplc just replaces the first match. The function str_repeat builds a
  3820. string of desired length by repeating a string.
  3821. The function str_vcat is a variable-argument version of the Standard C
  3822. function strcat. This function concatenates a list of strings. The last string
  3823. or parameter passed to str_vcat must be a null-pointer. str_ocat is a version
  3824. of strcat that can handle overlapping strings. An example of overlapping
  3825. strings would be a single string with multiple pointers to different locations
  3826. in the string. Depending on the compiler vendor, sometimes strcat will work
  3827. with overlapping strings, sometimes it will not. For safety and constancy I
  3828. created the function str_ocat. str_ocat is just wrapper for memmove.
  3829.  
  3830.  
  3831. Conclusions
  3832.  
  3833.  
  3834. Nearly every major program I have written has involved text processing in some
  3835. form. The Standard C library provides a useful, but shallow group of
  3836. string-manipulation functions. Over time and out of need, I have come up with
  3837. the group of string functions presented here. These functions build upon the
  3838. standard library functions and provide the functionality that I have found
  3839. important in practice.
  3840. There are an endless number of more functions you can invent. And you can
  3841. probably find more efficient ways to implement the functions demonstrated
  3842. here. Nevertheless, these are the functions I have found useful and essential
  3843. in my work with C.
  3844.  
  3845. Listing 1 STR_NGET.C - functions for extracting or finding a substring
  3846. /*****************************************************
  3847. File Name: STR_NGET.C
  3848. Description: Library of functions for geting
  3849. substrings in a string
  3850. Global Function List: str_nleft
  3851.  
  3852. str_nmid
  3853. str_nright
  3854. str_rstr
  3855. Portability: Standard C
  3856. *****************************************************/
  3857.  
  3858. #include <stdlib.h>
  3859. #include <string.h>
  3860. #include <str_nget.h>
  3861.  
  3862. /*****************************************************
  3863. Name: str_nleft
  3864. Expanded Name: Get Left N Characters
  3865. Parameters: Str - string to get left characters
  3866. Num - number of characters to get
  3867. Return: Str
  3868. Description: Get Num leftmost charcters in Str.
  3869. Modifies Str.
  3870. *****************************************************/
  3871. char *str_nleft( char *Str, size_t Num )
  3872. {
  3873.  
  3874. if ( Num < strlen( Str ))
  3875. {
  3876. Str[Num] = '\0';
  3877. }
  3878.  
  3879. return ( Str );
  3880.  
  3881. } /* function str_nleft */
  3882.  
  3883. /*****************************************************
  3884. Name: str_nmid
  3885. Expanded Name: Get Middle N Characters
  3886. Parameters: Str - string to get substring in
  3887. Pos - index into Str of start of midstr
  3888. Num - count of charcters to get
  3889. Return: Str
  3890. Description: Get Num chars from middle of string.
  3891. *****************************************************/
  3892. char *str_nmid( char *Str, size_t Pos, size_t Num )
  3893. {
  3894.  
  3895. char *Mid;
  3896. size_t Len = strlen( Str );
  3897.  
  3898. if ( Pos >= Len )
  3899. {
  3900. /* Outside of string */
  3901. *Str = '\0';
  3902. return ( Str );
  3903. }
  3904.  
  3905. /* Adjust count if it extends outside of string */
  3906. if ( Pos + Num > Len )
  3907. {
  3908. Num = Len - Pos;
  3909. }
  3910.  
  3911.  
  3912. Mid = &Str[Pos];
  3913. memmove( (void *)Str, (void *)Mid, Num );
  3914. Str[Num] = '\0';
  3915.  
  3916. return ( Str );
  3917.  
  3918. } /* function str_nmid */
  3919.  
  3920. /*****************************************************
  3921. Name: str_nright
  3922. Expanded Name: Get Right N Characters
  3923. Parameters: Str - string to get right characters
  3924. Num - number of characters to get
  3925. Return: Str
  3926. Description: Get Num righmost characters in Str.
  3927. Modifies Str.
  3928. *****************************************************/
  3929. char *str_nright( char *Str, size_t Num )
  3930. {
  3931.  
  3932. size_t Len = strlen( Str );
  3933.  
  3934. return ( str_nmid( Str,
  3935. ( Num > Len ? 0 : Len - Num ),
  3936. min( Num, Len ) ) );
  3937.  
  3938. } /* function str_nright */
  3939.  
  3940. /*****************************************************
  3941. Name: str_rstr
  3942. Expanded Name: String Right (Reverse) Search
  3943. Parameters: Str - string to search
  3944. Find - string to search for
  3945. Return: Pointer to last occurrence of substring
  3946. Find in Str or NULL if not found
  3947. Description: Searches for last occurrence of sub
  3948. string Find within Str.
  3949. *****************************************************/
  3950. char *str_rstr( char *Str, char *Find )
  3951. {
  3952.  
  3953. char *StrResult = NULL, *StrWork = Str;
  3954.  
  3955. while ( ( StrWork =
  3956. strstr( StrWork, Find ) ) != NULL )
  3957. {
  3958. StrResult = StrWork;
  3959. StrWork++;
  3960. }
  3961.  
  3962. return ( StrResult );
  3963.  
  3964. } /* function str_rstr */
  3965.  
  3966. /* End of File */
  3967.  
  3968.  
  3969. Listing 2 STR_NGET.H - contains function prototypes for Listing 1
  3970. /*****************************************************
  3971.  
  3972. File Name: STR_NGET.H
  3973. Description: Include file for STR_NGET.C
  3974. *****************************************************/
  3975.  
  3976. #if !defined ( STR_NGET_DEFINED )
  3977.  
  3978. #define STR_NGET_DEFINED
  3979.  
  3980. char *str_nleft( char *Str, size_t Num );
  3981. char *str_nmid( char *Str, size_t Pos, size_t Num );
  3982. char *str_nright( char *Str, size_t Num );
  3983. char *str_rstr( char *Str, char *Find );
  3984.  
  3985. #endif
  3986.  
  3987. /* End of File */
  3988.  
  3989.  
  3990. Listing 3 STR_EDIT.C - functions for editing strings
  3991. /****************************************************
  3992. File Name: STR_EDIT.C
  3993. Description: Library of functions for editing
  3994. strings
  3995. Global Function List: str_center
  3996. str_delete
  3997. str_insert
  3998. str_ljust
  3999. str_ltrim
  4000. str_mrplc
  4001. str_ocat
  4002. str_repeat
  4003. str_rjust
  4004. str_rplc
  4005. str_rtrim
  4006. str_trim
  4007. str_vcat
  4008. Portability: Standard C
  4009. ****************************************************/
  4010.  
  4011. /* Standard C */
  4012. #include <stdarg.h>
  4013. #include <stdlib.h>
  4014. #include <string.h>
  4015.  
  4016. /* Own */
  4017. #include <str_edit.h>
  4018.  
  4019. /*****************************************************
  4020. Name: str_center
  4021. Parameters: Str - string to center
  4022. Len - num of chars of centered string
  4023. Return: Str
  4024. Description: Centers a string in a desired length
  4025. by removing tabs and adding spaces
  4026. to both sides of string.
  4027. *****************************************************/
  4028. char *str_center( char *Str, size_t Len )
  4029. {
  4030.  
  4031.  
  4032. size_t LenOrg;
  4033.  
  4034. /* Trim spaces and tabs off the string */
  4035. str_trim( Str, "\t" );
  4036.  
  4037. LenOrg = strlen( Str );
  4038. if ( Len <= LenOrg )
  4039. {
  4040. /* The desired string length is shorter than
  4041. ** the original so return */
  4042. return ( Str );
  4043. }
  4044.  
  4045. /* Add the spaces to each side */
  4046. str_rjust( Str,( LenOrg + Len ) / 2 );
  4047. str_ljust( Str, Len );
  4048.  
  4049. return ( Str );
  4050.  
  4051. } /* function str_center */
  4052.  
  4053. /*****************************************************
  4054. Name: str_delete
  4055. Parameters: Str - string to edit
  4056. Pos - index to start deleting chars at
  4057. Num - number of charcters to delete
  4058. Return: Str
  4059. Description: Modifies Str, by deleting Num chars
  4060. beginning at Pos.
  4061. *****************************************************/
  4062. char *str_delete( char *Str, char *Pos, size_t Num )
  4063. {
  4064.  
  4065. size_t Len = strlen( Str );
  4066.  
  4067. if ( ( Pos >= &Str[Len] ) ( Num == 0 ) )
  4068. {
  4069. /* Outside string or no chars to delete */
  4070. return ( Str );
  4071. }
  4072.  
  4073. Num = min( Num, strlen( Pos ) );
  4074. if ( Num )
  4075. {
  4076. /* Delete characters by contactenating */
  4077. memmove( Pos, &Pos[Num],
  4078. strlen( &Pos[Num] ) + 1 );
  4079. }
  4080.  
  4081. return ( Str );
  4082.  
  4083. } /* function str_delete */
  4084.  
  4085. /*****************************************************
  4086. Name: str_insert
  4087. Parameters: Str - string to edit
  4088. Pos - pointer to location withing Str
  4089. Insrt - string to insert into Str
  4090. Return: Str
  4091.  
  4092. Description: Inserts a string Insrt into Str at Pos
  4093. *****************************************************/
  4094. char *str_insert( char *Str, char *Pos, char *Insrt )
  4095. {
  4096.  
  4097. size_t Len = strlen( Insrt );
  4098. char *Tmp = &Pos[Len];
  4099.  
  4100. memmove( Tmp, Pos, strlen( Pos ) + 1 );
  4101. memmove( Pos, Insrt, Len );
  4102.  
  4103. return ( Str );
  4104.  
  4105. } /* function str_insert */
  4106.  
  4107. /*****************************************************
  4108. Name: str_ljust
  4109. Parameters: Str - string to left justify
  4110. Len - length of string
  4111. Return: Str
  4112. Description: Pads right end of Str with spaces to
  4113. left justify Str to a new length Len.
  4114. *****************************************************/
  4115. char *str_ljust( char *Str, size_t Len )
  4116. {
  4117.  
  4118. size_t LenOrg = strlen( Str );
  4119. char *StrEnd = &Str[LenOrg];
  4120.  
  4121. Len = max( Len, LenOrg ) - LenOrg;
  4122. StrEnd[Len] = '\0';
  4123.  
  4124. while ( Len )
  4125. {
  4126. Len--;
  4127. StrEnd[Len] = ' ';
  4128. }
  4129.  
  4130. return ( Str );
  4131.  
  4132. } /* function str_ljust */
  4133.  
  4134. /*****************************************************
  4135. Name: str_ltrim
  4136. Parameters: Str - string to trim
  4137. Trim - string containing chars to trim
  4138. Return: Str
  4139. Description: Delete characters from the left end of
  4140. Str that are contained in Trim
  4141. *****************************************************/
  4142. char *str_ltrim( char *Str, char *Trim )
  4143. {
  4144.  
  4145. size_t Num = strspn( Str, Trim );
  4146.  
  4147. str_delete( Str, Str, Num );
  4148.  
  4149. return ( Str );
  4150.  
  4151.  
  4152. } /* function str_ltrim */
  4153.  
  4154. /*****************************************************
  4155. Name: str_mrplc
  4156. Expanded Name: String Multiple Search and Replace
  4157. Parameters: Str - string to edit
  4158. Find - search string
  4159. Rplc - replacement string
  4160. Return: Str
  4161. Description: Multiple search and replace. All
  4162. occurrences of Find within Str are
  4163. replaced with Rplc.
  4164. *****************************************************/
  4165. char *str_mrplc( char *Str, char *Find, char *Rplc )
  4166. {
  4167.  
  4168. char *StrWork = Str;
  4169. size_t LenRplc = strlen( Rplc );
  4170. while ( ( StrWork =
  4171. strstr( StrWork, Find ) ) ! = NULL )
  4172. {
  4173. str_delete( Str, StrWork, strlen( Find ) );
  4174. str_insert( Str, StrWork, Rplc );
  4175. StrWork += LenRplc;
  4176. }
  4177.  
  4178. return ( Str );
  4179.  
  4180. } /* function str_mrplc */
  4181.  
  4182. /*****************************************************
  4183. Name: str_ocat
  4184. Expanded Name: Concatenate overlapped strings
  4185. Parameters: Dest - destination string
  4186. Str - string to concat
  4187. Return: Dest
  4188. Description: Behaves the same as strcat in string.h.
  4189. This version will work for strings that
  4190. overlap in memory.
  4191. *****************************************************/
  4192. char *str_ocat( char *Dest, char *Str )
  4193. {
  4194.  
  4195. return ( (char *)memmove(
  4196. (void *)&Dest[strlen( Dest )],
  4197. (void *)Str, strlen( Str) + 1 ) );
  4198.  
  4199. } /* function str_ocat */
  4200.  
  4201. /*****************************************************
  4202. Name: str_repeat
  4203. Parameters: Str - string buffer to load
  4204. Rpt - repetition string
  4205. Num - number of repetitions
  4206. Return: Str
  4207. Description: Builds a string of length Num, by
  4208. repeating a substring Rpt.
  4209. *****************************************************/
  4210. char *str_repeat( char *Str, char *Rpt, size_t Num )
  4211.  
  4212. {
  4213.  
  4214. size_t Len = strlen( Rpt );
  4215.  
  4216. if ( Len == 1 )
  4217. {
  4218. /* The string is only one character */
  4219. memset( Str, *Rpt, Num );
  4220. }
  4221. else
  4222. {
  4223.  
  4224. size_t i, j;
  4225.  
  4226. /* Build Str with repetitions of Rpt */
  4227. for ( i = 0, j = 0; i < Num; i++ )
  4228. {
  4229. Str[i] = Rpt[j++];
  4230. j %= Len;
  4231. } /* for i */
  4232.  
  4233. } /* else */
  4234.  
  4235. Str[Num] = '\0';
  4236.  
  4237. return ( Str );
  4238.  
  4239. } /* function str_repeat */
  4240.  
  4241. /**************************************************
  4242. Name: str_rplc
  4243. Expanded Name: String Search and Replace
  4244. Parameters: Str - string to edit
  4245. Find - search string
  4246. Rplc - replacement string
  4247. Return: Str
  4248. Description: Search and replace. First
  4249. occurrences of Find within Str is
  4250. replaced with Rplc.
  4251. ***************************************************/
  4252. char *str_rplc( char *Str, char *Find, char *Rplc )
  4253. {
  4254.  
  4255. char *StrWork = strstr( Str, Find );
  4256.  
  4257. if ( StrWork )
  4258. {
  4259. str_delete( Str, StrWork, strlen( Find ) );
  4260. str_insert( Str, StrWork, Rplc );
  4261. }
  4262.  
  4263. return ( Str );
  4264.  
  4265. } /* function str_rplc */
  4266.  
  4267. /*****************************************************
  4268. Name: str_rjust
  4269. Expanded Name: String Right Justify
  4270. Parameters: Str - string to edit
  4271.  
  4272. Len - new length of string
  4273. Return: Str
  4274. Description: Pads the left end of string so that it
  4275. is right justified to a total length of
  4276. Len.
  4277. *****************************************************/
  4278. char *str_rjust( char *Str, size_t Len )
  4279. {
  4280.  
  4281. size_t LenOrg = strlen( Str );
  4282.  
  4283. Len = max( LenOrg, Len ) - LenOrg;
  4284.  
  4285. if ( Len )
  4286. {
  4287. memmove( &Str[Len], Str, LenOrg + 1 );
  4288. while ( Len )
  4289. {
  4290. Len--;
  4291. Str[Len] = ' ';
  4292. }
  4293. }
  4294.  
  4295. return ( Str );
  4296.  
  4297. } /* function str_rjust */
  4298.  
  4299. /*****************************************************
  4300. Name: str_rtrim
  4301. Parameters: Str - string to trim
  4302. Trim - string of characters to trim
  4303. Return: Str
  4304. Description: Delete characters from the right end
  4305. of Str that are contained in Trim
  4306. *****************************************************/
  4307. char *str_rtrim( char *Str, char *Trim )
  4308. {
  4309.  
  4310. char *StrWork = &Str[strlen( Str) - 1];
  4311.  
  4312. /* Look for last character in string not being
  4313. ** in trim string */
  4314. while ( (Str != StrWork ) &&
  4315. ( strspn( StrWork, Trim ) != 0 ) )
  4316. {
  4317. *StrWork-- = '\0';
  4318. }
  4319.  
  4320. return ( Str );
  4321.  
  4322. } /* function str_rtrim */
  4323.  
  4324. /*****************************************************
  4325. Name: str_trim
  4326. Parameters: Str - string to trim
  4327. Trim - string of characters to trim
  4328. Return: Str
  4329. Description: Delete characters from both ends of Str
  4330. *****************************************************/
  4331.  
  4332. char *str_trim( char *Str, char *Trim )
  4333. {
  4334.  
  4335. str_ltrim( Str, Trim );
  4336. str_rtrim( Str, Trim );
  4337.  
  4338. return ( Str );
  4339.  
  4340. } /* function str_trim */
  4341.  
  4342. /*****************************************************
  4343. Name: str_vcat
  4344. Parameters: Dest - destination string
  4345. Strl - required first string
  4346. Return: Dest
  4347. Description: Variable argument version of strcat.
  4348. Concatenates a list of strings into
  4349. a destination string. The last
  4350. argument must be a NULL pointer.
  4351. *****************************************************/
  4352. char *str_vcat( char *Dest, char *Strl, .... )
  4353. {
  4354.  
  4355. va_list VarArgList;
  4356. char *Str;
  4357.  
  4358. /* Initialize variable arguments */
  4359. va_start( VarArgList, Dest );
  4360.  
  4361. /* Get first var arg string */
  4362. Str = va_arg( VarArgList, char * );
  4363.  
  4364. strcat( Dest, Strl );
  4365. while ( Str != NULL )
  4366. {
  4367.  
  4368. /* Loop though all the arguments */
  4369. strcat( Dest, Str );
  4370.  
  4371. /* Get the next string */
  4372. Str = va_arg( VarArgList, char * );
  4373.  
  4374. }
  4375.  
  4376. /* Clean up */
  4377. va_end( VarArgList );
  4378.  
  4379. return ( Dest );
  4380.  
  4381. } /* function str_vcat */
  4382.  
  4383. /* End of File */
  4384.  
  4385.  
  4386. Listing 4 STR_EDIT.H - contains function prototypes for Listing 3
  4387. /******************************************************
  4388. File Name: STR_EDIT.H (Listing 4)
  4389. Description: Include file for STR_EDIT.C
  4390. ******************************************************/
  4391.  
  4392.  
  4393. #if !defined( STR_EDIT_DEFINED )
  4394.  
  4395. #define STR_EDIT_DEFINED
  4396.  
  4397. char *str_center( char *Str, size t_Len );
  4398. char *str_delete( char *Str, char *Pos, size_t Num );
  4399. char *str_insert( char *Str, char *Pos, char *Insrt );
  4400. char *str_ljust( char *Str, size_t Len );
  4401. char *str_ltrim( char *Str, char *Trim );
  4402. char *str_mrplc( char *Str, char *Find, char *Rplc );
  4403. char *str_ocat( char *Dest, char *Str );
  4404. char *str_repeat( char *Str, char *Rpt, size_t Num );
  4405. char *str_rjust( char *Str, size_t Len );
  4406. char *str_rplc( char *Str, char *Find, char *Rplc );
  4407. char *str_rtrim( char *Str, char *Trim );
  4408. char *str_trim( char *Str, char *Trim );
  4409. char *str_vcat( char *Dest, char *Strl, ... );
  4410.  
  4411. #endif
  4412.  
  4413. /* End of File */
  4414.  
  4415.  
  4416.  
  4417.  
  4418.  
  4419.  
  4420.  
  4421.  
  4422.  
  4423.  
  4424.  
  4425.  
  4426.  
  4427.  
  4428.  
  4429.  
  4430.  
  4431.  
  4432.  
  4433.  
  4434.  
  4435.  
  4436.  
  4437.  
  4438.  
  4439.  
  4440.  
  4441.  
  4442.  
  4443.  
  4444.  
  4445.  
  4446.  
  4447.  
  4448.  
  4449.  
  4450.  
  4451.  
  4452.  
  4453.  
  4454.  
  4455. Hiding ISAM Function Libraries with OOP
  4456.  
  4457.  
  4458. Thomas Murphy
  4459.  
  4460.  
  4461. Thomas J. Murphy has a bachelor's degree in Physics from St. Ambrose College,
  4462. Davenport, IA, and a Ph.D. in Biophysics from the University of Illinois. He
  4463. has worked in the software-development field for 13 years and has owned his
  4464. own computer-consulting and custom software business for over seven years. His
  4465. major area of expertise is database systems. He can be reached at Computer
  4466. Management Consultants, Ltd, Box 132, RR #10, Oswego, NY 13126.
  4467.  
  4468.  
  4469. Function libraries each have a unique set of functions, unique lists of
  4470. arguments, and a unique user manual. Quality of documentation ranges from
  4471. excellent to so indecipherable that you must analyze the source code to
  4472. successfully use the product. On a project of any size, project programmers
  4473. have to surmount the learning curve, not only for the application and
  4474. hardware/operating system environment but for the function libraries in use.
  4475. In addition, you may be stuck with your first choice of libraries or face some
  4476. rather nasty problems if you want to switch to another library. Should you
  4477. convert the code for all your past clients so your current staff of
  4478. programmers (even if that is only you) can continue to maintain it? Should you
  4479. keep knowledgeable about every library you've ever used? Or should you just
  4480. stick with your original choice?
  4481. This article presents an example programmer interface to a special-purpose
  4482. function library designed to ease these problems for the case of a B+Tree,
  4483. Indexed Sequential Access Method (ISAM) data-handling function library. The
  4484. interface uses the standard Object-Oriented Programming (OOP) approach to
  4485. protecting the application programmer and the function library from each
  4486. other. It handles all the gory details of dealing with the function library by
  4487. inserting a layer of function calls between the application programmer and the
  4488. library itself. The functions called by the application programmer do not
  4489. depend on the particular library product in use. They depend only on what the
  4490. application programmer wants to do.
  4491. OOP is not a language; it is an approach to programming. The class presented
  4492. in this article is written in C++, but it does not use any of the flashier
  4493. bells and whistles most often talked about when one is discussing C++. There
  4494. is no operator overloading, no inheritance, no polymorphism. All this class
  4495. has is a little bit of data and function hiding and an OOP approach to doing
  4496. business. The code could be rewritten in Standard C by throwing all the
  4497. class-member data into a structure, declaring instances of the structure, and
  4498. passing as an argument the address of the declared structure to what are
  4499. presented here as member functions.
  4500. The examples in this article are for a specific product (CBTREE v3.0 by
  4501. Peacock Systems, Inc.). Without changing the calling parameters or the names
  4502. of the functions called by the application programmer, however, you could
  4503. rewrite the body of these functions for any of the several ISAM function
  4504. libraries I have used, and for many (no doubt) that I have not. The
  4505. application code that uses the class is no longer dependent on what data
  4506. handler is in use. The details of the data handler are hidden from the
  4507. application programmer.
  4508.  
  4509.  
  4510. What the Class Does
  4511.  
  4512.  
  4513. Before looking at the code, look at what it was designed to do. Class Isam is
  4514. a programmer interface to a data handler that uses ISAM files. After the
  4515. application programmer/analyst has set up the ISAM files in the application
  4516. (designed the record layouts, decided on index keys, created the files, etc.),
  4517. I want him/her to be as free as possible from the nitty-gritty details of
  4518. working with the data files themselves. The application programmer:
  4519. needs to be able to gain access to the data and index files with a single,
  4520. simple statement. If the files are already opened in another part of the
  4521. program, they should not be opened again; the programmer should just have
  4522. access to them.
  4523. after gaining access to a file, needs to have space automatically allocated
  4524. for the records he/she will be reading from the file. If the programmer is
  4525. going to be dealing with 20 records at once, space must be allocated for 20
  4526. records.
  4527. if modifying, deleting, and adding records, needs to be able to put the new
  4528. set of records in an array and write them. The programmer should not have to
  4529. keep track of which records have been worked on or what has been done to them.
  4530. The programmer should not have to keep track of which keys have to be removed
  4531. and added to the btree indices. In other words, the programmer needs to be
  4532. able to use indices without having to think about them.
  4533. should not have to write programs to re-index files. The class should do most
  4534. of the work. If the programmer had to re-index the file because he/she added
  4535. another index, all the remaining code should not need to be modified.
  4536. should have the freedom to write a function to create a key that manipulates
  4537. the data as desired.
  4538. be able to address the indices by name, instead of an index number.
  4539.  
  4540.  
  4541. How the Class Does It
  4542.  
  4543.  
  4544. Listing 1 contains the header file defining class Isam. Notice that one of the
  4545. member data items and one of the member functions is marked as specific to
  4546. CBTREE. The rest of the member data and functions are generic and would be
  4547. present regardless of the library being used. The code for the member
  4548. functions is shown in Listing 2.
  4549. The class contains ten public functions, eight of which are routinely called
  4550. by the application programmer. The destructor, ~Isam, is typically called only
  4551. automatically when the instance of the class goes out of scope. Isam::reindex
  4552. is called to recover from hopefully infrequent disasters. public data consists
  4553. of one string array, rec, which will contain records read from the ISAM file.
  4554.  
  4555.  
  4556. The Constructor
  4557.  
  4558.  
  4559. The programmer gains access to an ISAM file by declaring an instance of the
  4560. class, such as
  4561. Isam employeefile ("employee");
  4562. If the programmer will be dealing with more than one employee record at a
  4563. time, such as all the employees in a department (for a small company), the
  4564. maximum number can be specified in the declaration. The constructor, Isam,
  4565. will allocate space for that number, as in
  4566. Isam employeefile ("employee", 20);
  4567. The first time the Isam constructor is called in a program, it calls a static
  4568. function, isam_init, which loads a static array of structures, btparms[], with
  4569. file and index parameters for all files in the application. This information
  4570. is read from a setup file for the application. As it happens, CBTREE already
  4571. uses such a file in its data-file and b-tree creation utilities and,
  4572. optionally, in its runtime library. I found that CBTREE's parameter file,
  4573. btparms.btr, contained almost all the information needed for the Isam class.
  4574. Moreover, CBTREE provided a utility to display and modify these parameters.
  4575. The two parameters missing from the file were kstart, the starting character
  4576. position in the data record for a key, and keygen, the name of an application
  4577. function that will generate a key if specified.
  4578. Rather than re-invent the wheel, I modified the data entry and display utility
  4579. for the file provided by CBTREE to include these two parameters. Because the
  4580. data entry and display utility is proprietary, the modified code is not
  4581. presented here; but modification was completely straightforward. Adding the
  4582. parameters on the end of each record in the file had no ill effects on
  4583. CBTREE's use of the file. If you are not using CBTREE, you may have to create
  4584. your own parameter file.
  4585. In addition to file and index parameters, the btparms array also contains a
  4586. count (int count) of how many instances of class Isam are currently active
  4587. that access each data file. If the count is greater than zero for a particular
  4588. data file, btparms[] contains the file handles for the data and index files
  4589. (datafd and indxfd). Thus, multiple instances of Isam for the same data file
  4590. (in the same program) can share the file handles. The class constructor and
  4591. destructor (Isam and ~Isam) manage all bookkeeping involved.
  4592. The constructor, based on the parameters in btparms[] and on the callers
  4593. specification for the number of records to be handled at a time, allocates all
  4594. required space, thus satisfying the requirements for easy access and
  4595. automatically-allocated space.
  4596.  
  4597.  
  4598. Read and Write Functions
  4599.  
  4600.  
  4601. Isam::read and Isam::write work as a pair for reading records to be modified
  4602. and writing them back to the file. You can retrieve a record, modify it, and
  4603. write it back to the file using the get... functions (see the first if
  4604. statement in Isam::write), but the result would be the addition of the new
  4605. version of the record, not the replacement of the old version.
  4606. Isam::read asks the caller for a key (the only required parameter).
  4607. Optionally, the caller can specify how many records are to be read (default is
  4608. zero), what index to use (default is the first one), and where in the rec
  4609. array to start storing records (default is the beginning). The function
  4610. returns the number of records found matching the specified (perhaps partial)
  4611. key, regardless of how many records were specified to be read. Many would have
  4612. set the default number of records to be read at one; but I tend to do a lot of
  4613. "authority checking," reading a cross-reference file to determine whether or
  4614. not there is a record with the key entered by the user in a data entry
  4615. program. In this instance, I don't even care about the record; I just want to
  4616. know whether or not it is there. If you'd like the default to be one, simply
  4617. change the declaration in Listing 1.
  4618. The maximum number of records to be read plus the starting location in the rec
  4619. array must not exceed Isam::elements. (This check is made in the code.) If you
  4620. don't know the number of records to expect and you want all matches, you must
  4621. create an instance of Isam with room for one record, make a test read, and
  4622. create another instance of Isam with room for the number of records found
  4623. (integer returned by Isam::read).
  4624. Isam::read places each record read in public array, rec. It makes a second
  4625. copy in private array, oldrec. Isam::write uses the copy in oldrec to compare
  4626. the original version of the record as read with the new version to be written,
  4627. and to generate values for old keys to be removed from the b-tree(s).
  4628. A record can be added by calling Isam::clear, then placing the new record in
  4629. the array. Isam::rec and calling Isam::write. A record can also be added after
  4630. a failed attempt to read a record using a user-entered value for a key.
  4631. Records can be deleted by reading them using lsam::read, setting the
  4632. appropriate array element of rec to a null string, and calling lsam::write.
  4633. Modifying a record involves reading it using Isam::read, changing the data in
  4634. rec, and calling Isam::write. A typical calling sequence (using non-Isam
  4635. function names now) might look like
  4636. Isam invoice ("invoice");
  4637.  
  4638. Isam lineitm ("lineitm", 20);
  4639. while (1) {
  4640. paint_invoice_screen();
  4641. if (!get_invnum (inv_num))
  4642. break;
  4643. invoice.clear();
  4644. lineitm.clear();
  4645. if (invoice.read (inv_num,1)){
  4646. get_inv_flds(invoice.rec[0]);
  4647. lineitm.read(inv_num, 20);
  4648. get_lin_flds(lineitm.rec);
  4649. fill_inv_screen();
  4650. }
  4651. if (inv_edit()){
  4652. package_inv(invoice.rec[0]);
  4653. package_lin(lineitm.rec);
  4654. invoice.write();
  4655. lineitm.write();
  4656. }
  4657. else
  4658. break;
  4659. }
  4660. In this code segment, get_inv_flds extracts the fields from the record,
  4661. get_lin_flds extracts fields from the whole array of line item records, and
  4662. package_inv and package_lin put the (perhaps modified) fields back into the
  4663. records. These functions are still the responsibility of the application
  4664. programmer.
  4665. Application functions get_inv_flds, get_lin_flds, and inv_edit may have their
  4666. own instances of the Isam class (e.g., for data generation from customer and
  4667. product files based on customer and product numbers in the invoice and
  4668. lineitem records).
  4669. Once Isam::read has been called, the class should be cleared (Isam::clear) in
  4670. order to forget what it read if the next write is not related to the records
  4671. read. Notice in Listing 2 that Isam::write calls Isam::clear when its work is
  4672. done.
  4673. All key generation is done in Isam::write. The function generates both keys to
  4674. add to the b-tree(s) and keys to be removed from the b-tree(s). If a function
  4675. is specified (btparms[].keygen), that function is called to generate the key.
  4676. If not, a strnncpy from rec[] or oldrec[] is executed using btparms[].kstart
  4677. and btparms[].keylen (Function strnncpy came in the CBTREE library. It works
  4678. exactly like strncpy except that it null-terminates the copied string). Before
  4679. any keys are added to the database, all characters in the keys are converted
  4680. to upper case. Before any keys are used to retrieve records (in Isam::read or
  4681. Isam::getge), the same case conversion is executed automatically.
  4682. Should an error occur during execution of Isam::write (like disk full),
  4683. Isam::backout is called to reverse any changes to the index or data file made
  4684. for the record before the error occurred. Only the record being operated on at
  4685. the time of the error is backed out.
  4686. Listing 3 shows the source file for the catalog and cataloged programs used to
  4687. generate keys. The application programmer is assumed to have access to this
  4688. file (though not necessarily to the source file shown in Listing 2). In order
  4689. to set up a function to generate keys, the function name must be entered in
  4690. the parameter file (btparms.btr), the function prototype and code must be
  4691. added to Listing 3, and finally the name and address must be added to the
  4692. Catolog[] array in Listing 3. This process is referred to as cataloging the
  4693. function. Listing 3 shows a sample function, descwds, that returns a list of
  4694. words used in a description type field, ready for insertion into the b-tree.
  4695. The application programmer must insure that the strings returned by cataloged
  4696. functions are the correct length. The correct length can be any multiple of
  4697. btparms[].keylen. Multiples larger than one indicate multiple values in the
  4698. b-tree for the same record. Note how these are processed in the key sections
  4699. of Isam::write (Listing 2).
  4700.  
  4701.  
  4702. Re-Indexing Function
  4703.  
  4704.  
  4705. Once we've put key generation totally in the hands of Isam::write, it is a
  4706. relatively easy job to create a generic re-indexing function. (See the last
  4707. function in Listing 2.) Listing 4 shows a sample program that uses this
  4708. function. Note in Listing 4 that, not only are the parent ("invoice") and
  4709. child ("line item") files re-indexed, but all orphan line items (lineitem
  4710. records with an inv_num that corresponds to no record in the parent file) are
  4711. eliminated.
  4712.  
  4713.  
  4714. Index Keys
  4715.  
  4716.  
  4717. For performance considerations, the read function and the getfirst and getnext
  4718. functions described above require an integer argument specifying what index to
  4719. use. Some addition and deletion of indices can take place for a file without
  4720. the requirement for change to all code that uses the file. Thus, the
  4721. programmer may not know what the current or future value for the integer
  4722. corresponding to a particular index is. The Isam::keynum function returns the
  4723. integer corresponding to the argument btname. Note in Listing 2 that I've
  4724. chosen to make the name comparison case insensitive (stricmp).
  4725. It takes time to compare a specified name with the list of indices (stored in
  4726. Isam::btnames). Isam::keynum improves performance because it is typically
  4727. called once and the returned integer used several times in an application
  4728. function.
  4729. If the function fails to find the specified btname, there is obviously a
  4730. problem. The function executes a fatal-error exit (eprintf). Conceivably,
  4731. however, the function could be called in response to entry of an index name
  4732. specified by an end-user. If this type of interaction with the end-user is
  4733. included in the code, the function should be written to return a negative
  4734. integer to indicate failure rather than fatally exiting.
  4735.  
  4736.  
  4737. Report-Creation Functions
  4738.  
  4739.  
  4740. The functions, Isam::getfirst, lsam::getge, and Isam::getnext are designed for
  4741. application procedures that go through the file in the order of an index
  4742. (reports and data display programs). Isam::getfirst puts into Isam::rec[0] the
  4743. first record in the file (as viewed through the specified index). Isam::getge
  4744. puts into Isam:: rec[0] the first record with a key greater than or equal to
  4745. the specified key. Isam::getnext gets the record following the one read by the
  4746. last call to a function starting with get. These functions are the bare-bones
  4747. minimum for putting together reasonable reports. All of the ISAM data-handling
  4748. libraries have more functions of this type (getlast, getprevious, etc.). Add
  4749. them to the class if you use them. A typical code segment using the get
  4750. functions might look like
  4751. Isam inv("invoice");
  4752. int idx=inv.keynum("DATE_INVNUM");
  4753.  
  4754. inv.getge(idx, begin_date);
  4755. get_inv_flds(inv.rec[0]);
  4756. print_inv_summary_header();
  4757. while (inv_date <= end_date) {
  4758. print_inv_summary_line();
  4759. if (!inv.getnext (idx))
  4760. break;
  4761.  
  4762. get_inv_flds(inv.rec [0]);
  4763. }
  4764.  
  4765.  
  4766. Utility Functions
  4767.  
  4768.  
  4769. There are three non-class-member functions used and shown in Listing 2.
  4770. Function nospace removes all spaces from a character string. Function ToUpper
  4771. converts all characters in a string to upper case. (It's a big version of
  4772. toupper.) Function eprintf, as it is shown in Listing 2, works like members of
  4773. the printf family, except that, after printing the error message, it exits the
  4774. program. My own version of eprintf uses the window class in my toolbox to
  4775. print the error message in a bright red window, sound an error buzzer, and
  4776. wait for the user to write down the error message and press a key before
  4777. exiting.
  4778.  
  4779.  
  4780. Conclusion
  4781.  
  4782.  
  4783. The Isam class presented in this article is an example of how OOP can be used
  4784. to set up a function-library interface. With the interface, application code
  4785. can be independent of the function library. More importantly, with the
  4786. interface, a function library written by somebody else can work the way you
  4787. decide it should work.
  4788. Using CBTREE with C++
  4789. The CBTREE function library was developed in standard C. In order to use it
  4790. with C++, all CBTREE headers must be included using the extern "C"
  4791. modification (see Listing 1).
  4792. The code presented in this article was compiled using Borland's C/C++ 3.1
  4793. compiler. In my hands, the CBTREE library was originally compiled using
  4794. Borland's Turbo C 2.0. While preparing this article, I retrieved the original
  4795. distribution disks and recompiled the CBTREE library programs using Borland's
  4796. C/C++ 3.1 compiler rebuilt the library. I recompiled and linked an application
  4797. using the code presented here using the newly rebuilt library. Aside from a
  4798. small reduction in the size of the library, I saw no change.
  4799. To successfully compile and link my programs, I had to make one change to the
  4800. original CBTREE code. In the main header file, CBTREE.H, there is a
  4801. superfluous declaration for function lockunlk (line 142) that does not include
  4802. the argument list prototype. In C mode, the Borland compiler ignored this. In
  4803. C++ mode, the compiler interpreted this as an overloaded function declaration
  4804. and refused to continue because of the extern C modification. Removing the
  4805. declaration (I commented mine out) made everybody happy.
  4806.  
  4807. Listing 1 isam.h
  4808. ////////////
  4809. // isam.h //
  4810. ////////////
  4811.  
  4812. #ifndef isam_h
  4813. #define isam_h
  4814. extern "C" {
  4815. #include <cbtree.h> // CBTREE header
  4816. #include <btfio.h> // CBTREE header
  4817. }
  4818.  
  4819. typedef char *(*t_func)(char *);
  4820. typedef int (*rel_func)(char *);
  4821.  
  4822. class Isam
  4823. {
  4824. private:
  4825. int elements, fd[2], btr[10], indices, backingout;
  4826. long * loc;
  4827. char ** oldrec, * okey, * nkey, ** inames;
  4828. BTC * btc; // CBTREE specific
  4829. int getxxx (int index, int opt); // CBTREE specific
  4830. void backout (int ele, char op, int index = -1,
  4831. int result = 0);
  4832. public:
  4833. char ** rec;
  4834.  
  4835. Isam (const char *datafilename, int e = 1);
  4836. ~Isam ( );
  4837.  
  4838. int read (const char *key, int ele_limit = 0,
  4839. int idx = 0, int ele = 0);
  4840. int write ();
  4841. void clear ();
  4842. int getfirst (int index = 0);
  4843. int getnext (int index = 0);
  4844. int getge (char *key, int index = 0);
  4845. int keynum (const char *btname);
  4846. void reindex (rel_func func);
  4847.  
  4848. };
  4849.  
  4850. char * nospace(const char *arg);
  4851. int eprintf(const char *format, ...);
  4852. char * ToUpper(const char *c);
  4853.  
  4854. ///////////////////////////////
  4855. // catalog utility functions //
  4856. ///////////////////////////////
  4857.  
  4858. int catalog_number (char *name);
  4859. t_func cataloged_func (int f );
  4860.  
  4861. #endif
  4862.  
  4863. /* End of File */
  4864.  
  4865.  
  4866. Listing 2 isam. cpp
  4867. //////////////
  4868. // Isam.cpp //
  4869. //////////////
  4870.  
  4871. #include "isam.h"
  4872. #include <fstream.h>
  4873. #include <stdio.h>
  4874. #include <string.h>
  4875. #include <stdlib.h>
  4876. #include <stdarg.h>
  4877. #include <ctype.h>
  4878.  
  4879. static struct BTParms {
  4880. char name[9], filename[9];
  4881. int keylen, dreclen, maxrecs, indxfd, datafd,
  4882. kstart, count;
  4883. t_func keygen;
  4884. } btparms[40];
  4885.  
  4886. static int items = 0;
  4887.  
  4888. extern "C" {
  4889. int (*Write)(int,const void *,unsigned) = write;
  4890. int (*Read)(int,void *,unsigned) = read;
  4891. }
  4892. static void isam_init(void);
  4893.  
  4894. static void isam_init(void)
  4895. {
  4896. ifstream btr("btparms.btr");
  4897. char nwln, item[81], buf[20], fname[13], fcname[9];
  4898. int i;
  4899.  
  4900. if (!btr)
  4901. eprintf("\n\nUnable to open btparms.btr.\n");
  4902.  
  4903. for (i = 0; i < 40; i++) {
  4904. btr.get (item, 80);
  4905. if (!strlen(item))
  4906. break;
  4907.  
  4908. btr.get(nwln);
  4909. strcpy(btparms[i].name, strtok(item, "^"));
  4910. strtok(NULL, "^");
  4911. strcpy(buf, strtok(NULL, "^"));
  4912. btparms[i].keylen = atoi(buf);
  4913. strtok(NULL, "^");
  4914. strcpy(buf, strtok(NULL, "^"));
  4915. btparms[i] .dreclen = atoi (buf);
  4916. strtok(NULL, "^");
  4917. strcpy(fname, strtok(NULL, "^"));
  4918. strtok(NULL, "^");
  4919. strcpy(buf, strtok(NULL, "^"));
  4920. btparms[i].kstart = atoi(buf);
  4921. strcpy(fcname, nospace(strtok(NULL, "^")));
  4922. strcpy(btparms[i].filename, strtok(fname,"."));
  4923. btparms[i].count = O;
  4924. btparms[i].indxfd = btparms[i].datafd = -1;
  4925. if (strlen(fcname))
  4926. btparms[i].keygen = cataloged_func (
  4927. catalog_number(fcname));
  4928. else
  4929. btparms[i].keygen = (t_func) NULL;
  4930. }
  4931.  
  4932. items = i;
  4933. btr.close();
  4934.  
  4935. if (!items)
  4936. eprintf("\n\nUnable to initialize isam system.\n");
  4937.  
  4938. for (i = items; i < 40; i++) {
  4939. strcpy(btparms[i].name,"");
  4940. strcpy(btparms[i].filename,"");
  4941. btparms[i].keygen = NULL;
  4942. btparms[i].keylen = btparms[i].dreclen =
  4943. btparms[i].count = btparms[i].kstart = 0;
  4944. btparms[i].indxfd = btparms[i].datafd = -1;
  4945. }
  4946. }
  4947.  
  4948. Isam::Isam(const char *datafilename, int e)
  4949. {
  4950. int i, j;
  4951. char buf[20];
  4952.  
  4953. backingout = 0;
  4954. elements = e;
  4955. if (!items) // this is the first Isam
  4956. isam_init(); // initialize btparms
  4957.  
  4958. strcpy(buf, nospace(datafilename));
  4959. indices = 0;
  4960. // find btree params
  4961. for (i = 0; i < items; i++)
  4962. if (!stricmp(buf, btparms[i].filename))
  4963. btr[indices++] = i;
  4964. if (!indices) // couldn't find any, fatal
  4965. eprintf("\n\nCan't find parameters for %s!\n",
  4966. datafilename);
  4967.  
  4968. // set up btree interfaces
  4969.  
  4970. if( !(btc = new BTC [indices]) 
  4971. !(inames = new char* [indices]) 
  4972. !(loc = new long [elements]) 
  4973. !(oldrec = new char* [elements]) 
  4974. !(rec = new char* [elements]) 
  4975. !(okey = new char [btparms[*btr].dreclen]) 
  4976. !(nkey = new char [btparms[*btr].dreclen]) )
  4977. eprintf("\n\nOut of memory.\n");
  4978. for (i = 0; i < indices; i++) {
  4979. inames[i] = btparms[btr[i]].name;
  4980. btc[i].btvcount = 1;
  4981. if (btrinit(inames[i], btc+i) == ERR)
  4982. eprintf("\n\nCouldn't initialize %s.\n",
  4983. inames[i]);
  4984. btc[i].btmulti = 0; // record locking off
  4985. }
  4986. for (i = 0; i < elements; i++) {
  4987. loc[i] = OL;
  4988. if (!(oldrec[i] = new char
  4989. [btparms[*btr].dreclen + 1]) 
  4990. !(rec[i] = new char
  4991. [btparms[*btr].dreclen + 1]) )
  4992. eprintf("\n\nOut of memory.\n");
  4993. }
  4994. if (btparms[*btr].count) { // open files if neccesary
  4995. fd[0] = btparms[*btr].datafd;
  4996. fd[1] = btparms[*btr].indxfd;
  4997. }
  4998. else { // yup, it's neccesary
  4999. strcpy(buf, btparms[*btr].filename);
  5000. strcat(buf, ".dat");
  5001. if ((fd[0]:bt_open(buf, 0_RDWR,S_IRDWR)) == ERR)
  5002. eprintf("\n\nCan't open %s.\n", buf);
  5003. strcpy(buf, btparms[*btr].filename);
  5004. strcat(buf, ".idx");
  5005. if ((fd[1]=bt_open(buf, 0_RDWR,S_IRDWR)) == ERR)
  5006. eprintf("\n\nCan't open %s.\n", buf);
  5007. btparms[*btr].datafd = fd[0];
  5008. btparms[*btr].indxfd = fd[1];
  5009. }
  5010.  
  5011. (btparms[*btr].count)++; // record that we are here
  5012. clear();
  5013. }
  5014.  
  5015. Isam::~Isam()
  5016. {
  5017. int i, j;
  5018. // clean up out mess
  5019. for (i = 0; i < indices; i++)
  5020. btrterm(btc+i);
  5021. for (i = 0; i < elements; i++) {
  5022. delete [] oldrec[i];
  5023. delete [] rec[i];
  5024. }
  5025. delete [] btc;
  5026. delete [] inames;
  5027.  
  5028. delete [] loc;
  5029. delete [] oldrec;
  5030. delete [] rec;
  5031. delete [] nkey;
  5032. delete [] okey;
  5033. // if we're last ones out, turn off the lights
  5034. if (!(--(btparms[*btr].count))) {
  5035. close (fd[0]);
  5036. close (fd[1]);
  5037. btparms[*btr].indxfd = -1;
  5038. btparms[*btr].datafd = -1;
  5039. }
  5040. }
  5041.  
  5042. int Isam::write ()
  5043. {
  5044. int deltakey, oldl, new1, ele, index, result, len,
  5045. start, notfirst;
  5046. t_func fcn;
  5047. if (*loc < 0) // programmer hasn't followed rules
  5048. eprintf ("No writes after gets!");
  5049. for (ele = 0; ele < elements; ele++) {
  5050. old1 : strlen(oldrec[ele]);
  5051. new1 = strlen( rec[ele]);
  5052. if (!(old1 new1) 
  5053. !strcmp(oldrec[ele], rec[ele]))
  5054. continue;
  5055. for (index = 0; index < indices; index++) {
  5056. // generate the old and/or new key
  5057. len = btparms[btr[index]].keylen;
  5058. start = btparms[btr[index]].kstart;
  5059. if ((fcn = btparms[btr[index]].keygen) !=
  5060. (t_func) NULL) {
  5061. if (old1)
  5062. strcpy(okey, fcn(oldrec[ele]));
  5063. if (new1)
  5064. strcpy(nkey, fcn( rec[ele]));
  5065. }
  5066. else {
  5067. if (old1)
  5068. strnncpy(okey, oldrec[ele] + start, len);
  5069. if (new1)
  5070. strnncpy(nkey, rec[ele] + start, len);
  5071. }
  5072. deltakey = (!old1!new1stricmp(okey,nkey));
  5073.  
  5074. if (old1 && deltakey) {
  5075. notfirst = 0;
  5076. while (strlen(okey) >= len) {
  5077. strnncpy(btc[index].btkey, ToUpper(okey),
  5078. len);
  5079. btc[index].btoptype = (new1 index 
  5080. notfirst++ backingout) ? DELTKY
  5081. : DELETE;
  5082. btc[index].btloc = loc[ele];
  5083. result=cbtree(fd[O], fd[1], btc+index);
  5084. if (result != BTCALLOK)
  5085. backout (ele, 'D', index, result);
  5086. strcpy(okey, okey + len);
  5087.  
  5088. }
  5089. }
  5090. if (new1 && deltakey) {
  5091. notfirst = 0;
  5092. while (strlen(nkey) >= len) {
  5093. strnncpy(btc[index].btkey, ToUpper(nkey),
  5094. len);
  5095. btc[index].btoptype = (loc[ele] == OL) ?
  5096. INSERT : ISRTKY;
  5097. btc[index].btloc = loc[ele];
  5098. result = cbtree(fd[0], fd[1], btc+index);
  5099. if (result == BTCALLOK)
  5100. loc[ele] = btc[index].btloc;
  5101. else
  5102. backout(ele, 'I', index, result);
  5103. strcpy(nkey, nkey+ len);
  5104. }
  5105. }
  5106. }
  5107. if (!new1)
  5108. continue;
  5109. if (btseek (fd[0], loc[ele], btc->btdtalen)
  5110. == -1L)
  5111. backout(ele, 'S');
  5112. if(Write(fd[0],rec[ele],(unsigned)btc->btdtalen)
  5113. !=btc->btdtalen)
  5114. backout(ele,'W');
  5115. }
  5116. clear();
  5117. return 0;
  5118. }
  5119.  
  5120. void Isam::backout(int ele,char op,int index,int result)
  5121. { //backs out record add/change/delete on error
  5122. char *trec;
  5123. if(!(trec = new char [btparms[*btr].dreclen]) 
  5124. backingout)// Iquit! Error in error backout!
  5125. eprintf("\n\nBackout error, error type was %c\n",
  5126. backingout);
  5127. if (index>=0)
  5128. indices = index + 1; // those beyond index are ok
  5129. backingout = op;
  5130. elements = 1;
  5131. strcpy(trec,rec[ele]);
  5132. strcpy(rec[0],oldrec[ele]);
  5133. strcpy(oldrec[0],trec);
  5134. loc[0] = loc[ele];
  5135. write();
  5136. switch(backingout) {
  5137. case 'I':
  5138. eprintf(
  5139. "\n\nINSERT error, element %d, index %s, result %d\n",
  5140. ele, inames[index],result);
  5141. case 'D':
  5142. eprintf(
  5143. "\n\nDELETE error, element %d, index %s, result %d\n",
  5144. ele, inames[index],result);
  5145. case 'S':
  5146. eprintf ("\n\nSeek error, element %d\n", ele);
  5147.  
  5148. case 'W':
  5149. eprintf ("\n\nWrite error, element %d\n", ele);
  5150. }
  5151. }
  5152.  
  5153. int Isam::read(const char *key, int ele_limit, int idx,
  5154. int ele)
  5155. {
  5156. int i = 0, j = 0;
  5157.  
  5158. if ((ele_limit + ele) > elements)
  5159. eprintf("\n\nNot enough elements!\n");
  5160. free_svkey(btc+idx);
  5161. btc[idx].btoptype = GETALL;
  5162. strcpy(btc[idx].btkey, ToUpper(key));
  5163. btc[idx].btloc = OL;
  5164. while (cbtree(fd[0], fd[1], btc + idx) == BTCALLOK) {
  5165. while (btc[idx].btrecnum[i-j] != OL) {
  5166. if (i < ele_limit) {
  5167. loc[i+ele] = bte[idx].btrecnum[i-j];
  5168. btseek(fd[0], loc[i+ele], btc[idx].btdtalen);
  5169. Read (fd[0], rec[i+ele], btc[idx].btdtalen);
  5170. }
  5171. i++;
  5172. if (!((i-j) < btc[idx].btmax))
  5173. break;
  5174. }
  5175. if ((i-j) < btc[idx].btmax)
  5176. break;
  5177. j += btc[idx].btmax;
  5178. }
  5179. for (j = 0; j < ele_limit;j++)
  5180. strcpy(oldrec[j], rec[j]);
  5181. return i;
  5182. }
  5183.  
  5184. int Isam::getfirst(int index)
  5185. {
  5186. return getxxx (index, GETFRST);
  5187. }
  5188.  
  5189. int Isam::getnext(int index)
  5190. {
  5191. return getxxx(index, GETNXT);
  5192. }
  5193.  
  5194. int Isam::getge (char *key, int index)
  5195. {
  5196. *loc = -1;
  5197. btc[index].btoptype = GETGE;
  5198. strcpy(btc[index].btkey,ToUpper(key));
  5199. if (!(cbtree(fd[0], fd[1], btc + index) == BTCALLOK))
  5200. return 0;
  5201. btseek(fd[0], btc[index].btloc, btc[index].btdtalen);
  5202. Read (fd[0], rec[0], btc[index].btdtalen);
  5203. return 1;
  5204. }
  5205.  
  5206. int Isam::getxxx(int index, int opt)
  5207.  
  5208. {
  5209. *loc = -1;
  5210. btc[index].btoptype = opt;
  5211. if (!(cbtree(fd[0], fd[1], btc + index) == BTCALLOK))
  5212. return 0;
  5213. btseek(fd[0], btc[index].btloc, btc[index].btdtalen);
  5214. Read (fd[0], rec[0], btc[index].btdtalen);
  5215. return 1;
  5216. }
  5217.  
  5218. void Isam::clear()
  5219. {
  5220. int i;
  5221. for (i = 0; i < elements; i++) {
  5222. loc[i] = OL;
  5223. memset (oldrec[i], '\0', btparms[*btr].dreclen +
  5224. 1);
  5225. memset ( rec[i], '\0', btparms[*btr].dreclen + 1);
  5226. }
  5227. }
  5228.  
  5229. int Isam::keynum (const char *btname)
  5230. {
  5231. int i;
  5232.  
  5233. if (!items)
  5234. eprintf("\n\nBtparms not initialized!\n");
  5235. if (indices < 1)
  5236. eprintf("\n\nNo indices.\n");
  5237. for (i = 0; i <indices; i++)
  5238. if (!stricmp(inames[i], nospace(btname)))
  5239. return i;
  5240. eprintf ("\n\nIndex %s not found.\n", btname);
  5241. return -1;
  5242. }
  5243.  
  5244. char* nospace(const char *arg)
  5245. {
  5246. static char rtn[80];
  5247. int i, j = 0, k = strlen(arg);
  5248. for (i = 0; i < k; i++)
  5249. if (arg[i] != ' ')
  5250. rtn[j++] = arg[i];
  5251. rtn[j] = '\0';
  5252. return rtn;
  5253. }
  5254.  
  5255. int eprintf(const char *format, ...)
  5256. {
  5257. int rtn;
  5258. va_list argptr;
  5259. va_start(argptr, format);
  5260. rtn = vprintf(format, argptr);
  5261. va_end (argptr);
  5262. exit (1);
  5263. return rtn;
  5264. }
  5265.  
  5266. char *ToUpper(const char *c)
  5267.  
  5268. {
  5269. static char d[257];
  5270. int i;
  5271.  
  5272. for (i = 0; i < 256, c[i] != '\0'; i++)
  5273. d[i] = toupper(c[i]);
  5274. d[i] = '\0';
  5275. return d;
  5276. }
  5277.  
  5278. void Isam::reindex(rel_func func)
  5279. {
  5280. char buf[20], cmd[80];
  5281. int tad, i, rlen = btparms[*btr].dreclen, x, y;
  5282. long l;
  5283.  
  5284. clear();
  5285. strcpy(buf, btparms[*btr].filename);
  5286. strcat(buf, ".tad");
  5287. if (bt_open(buf, 0_RDWR,S_IRDWR) != ERR)
  5288. // I can't cook in a dirty kitchen!
  5289. // Besides that, the last re-index
  5290. // must have failed and I don't know
  5291. // where the real data is, now.
  5292. eprintf("\n\n%s still exists!\n", buf);
  5293. sprintf(cmd, "ren %s.dat %s.tad",
  5294. btparms[*btr].filename, btparms[*btr].filename);
  5295. close (fd[0]);
  5296. system (cmd); // ren .dat to .tad
  5297. if ((tad = bt_open(buf, 0_RDWR,S_IRDWR)) == ERR)
  5298. eprintf("n\nCan't open %s\n", buf);// ren didn't go
  5299. strcpy(buf, btparms[*btr].filename); // recreate .dat
  5300. strcat(buf, ".dat");
  5301. if((fd[0]=bt_open(buf, 0_NEW/0_RDWR, S_IRDWR) ) == ERR)
  5302. eprintf ("\n\nCouldn't recreate %s.dat\n",
  5303. btparms[*btr].filename);
  5304. initdat(fd[0], btc); // create file header
  5305. btparms[*btr].datafd = fd[0];
  5306. strcpy(buf, btparms[*btr].filename); // unlink .idx
  5307. strcat(buf, ".idx");
  5308. close (fd[1]);
  5309. unlink (buf);
  5310. for (i = 0; i < indices; i++) {
  5311. btrterm(btc + i); // to btrinit() with no .idx
  5312. if (btrinit(inames, btc+i) == ERR)
  5313. eprintf("\n\nCouldn't re-initialize %s.\n",
  5314. inames[i]);
  5315. creatbtr(btc + i);
  5316. }
  5317. for (i = 0; i < indices; i++) { // re-initialize
  5318. btrterm(btc + i);
  5319. if (btrinit(inames[i], btc+i) == ERR)
  5320. eprintf("\n\nCouldn't re-initialize %s.\n",
  5321. inames[i]);
  5322. } // We now have empty datafile and indexfile
  5323. strcpy(buf, btparms[*btr].filename); // re-open .idx
  5324. strcat(buf, ".idx");
  5325.  
  5326. if((fd[1]:bt_open(buf, 0_RDWR, S_IRDWR)) == ERR)
  5327.  
  5328. eprintf ("\n\nCouldn't recreate %s.idx\n",
  5329. btparms[*btr].filename);
  5330. btparms[*btr].indxfd = fd[1];
  5331. clrscr(); // we're ready
  5332. l = 2L; i = 0;
  5333. while (1) {
  5334. if (!(l%10)) {
  5335. if (l < 11L) {
  5336. gotoxy(10,10);
  5337. cprintf("Processing %s location ",
  5338. btparms[*btr].filename);
  5339. x = wherex(); y = wherey();
  5340. }
  5341. gotoxy(x, y);
  5342. cprintf("%ld", l);
  5343. }
  5344. btseek(tad, 1++, rlen);
  5345. if (Read (tad, *rec, rlen) == rlen) {
  5346. if (**rec != '~') {
  5347. if (func)
  5348. if (func(*rec))
  5349. continue;
  5350. write();
  5351. i++;
  5352. }
  5353. }
  5354. else
  5355. break;
  5356. }
  5357. printf ("\n\n%d records added.\n", i);
  5358. close (tad);
  5359. strcpy(buf, btparms[*btr].filename);
  5360. strcat(buf, ".tad");
  5361. unlink (bur); // clean kitchen for next time
  5362. }
  5363. /* End of File */
  5364.  
  5365.  
  5366. Listing 3 catalog.cpp
  5367. /////////////////
  5368. // catalog.cpp //
  5369. /////////////////
  5370.  
  5371. #include "isam.h"
  5372. #include <ctype.h>
  5373.  
  5374. static struct cat {
  5375. char *name;
  5376. t_func func;
  5377. } Catalog[] = { { "descwrds", descwrds },
  5378. { NULL, NULL }
  5379. };
  5380.  
  5381. ///////////////////////
  5382. // Catalog utilities //
  5383. ///////////////////////
  5384.  
  5385. int catalog_number (char *name)
  5386. {
  5387.  
  5388. int i = 0;
  5389. while (Catalog[i].name) {
  5390. if (!strcmp(nospace(name), Catalog[i].name))
  5391. return i;
  5392. i++;
  5393. }
  5394. return -1;
  5395. }
  5396.  
  5397. t_func cataloged_func(int f)
  5398. {
  5399. return Catalog[f].func;
  5400. }
  5401.  
  5402. /////////////////////////
  5403. // Cataloged functions //
  5404. /////////////////////////
  5405.  
  5406. char * descwrds (char *r);
  5407.  
  5408. char *descwrds (char *r)
  5409. { // Extracts up to 20 seven-letter words from
  5410. // desc field. The description field starts at
  5411. // posn 37 and goes on for 120 characters.
  5412. static char keys[141];
  5413. int i = 37, j = 0, k = 0;
  5414.  
  5415. memset (keys, ' ' , 140);
  5416. while ((i < 157) && (k < 20)){
  5417. while ((ispunct(r[i]) isspace(r[i])) && i < 157)
  5418. i++;
  5419. while (!ispunct(r[i]) && !isspace(r[i]) && i < 157
  5420. && j < 7)
  5421. keys[(7 * k) + j++] = r[i++];
  5422. while (!ispunct(r[i]) && !isspace(r[i]) && i < 157)
  5423. i++;
  5424. k++;
  5425. j = 0;
  5426. }
  5427. keys[7 * k] = '\0';
  5428. return keys;
  5429. }
  5430.  
  5431. /* End of File */
  5432.  
  5433.  
  5434. Listing 4 A sample program using the re-index function Isam::re-index
  5435. #include "isam.h"
  5436.  
  5437. int orphan (char *rec);
  5438.  
  5439. static Isam i ("invoice");
  5440.  
  5441. void main(void)
  5442. {
  5443. Isam l ("lineitem");
  5444.  
  5445. i.reindex (rel_func NULL);
  5446. l.reindex (orphan);
  5447.  
  5448. }
  5449.  
  5450. int orphan (char *rec)
  5451. {
  5452. char inv_num[10];
  5453. strnncpy(inv_num, rec + 7, 9);
  5454. return (i.read(inv_num)) ? 0 : 1;
  5455. }
  5456.  
  5457. /* End of File */
  5458.  
  5459.  
  5460.  
  5461.  
  5462.  
  5463.  
  5464.  
  5465.  
  5466.  
  5467.  
  5468.  
  5469.  
  5470.  
  5471.  
  5472.  
  5473.  
  5474.  
  5475.  
  5476.  
  5477.  
  5478.  
  5479.  
  5480.  
  5481.  
  5482.  
  5483.  
  5484.  
  5485.  
  5486.  
  5487.  
  5488.  
  5489.  
  5490.  
  5491.  
  5492.  
  5493.  
  5494.  
  5495.  
  5496.  
  5497.  
  5498.  
  5499.  
  5500.  
  5501.  
  5502.  
  5503.  
  5504.  
  5505.  
  5506.  
  5507.  
  5508.  
  5509.  
  5510.  
  5511. Standard C
  5512.  
  5513.  
  5514. The Header <time.h>
  5515.  
  5516.  
  5517.  
  5518.  
  5519. P.J. Plauger
  5520.  
  5521.  
  5522. P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
  5523. ANSI C standards committee, X3J11, and convenor of the ISO C standards
  5524. committee, WG14. His latest books are The Standard C Library, published by
  5525. Prentice-Hall, and ANSI and ISO Standard C (with Jim Brodie), published by
  5526. Microsoft Press. You can reach him at pjp@plauger.com.
  5527.  
  5528.  
  5529.  
  5530.  
  5531. Background
  5532.  
  5533.  
  5534. Time and date calculations achieved a new level of sophistication under the
  5535. UNIX operating system. Several of the developers of that system were amateur
  5536. astronomers. They were sensitive to the need for representing times over a
  5537. span of decades, not just years. They automatically reckoned time as Greenwich
  5538. Mean Time (once GMT, now UTC), not just by the clock on the wall. They were,
  5539. in short, more finicky than most about measuring and representing time on a
  5540. computer.
  5541. That same attention to detail has spilled over into the Standard C library.
  5542. Its scope is basically whatever was available in C under UNIX that didn't
  5543. depend on the peculiarities of UNIX. As a consequence, you can do a lot with
  5544. times and dates in Standard C. The functions declared in <time. h> provide the
  5545. relevant services.
  5546. It stretches the truth a bit to say that these functions don't depend on the
  5547. peculiarities of UNIX. Not all operating systems distinguish between local
  5548. time and UTC. Even fewer allow different users to display times relative to
  5549. different time zones. Some of the smallest systems can't even give you the
  5550. time of day. Yet all implementations of C must take a stab at telling time
  5551. wisely if they want to claim conformance to the C Standard.
  5552. The C Standard contains enough weasel words to let nearly everybody off the
  5553. hook. A system need only provide its "best approximation" to the current time
  5554. and date, or to processor time consumed, to conform to the C Standard. A
  5555. vendor could argue that 1 January 1980 is always the best available
  5556. approximation to any time and date. A customer can rightly quarrel about the
  5557. low quality of such an approximation, but not whether it satisfies the C
  5558. Standard.
  5559. What this means in practice is that a program should never take times too
  5560. seriously. It can enquire about the current time (by calling time) and display
  5561. what it gets in a variety of attractive formats. But it can't know for sure
  5562. that the time and date are meaningful. If you have an application that depends
  5563. critically upon accurate time stamps, check each implementation of Standard C
  5564. closely.
  5565. There are too many functions declared in <time.h> to cover in one installment.
  5566. So I've divided the presentation into three parts:
  5567. the basic time functions--those that yield results of type clock_t, time_t, or
  5568. double
  5569. the conversion functions--those that convert times between scalar and
  5570. structured forms
  5571. the formatting functions--those that yield text representations of encoded
  5572. times
  5573. The topic for this month is the basic functions.
  5574.  
  5575.  
  5576. What the C Standard Says
  5577.  
  5578.  
  5579.  
  5580.  
  5581. 7.12 Date and time <time.h>
  5582.  
  5583.  
  5584.  
  5585.  
  5586. 7.12.1 Components of time
  5587.  
  5588.  
  5589. The header <time.h> defines two macros, and declares four types and
  5590. severalufunctions for manipulating time. Many functions deal with a calendar
  5591. time that represents the current date (according to the Gregorian calendar)
  5592. and time. Some functions deal with local time, which is the calendar time
  5593. expressed for some specific time zone, and with Daylight Saving Time, which is
  5594. a temporary change in the algorithm for determining local time. The local time
  5595. zone and Daylight Saving Time are implementation-defined.
  5596. The macros defined are NULL (described in 7.1.6); and
  5597. CLOCKS_PER_SEC
  5598. which is the number per second of the value returned by the clock function.
  5599. The types declared are size_t (described in 7.1.6);
  5600. clock t
  5601. and
  5602. time_t
  5603. which are arithmetic types capable of representing times; and
  5604. struct tm
  5605. which holds the components of a calendar time, called the broken-down time.
  5606. The structure shall contain at least the following members, in any order. The
  5607. semantics of the members and their normal ranges are expressed in the
  5608. comments.137
  5609. int tm_sec;
  5610. /* seconds after the minute- [0, 61] */
  5611.  
  5612. int tm_min;
  5613. /* minutes after the hour- [0, 59] */
  5614. int tm_hour;
  5615. /* hours since midnight- [0, 23] */
  5616. int tm_mday;
  5617. /* day of the month- [1, 31] */
  5618. int tm_mon;
  5619. /* months since January- [0, 11] */
  5620. int tm_year;
  5621. /* years since 1900 */
  5622. int tm_wday;
  5623. /* days since Sunday- [0, 6] */
  5624. int tm_yday;
  5625. /* days since January 1- [0, 365] */
  5626. int tm_isdst;
  5627. /* Daylight Saving Time flag */
  5628. The value of tm_isdst is positive if Daylight Saving Time is in effect, zero
  5629. if Daylight Saving Time is not in effect, and negative if the information is
  5630. not available.
  5631.  
  5632.  
  5633. 7.12.2 Time manipulation functions
  5634.  
  5635.  
  5636.  
  5637.  
  5638. 7.12.2.1 The clock function
  5639.  
  5640.  
  5641.  
  5642.  
  5643. Synopsis
  5644.  
  5645.  
  5646. #include <time.h>
  5647. clock_t clock(void);
  5648.  
  5649.  
  5650. Description
  5651.  
  5652.  
  5653. The clock function determines the processor time used.
  5654.  
  5655.  
  5656. Returns
  5657.  
  5658.  
  5659. The clock function returns the implementation's best approximation to the
  5660. processor time used by the program since the beginning of an
  5661. implementation-defined era related only to the program invocation. To
  5662. determine the time in seconds, the value returned by the clock function should
  5663. be divided by the value of the macro CLOCKS_PER_SEC. If the processor time
  5664. used is not available or its value cannot be represented, the function returns
  5665. the value (clock_t) -1.138
  5666.  
  5667.  
  5668. 7.12.2.2 The difftime function
  5669.  
  5670.  
  5671.  
  5672.  
  5673. Synopsis
  5674.  
  5675.  
  5676. #include <time.h>
  5677. double difftime(time_t time1, time_t time0);
  5678.  
  5679.  
  5680.  
  5681. Description
  5682.  
  5683.  
  5684. The difftime function computes the difference between two calendar times:
  5685. time1 - time0.
  5686.  
  5687.  
  5688. Returns
  5689.  
  5690.  
  5691. The difftime function returns the difference expressed in seconds as a double.
  5692. .....
  5693.  
  5694.  
  5695. 7.12.2.4 The time function
  5696.  
  5697.  
  5698.  
  5699.  
  5700. Synopsis
  5701.  
  5702.  
  5703. #include <time.h>
  5704. time_t time(time_t *timer);
  5705.  
  5706.  
  5707. Description
  5708.  
  5709.  
  5710. The time function determines the current calendar time. The encoding of the
  5711. value is unspecified.
  5712.  
  5713.  
  5714. Returns
  5715.  
  5716.  
  5717. The time function returns the implementation's best approximation to the
  5718. current calendar time. The value (time_t)-1 is returned if the calendar time
  5719. is not available. If timer is not a null pointer, the return value is also
  5720. assigned to the object it points to.
  5721. Footnotes
  5722. 137. The range [0, 61] for tm_see allows for as many as two leap seconds.
  5723. 138. In order to measure the time spent in a program, the clock function
  5724. should be called at the start of the program and its return value subtracted
  5725. from the value returned by subsequent calls.
  5726.  
  5727.  
  5728. Using the Basic Time Functions
  5729.  
  5730.  
  5731. The functions declared in <time.h> determine elapsed processor time and
  5732. calendar time. They also convert among different data representations. You can
  5733. represent a time as:
  5734. type clock_t for elapsed processor time, as returned by the primitive function
  5735. clock
  5736. type time_t for calendar time, as returned by the primitive function time or
  5737. the function mktime
  5738. type double for calendar time in seconds, as returned by the function difftime
  5739. type struct tm for calendar time broken down into separate components, as
  5740. returned by the functions gmtime and localtime
  5741. a text string for calendar time, as returned by the functions asctime, ctime,
  5742. and strftime
  5743. You have a rich assortment of choices. The hard part is often identifying just
  5744. which data represention, and which functions, you want to use for a particular
  5745. application. For this installment, I ignore functions that produce a struct tm
  5746. or a text string.
  5747. Here is a brief description of the individual types and macros defined in
  5748. <time.h>. It is followed by brief notes on how to use the basic time functions
  5749. declared in <time.h>.
  5750. NULL--See "The Header <stddef. h>," CUJ December 1991.
  5751. CLOCKS_PER_SEC--The expression clock() / CLOCKS_PER_SEC measures elapsed
  5752. processor time in seconds. The macro can have any arithmetic type, either
  5753. integer or floating point. Type cast it to double to ensure that you can
  5754. represent fractions of a second as well as a wide range of values.
  5755. clock_t--This is the arithmetic type returned by clock, described below. It
  5756. represents elapsed processor time. It can have any integer or floating-point
  5757. type, which need not be the same type as the macro CLOCKS_PER_SECOND, above.
  5758. size_t--See "The Header <stddef.h >," CUJ December 1991.
  5759. time_t--This is the arithmetic type returned by time, described below. Several
  5760. other functions declared in <time.h> also manipulate values of this type. It
  5761. represents calendar times that span years, presumably to the nearest second
  5762. (although not necessarily). Don't attempt to perform arithmetic on a value of
  5763. this type.
  5764. tm--A structure of type struct tm represents a "broken-down time." Several
  5765. functions declared in <time.h> manipulate values of this type. You can access
  5766. certain members of struct tm. Its definition looks something like:
  5767. struct tm {
  5768.  
  5769. int tm_sec;
  5770. /* seconds after the minute (from 0) */
  5771. int tm_min;
  5772. /* minutes after the hour (from 0) */
  5773. int tm_hour;
  5774. /* hour of the day (from 0) */
  5775. int tm_mday;
  5776. /* day of the month (from 1) */
  5777. int tm_mon;
  5778. /* month of the year (from 0) */
  5779. int tm_year;
  5780. /* years since 1900 (from 0) */
  5781. int tm_wday;
  5782. /* days since Sunday (from 0) */
  5783. int tm_yday;
  5784. /* day of the year (from 0) */
  5785. int tm_isdst;
  5786. /* DST flag */
  5787. The members may occur in a different order, and other members may also be
  5788. present. The DST flag is greater than zero if Daylight Savings Time (DST) is
  5789. in effect, zero if it is not in effect, and less than zero if its state is
  5790. unknown. The unknown state encourages the functions that read this structure
  5791. to determine for themselves whether DST is in effect.
  5792. clock--This function measures elapsed processor time instead of calendar time.
  5793. It returns -1 if that is not possible. Otherwise, each call should return a
  5794. value equal to or greater than an earlier call during the same program
  5795. execution. It is the best measure you can get of the time your program
  5796. actually consumes. See the macro CLOCKS_PER_SEC, above.
  5797. difftime--The only safe way compute the difference between two times t1 and t0
  5798. is by calling difftime(t1, t0). The result, measured in seconds, is positive
  5799. if t1 is a later time than t0.
  5800. time--This function determines the current calendar time. It returns -1 if
  5801. that is not possible. Otherwise, each call should return a value at the same
  5802. time or later than an earlier call during the same program execution. It is
  5803. the best estimate you can get of the current time and date.
  5804.  
  5805.  
  5806. Implementing the Basic Time Functions
  5807.  
  5808.  
  5809. The functions declared in <time.h> are quite diverse. Many wrestle with the
  5810. bizarre irregularities involved in measuring and expressing times and dates.
  5811. Be prepared for an assortment of coding techniques (at least in future
  5812. installments).
  5813. Listing 1 shows the file time.h. As usual, it inherits from the internal
  5814. header <yvals.h> definitions that are repeated in several standard headers. I
  5815. discuss the implementation of both the macro NULL and the type definition
  5816. size_t in "The Header <stddef. h>," CUJ December 1991.
  5817. <yvals.h> also defines two macros that describe properties of the primitive
  5818. functions clock and time:
  5819. The macro _CPS specifies the value of the macro CLOCKS_PER_SECOND.
  5820. The macro_TBIAS gives the difference, in seconds, between values returned by
  5821. time and the time measured from 1 January 1900. (This macro name does not
  5822. appear in <time.h>.)
  5823. The values of these macros depend strongly on how you implement clock and
  5824. time. This implementation represents elapsed processor time as an unsigned int
  5825. (type clock_t). It represents calendar time as an unsigned long (type time_t)
  5826. that counts UTC seconds since the start of 1 January 1900. That represents
  5827. dates from 1900 until at least 2036. You have to adjust whatever the system
  5828. supplies to match these conventions.
  5829. The macro _TBIAS is a kludge. Normally, you want to set it to zero. The
  5830. version of time you supply should deliver calendar times with the appropriate
  5831. starting point. UNIX, however, measures time in seconds since 1 January 1970.
  5832. Many implementations of C offer a function time that matches this convention.
  5833. If you find it convenient to use such a time function directly, then <yvals.h>
  5834. should contain the definition:
  5835. #define _TBIAS
  5836. ((70 * 365LU + 17) * 86400
  5837. That counts the 70 years, including 17 leap days, that elapsed between the two
  5838. starting points. In several places, the functions declared in <time.h> adjust
  5839. a value of type time_t by adding or subtracting _TBIAS.
  5840. Listing 2 shows the file time.c. It defines the function time for a UNIX
  5841. system. As usual, I assume the existence of a C-callable function with a
  5842. reserved name that peforms the UNIX system service. For this version of time,
  5843. the header <yvals.h> can define the macro _TBIAS to be zero.
  5844. UNIX also provides an exact replacement for the function clock. So do many
  5845. implementations of C modeled after UNIX. Thus, you may not have to do any
  5846. additional work. Just define the macro _CPS appropriately. For a PC-compatible
  5847. computer, for example, the value is approximately 18.2.
  5848. Listing 3 shows the file clock.c. It defines a version of clock you can use if
  5849. the operating system doesn't provide a separate measure of elapsed processor
  5850. time. The function simply returns a truncated version of the calendar time. In
  5851. this case, the header <yvals.h> defines the macro _CPS to be 1.
  5852. Listing 4 shows the file difftime.c. It is careful to correct the biases of
  5853. both times before comparing them. It is also careful to develop a signed
  5854. difference between two unsigned integer quantities. Note how the function
  5855. negates the difference t1 - t0 only after converting it to double.
  5856. The remaining functions all include the internal header "xtime.h". Listing 5
  5857. shows the file xtime.h. It includes the standard header <time.h> and the
  5858. internal header "xtinfo.h". That internal header defines the type _Tinfo. It
  5859. also declares the data object _Times, defined in the file asctime.c. _Times
  5860. specifies locale-specific information on the category LC_TIME. Listing 6 shows
  5861. the file xtinfo.h.
  5862. The header "xtime.h" defines the macro WDAY that specifies the weekday for 1
  5863. January 1900 (Monday). It defines the type Dstrule that specifies the
  5864. components of an encoded rule for determining Daylight Savings Time. And it
  5865. declares the various internal functions that implement this version of
  5866. <time.h>.
  5867.  
  5868.  
  5869. Conclusion
  5870.  
  5871.  
  5872. The basic time functions I have shown so far meet a number of needs. You can
  5873. measure execution times, obtain time and date stamps, and compare those
  5874. stamps. That's more time support than many programming languages offer. For
  5875. Standard C, however, it's only the beginning.
  5876. References
  5877. W.M. O'Neil. 1975. Time and the Calendars. Sydney, N.S.W.: Sydney University
  5878. Press. Calendars are notoriously idiosyncratic. This book tells you more than
  5879. you probably want to know about the history of measuring calendar time. It
  5880. also explains why days and dates are named and determined the way they are
  5881. today.
  5882. This article is excerpted in part from P.J. Plauger, The Standard C Library,
  5883. (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
  5884.  
  5885. Listing 1 time. h
  5886. /* time.h standard header */
  5887. #ifndef _TIME
  5888. #define _TIME
  5889. #ifndef _YVALS
  5890. #include <yvals.h>
  5891.  
  5892. #endif
  5893. /* macros */
  5894. #define NULL _NULL
  5895. #define CLOCKS_PER_SEC _CPS
  5896. /* type definitions */
  5897. #ifndef _SIZET
  5898. #define _SIZET
  5899. typedef _Sizet size_t;
  5900. #endif
  5901. typedef unsigned int clock_t;
  5902. typedef unsigned long time_t;
  5903. struct tm {
  5904. int tm_sec;
  5905. int tm_min;
  5906. int tm_hour;
  5907. int tm_mday;
  5908. int tm_mon;
  5909. int tm_year;
  5910. int tm_wday;
  5911. int tm_yday;
  5912. int tm_isdst;
  5913. };
  5914. /* declarations */
  5915. char *asctime(const struct tm *);
  5916. clock_t clock(void);
  5917. char *ctime(const time_t *);
  5918. double difftime(time_t, time_t);
  5919. struct tm *gmtime(const time_t *);
  5920. struct tm *localtime(const time_t *);
  5921. time_t mktime(struct tm *);
  5922. size_t strftime(char *, size_t, const char *,
  5923. const struct tm *);
  5924. time_t time(time_t *);
  5925. #endif
  5926. /* End of File */
  5927.  
  5928.  
  5929. Listing 2 time.c
  5930. /* time function -- UNIX version */
  5931. #include <time.h>
  5932.  
  5933. /* UNIX system call */
  5934. time_t_Time(time_t *);
  5935.  
  5936. time_t (time)(time_t *rod)
  5937. { /* return calendar time */
  5938. time_t t = _Time(NULL) + (70*365LU+17)*86400;
  5939.  
  5940. if (tod)
  5941. *tod = t;
  5942. return (t);
  5943. }
  5944. /* End of File */
  5945.  
  5946.  
  5947. Listing 3 clock.c
  5948. /* clock function -- simple version */
  5949. #include <time.h>
  5950.  
  5951.  
  5952. clock_t (clock)(void)
  5953. { /* return CPU time */
  5954. return ( (clock_t) time(NULL) );
  5955. }
  5956. /* End of File */
  5957.  
  5958.  
  5959. Listing 4 difftime.c
  5960. /* difftime function */
  5961. #include <time.h>
  5962.  
  5963. double (difftime)(time_t t1, time_t t0)
  5964. { /* compute difference in times */
  5965. t0 -= _TBIAS, t1 -= _TBIAS;
  5966. return (t0 <= t1 ? (double)(t1 - t0)
  5967. : -(double) (t0 - t1));
  5968. }
  5969. /* End of File */
  5970.  
  5971.  
  5972. Listing 5 xtime.h
  5973. /* xtime.h internal header */
  5974. #include <time.h>
  5975. #include "xtinfo.h"
  5976. /* macros */
  5977. #define WDAY 1 /* to get day of week right */
  5978. /* type definitions */
  5979. typedef struct {
  5980. unsigned char wday, hour, day, mon, year;
  5981. } Dstrule;
  5982. /* internal declarations */
  5983. int _Daysto(int, int);
  5984. const char *_Gentime(const struct tm *, _Tinfo *,
  5985. const char *, int *, char *);
  5986. Dstrule *_Getdst(const char *);
  5987. const char *_Gettime(const char *, int, int *);
  5988. int _Isdst(const struct tm *);
  5989. const char *_Getzone(void);
  5990. size_t _Strftime(char *, size_t, const char *,
  5991. const struct tm *, _Tinfo *);
  5992. struct tm *_Ttotm(struct tm *, time_t, int);
  5993. time_t _Tzoff(void);
  5994. /* End of File */
  5995.  
  5996.  
  5997. Listing 6 xtinfo.h
  5998. /* xtinfo.h internal header*/
  5999.  
  6000. /* type definitions */
  6001. typedef struct {
  6002. const char *_Ampm;
  6003. const char *_Days;
  6004. const char *_Formats;
  6005. const char *_Isdst;
  6006. const char *_Months;
  6007. const char *_Tzone;
  6008. } _Tinfo;
  6009. /* declarations */
  6010. extern _Tinfo _Times;
  6011.  
  6012. /* End of File */
  6013.  
  6014.  
  6015.  
  6016.  
  6017.  
  6018.  
  6019.  
  6020.  
  6021.  
  6022.  
  6023.  
  6024.  
  6025.  
  6026.  
  6027.  
  6028.  
  6029.  
  6030.  
  6031.  
  6032.  
  6033.  
  6034.  
  6035.  
  6036.  
  6037.  
  6038.  
  6039.  
  6040.  
  6041.  
  6042.  
  6043.  
  6044.  
  6045.  
  6046.  
  6047.  
  6048.  
  6049.  
  6050.  
  6051.  
  6052.  
  6053.  
  6054.  
  6055.  
  6056.  
  6057.  
  6058.  
  6059.  
  6060.  
  6061.  
  6062.  
  6063.  
  6064.  
  6065.  
  6066.  
  6067.  
  6068.  
  6069.  
  6070.  
  6071.  
  6072.  
  6073.  
  6074.  
  6075. Stepping Up To C++
  6076.  
  6077.  
  6078. The Function operator[]
  6079.  
  6080.  
  6081.  
  6082.  
  6083. Dan Saks
  6084.  
  6085.  
  6086. Dan Saks is the owner of Saks & Associates, which offers consulting and
  6087. training in C and C++. He is secretary of the ANSI and ISO C++ standards
  6088. committees, and also contributing editor for Windows/DOS Developer's Journal.
  6089. Dan recently finished his first book, C++ Programming Guidelines, written with
  6090. Thomas Plum. You can write to him at 393 Leander Dr., Springfield, OH 45504,
  6091. or dsaks@wittenberg.edu (Internet), or call (513)324-3601.
  6092.  
  6093.  
  6094. Dynamic array classes let you create arrays for which you can delay choosing
  6095. the number of elements until runtime. In my last column, I described reasons
  6096. why you might want to use such things in your programs (see "Dynamic Arrays",
  6097. CUJ, November, 1992). I also presented a fairly typical implementation for
  6098. class float_array, a simple dynamic array of float.
  6099. Each float_array has two data members: array, a pointer to the first element
  6100. of an actual array of float allocated from the free store, and len, the number
  6101. of elements in that array. Listing 1 shows the class definition. It has two
  6102. constructors, a destructor, an assignment operator, a subscript operator, and
  6103. a function that returns the number of elements.
  6104.  
  6105.  
  6106. const Member Functions Revisited
  6107.  
  6108.  
  6109. Last time I also introduced const member functions. For the most part, you can
  6110. think of a const member function as one that promises not to change the object
  6111. addressed by its this pointer. Although you can call a const member function
  6112. for both const and non-const objects, you can call a non-const member function
  6113. only for non-const objects.
  6114. You declare a member function as const by placing the keyword const after the
  6115. parameter list in the member function declaration. Class float_array in
  6116. Listing 1 has two const member functions: operator[] and length. You must also
  6117. use the keyword const when you write the function definitions, as shown in
  6118. Listing 2.
  6119. If you accept my belief that you should declare objects const whenever
  6120. feasible, then clearly the length function should be const. length simply
  6121. returns the number of elements in the array without changing anything.
  6122. It also appears that operator[] should be const. If you don't declare it
  6123. const, you can't subscript a const float_array. The operation appears to be
  6124. perfectly safe. After all, it doesn't change anything. It simply returns a
  6125. reference to the selected element. But, when I concluded last time, I said
  6126. that the operator[] in Listing 2 is an accident waiting to happen. It lets you
  6127. write an expression, without a cast, that inadvertently modifies a const
  6128. float_array. For example, given
  6129. const float_array fb(fa);
  6130. you can write
  6131. fb[0] = 0;
  6132. which overwrites the value of the fb's first element. This is valid C++. The
  6133. assignment statement compiles and executes without error. But that assignment
  6134. is probably an accident, and you might find the results surprising.
  6135.  
  6136.  
  6137. constness is Shallow
  6138.  
  6139.  
  6140. To see how the accident can happen, let's take a closer look at what it means
  6141. for a member function to be const. In a non-const, non-static member function
  6142. for class X, the type of this is X *const. That is, the function cannot change
  6143. the value of its this pointer, but it can modify each non-const data member of
  6144. the object addressed by this.
  6145. In a const member function of class X, this has type const X *const. Not only
  6146. is the this pointer const, but so is the object it points to. Each data member
  6147. of a const object is in turn const. This applies recursively to any data
  6148. members that are objects of a class type. Thus, a const member function cannot
  6149. change the value of any data members in the object addressed by this. (A const
  6150. member function must be non-static because the const qualifier modifies the
  6151. object addressed by the this pointer. A static member function has no this
  6152. pointer, so it cannot be const.)
  6153. Aye, there's the rub: constness is shallow. A pointer member in a const object
  6154. is a const pointer, but it is not pointer to const unless explicitly declared
  6155. so. In other words, inside operator[], the object's array data member (that
  6156. is, this>array) has type float_array *const, not the desired const float_array
  6157. *. You cannot change the pointer, but you can change the values of the array
  6158. elements. This violates the intuitive notion that the elements of a const
  6159. array are themselves const.
  6160. operator[] in Listing 2 is a const member function. Inside the function, the
  6161. array member is const, but the element array[i] is not. The function returns a
  6162. float &, not a const float &. Thus, even though operator[] doesn't change any
  6163. values, it returns a modifiable reference to an array element that the caller
  6164. might accidentally use to change a value in a const float_array.
  6165. From the programming language's standpoint, this is not an error. The behavior
  6166. of the float_array class, even with this questionable operator[], is
  6167. well-defined. For a non-class type T, writing over a const T object (or a part
  6168. thereof) yields undefined behavior because you might be writing into ROM
  6169. (read-only memory). But C++ only places a const class object in ROM if that
  6170. class has neither constructors nor a destructor. Since class float_array has
  6171. constructors and a destructor, the translator will never put const float_array
  6172. objects in ROM. In this case, const float_array objects behave at runtime just
  6173. like non-const objects. However, if class float_array had neither constructors
  6174. nor a destructor, the translator might place const float_array objects in ROM,
  6175. and calling operator[] for a const float_array would have undefined behavior.
  6176. C++ has no notation for specifying "deep" constness. That is, in a class X
  6177. with pointer member T *p, you can't make the p member of a const X object have
  6178. type const T *. However, you can design X to simulate "deep" constness.
  6179.  
  6180.  
  6181. Overloading with const
  6182.  
  6183.  
  6184. Let's consider correcting the problem in operator[] (Listing 2) by changing
  6185. the function's return type from float & to const float & in both Listing 1 and
  6186. Listing 2. Then when you declare
  6187. const float_array fb(fa);
  6188. and write
  6189. fb[0] = 0;
  6190. you will get a compilation error because the expression fb[0] refers to a
  6191. const float object, which you cannot modify. Unfortunately, now every
  6192. float_array subscripting expression is const, so you cannot do that assignment
  6193. even if fb were non-const.
  6194. What you need is two different subscripting operators: one for const
  6195. float_arrays, and another for non-const float_arrays. C++ lets you overload a
  6196. member function as const and non-const. That is, you can declare two member
  6197. functions in a given class with identical names and parameter lists, provided
  6198. one of the functions is const and the other is not. In this case, you overload
  6199. operator[] as a const function returning a const float &, and as a non-const
  6200. function returning a (plain) float &. Listing 3 shows the float_array class
  6201. definition with both declarations for operator[]. Listing 4 shows the
  6202. corresponding function definitions.
  6203. When the compiler encounters a float_array subscripting expression like fb[i],
  6204. it chooses an operator[] by the constness of fb. It does not matter whether
  6205. fb[i] appears as an lvalue or rvalue. For example, given
  6206. float_array fa(fx);
  6207. const float_array fb(fy);
  6208. then
  6209. fa[i] = fb[i];
  6210. calls the non-const operator[] on the left of the assignment, and calls the
  6211. const operator[] on the right. Everything works fine. However, the assignment
  6212.  
  6213. fb[i] = fa[i];
  6214. calls the const operator[] on the left side and calls the non-const operator[]
  6215. on the right. But the const operator[] returns a non-modifiable lvalue (a
  6216. const float &), so it cannot appear as the destination of the assignment, and
  6217. you get a compile-time error.
  6218. Furthermore, the compiler selects the operator[] based on the constness of the
  6219. float_array expression that refers to the object, and not the constness of the
  6220. object itself. For example,
  6221. const float_array *p = &fa;
  6222. binds p, a pointer to const, to a non-const object, fa. Then (*p) [i] calls
  6223. the const operator[] because *p has type const float_array, even though p
  6224. points to a float_array that's declared non-const.
  6225.  
  6226.  
  6227. Arrays That Grow
  6228.  
  6229.  
  6230. When I first introduced operator[] in my previous column, I suggested that the
  6231. function could handle a subscript out-of-bounds in a number of ways. Thus far,
  6232. my float_array class has treated subscripts out of bounds as an error caught
  6233. by assert. Now let's consider rewriting operator[] to automatically extend the
  6234. array so that the subscript is in bounds.
  6235. For example, if you declare
  6236. float_array fa(4);
  6237. then fa has four elements whose indices are 0 through 3. Now if you write
  6238. fa[6] = 1.0;
  6239. then operator[] adds three more elements to fa at indices 4, 5, and 6, so the
  6240. assignment operator has some place to store the 1.0.
  6241. Rewrite only the non-const operator[]. Extending an array changes the array
  6242. (by making it bigger), and a const member function should not change the
  6243. object addressed by its this pointer. Subscripting out of range in a const
  6244. float_array should remain a runtime error.
  6245. Listing 5 shows my revised implementation for the non-const operator[]. If the
  6246. function determines that the subscript is out of bounds, it allocates a new,
  6247. larger array. It copies the values of the elements of the old array into their
  6248. corresponding positions in the new array, and fills the remaining elements in
  6249. the new array with zeros. Then it deletes the old array, thus returning that
  6250. storage to the free store.
  6251. Listing 6 shows a trivial test program that uses operator[] to extend an
  6252. array. Figure 1 shows output from that program. The second for loop in main
  6253. (Listing 6) overwrites the values in fb and then adds new elements. The output
  6254. expression just before the return statement forces a subscripting error on the
  6255. const float_array fc. I added that expression just to reinforce that
  6256. operator[] is different for const and non-const float_arrays.
  6257. The subscripting error makes the assertion in operator[] fail. assert displays
  6258. an error message and aborts the program. Unlike the exit function, abort does
  6259. not flush and close the standard output stream, cout. Thus I changed the body
  6260. of the display function from
  6261. cout << s << " = " << fa << '\n';
  6262. (as it was in my previous column), to
  6263. cout << s << " = " << fa << endl;
  6264. endl is a special kind of function called a manipulator. It's defined in
  6265. iostream.h. The expression
  6266. cout << endl;
  6267. writes a newline to cout and flushes cout's output buffer to standard output.
  6268. Typically, you won't see the difference between \n and endl unless you
  6269. redirect standard output to a file.
  6270. You might recognize an opportunity to use realloc in the non-const operator[].
  6271. realloc is a Standard C library function that changes the size of a
  6272. dynamically-allocated block of memory. However, realloc only works with
  6273. storage previously allocated by calloc, malloc, or realloc. Many C++
  6274. environments implement the new operator in terms of malloc, so realloc might
  6275. work with new. But there is no guarantee that it always will.
  6276. For float_arrays, you could simply abandon the new operator and use malloc and
  6277. realloc. The code for class float_array will still be portable. However, using
  6278. malloc and realloc only works for arrays of objects without constructors and
  6279. destructors. The new operator applies a default constructor to each element of
  6280. the array it allocates. malloc just grabs storage.
  6281. On occasion, members of the C++ standards committee have suggested adding a
  6282. renew operator that works with new as realloc works with malloc. So far,
  6283. nothing has come of it.
  6284.  
  6285.  
  6286. Dangling Pointers and References
  6287.  
  6288.  
  6289. A dangling pointer or reference is one that remains bound to an address after
  6290. the program deallocates the object at that address. The potential occurrence
  6291. of a dangling pointer or reference is an ever present fact of life for C++
  6292. programmers, as it is for C programmers. operator[] offers yet another
  6293. opportunity for the unwary to get bitten.
  6294. Consider, for example, the following code fragment:
  6295. float_array fa, fb;
  6296. ...
  6297. float *p = &fa[i];
  6298. ...
  6299. fa = fb;
  6300. ...
  6301. *p = 1.0;
  6302. p's declaration binds it to the address of an element of fa. (Remember, fa[i]
  6303. is a reference to an element, and so &fa[i] is the address of the referenced
  6304. element.) If fb and fa have a different number of elements, the assignment fa
  6305. = fb deletes the old elements of fa with new ones at a different address. This
  6306. leaves p dangling.
  6307. An even subtler problem occurs in the test program in Listing 7 when using the
  6308. (non-const) operator[] that may extend a float_array (Listing 5). If you run
  6309. the program with 4 as the input value for size, it initializes fa with four
  6310. elements { 0 1 2 3 }. The subsequent statements
  6311. size_t n = fa.length();
  6312. fa[n] = fa[n - 1] = 123;
  6313. look like they extend fa by one element and copy 123 into the last two
  6314. elements, so that
  6315. fa = { 0 1 2 123 123}
  6316. I tested the assignment against three MS-DOS C++ compilers (Borland, Comeau,
  6317. and Zortech), and they all produced that result.
  6318. The next two statements
  6319. n = fa.length();
  6320. fa[n - 1] = fa[n] = 456;
  6321. look like they should extend fa by one more element, and store 456 in the last
  6322. two elements, so that
  6323. fa = { 0 1 2 123 456 456 }
  6324. Interestingly, only Borland and Zortech produced that result; Comeau produced
  6325. fa = { 0 1 2 123 123 456 }
  6326. Apparently, Comeau evaluates
  6327. fa[n - 1] = fa[n] = 456;
  6328.  
  6329. more or less as follows:
  6330. float &t1 = fa[n - 1];
  6331. float &t2 = fa[n];
  6332. t2 = 456;
  6333. t1 = t2;
  6334. But the call to fa[n] in the second step extends fa and moves its elements to
  6335. a different place in the free store. Unfortunately, t1 has already been bound
  6336. to the original location of fa[n - 1], and the call to fa[n] leaves t1
  6337. dangling. Thus, the final step (t1 = t2), copies 456 from fa[n] into the
  6338. original location of fa[n - 1], not the current one.
  6339. The Comeau compiler is generating correct code. C++ compilers, like C
  6340. compilers, have considerable freedom about the order in which they evaluate
  6341. operands between sequence points, and the Comeau compiler is merely exercising
  6342. its freedom. The problem is in the test program, because the expression
  6343. depends on a particular evaluation order for success.
  6344.  
  6345.  
  6346. Subscripting Objects
  6347.  
  6348.  
  6349. operator[] opens the door for dangling pointers and references because it
  6350. returns a reference to part of a float_array's internal representation. That
  6351. representation tends to move around, but the references don't rebind to the
  6352. new locations. You can eliminate many opportunities for dangling references by
  6353. rewriting operator[] to return a subscripting object instead of a reference.
  6354. A reference to a float can only keep track of a single float element in a
  6355. float_array. The reference has no way of knowing if the value it's referring
  6356. to has moved. A subscripting object keeps track of both the float_array and
  6357. the value of the subscript, so it always subscripts using the current location
  6358. of the array.
  6359. Listing 8 is the header for class float_array using a subscripting class,
  6360. fa_index. fa_index has two data member: fa, a pointer to a float_array, and
  6361. ix, a subscript for an element of that float_array. fa_index has a
  6362. two-argument constructor that fills in values for members fa and ix.
  6363. Notice that the constructor is private. Thus, clients (users) of fa_index
  6364. can't create fa_index objects. How, then, does the program ever create an
  6365. fa_index? fa_index declares float_array as a friend class. Hence, every member
  6366. function of class float_array is a friend of class fa_index. float_array
  6367. member functions has access rights so they can construct fa_index objects.
  6368. Listing 9 presents the implementations for both the fa_index and float_array
  6369. member functions. (The listing includes all the float_array member functions
  6370. for completeness.) The only float_array member function that changes to use
  6371. fa_index is the non-const operator[]. Rather than return a float & as before,
  6372. it returns an fa_index object.
  6373. The fa_index class has a conversion operator that converts an fa_index to a
  6374. float. When you use an expression like fa[i] as an rvalue in a context
  6375. expecting an arithmetic expression, the program implicitly calls operator
  6376. float to convert the fa_index object into the value of element that the object
  6377. designates. That is, the compiler translates the assignment in
  6378. float x;
  6379. ...
  6380. x = fa[i];
  6381. into something like
  6382. fa_index t1(fa[i]);
  6383. x = float(t1);
  6384. The program constructs a temporary fa_index t1 to hold the result of fa[i].
  6385. Then it applies fa_index::operator float to t1 to get the value of the array
  6386. element.
  6387. When fa[i] appears to the left of an assignment, the program calls
  6388. fa_index::operator=(float) to write through the fa_index object and into the
  6389. float_array element it designates. For example, an assignment like
  6390. fa[i] = 1;
  6391. translates into
  6392. fa_index t1(fa[i]);
  6393. t1.operator=(float(1));
  6394. Using this new implementation for float_array with the test program in Listing
  6395. 7 eliminates the dangling reference. It enables all three compilers to produce
  6396. the same (intended!) result.
  6397. But you pay for subscripting objects. They slow down every subscripting
  6398. operation on a non-const float_array. Furthermore, you lose some functionality
  6399. that you have to reconstruct by writing more member functions for fa_index.
  6400. For example, fa_index::operator= only lets you use an expression like fa[i] as
  6401. the left side of an = operator. It doesn't cover any other uses as an lvalue,
  6402. such as fa[i] *= 10 or ++fa[i]. If you want these operators, you must define
  6403. them for class fa_index.
  6404.  
  6405.  
  6406. Other Uses for operator[]
  6407.  
  6408.  
  6409. Some C++ applications use operator[] as a file positioning operator, replacing
  6410. explicit calls to a function like lseek. For example,
  6411. File f("myfile");
  6412. ...
  6413. f[10] = 'X';
  6414. writes an X into the tenth character position of File f. Coplien (1992, 49-52)
  6415. outlines a simple implementation for this scheme using subscripting objects.
  6416. Cargill (1992, 91-112) provides an excellent description of what goes wrong
  6417. with poorly-designed subscripting objects. His example uses a file object
  6418. similar to Coplien's. Cargill also provides some thoughtful discussion on
  6419. whether using operator[] this way is just too clever. The pitfalls may
  6420. out-weigh the notational advantages.
  6421. References
  6422. Cargill, Tom. 1992. C++ Programming Style. Reading, MA: Addison-Wesley.
  6423. Coplien, James O. 1992. Advanced C++ Programming Styles and Idioms. Reading,
  6424. MA: Addison-Wesley.
  6425. Figure 1 Output from the test program in Listing 6
  6426. size? 3
  6427. fa = { 0 1 2 }
  6428. fb = { 0 1 2 }
  6429. fb = { 0 1 2 }
  6430. fb = { 0 1 2 }
  6431. fb = { 0 1 4 }
  6432. fb = { 0 1 4 9 }
  6433. fb = { 0 1 4 9 16 }
  6434. fb = { 0 1 4 9 16 25 }
  6435. fc = { 0 1 2 }
  6436. Assertion failed: i < len, file fa4.cpp, line 59
  6437. Abnormal program termination
  6438.  
  6439.  
  6440. Listing 1 Class definition of float_array
  6441. // fa2.h - a dynamic array of float
  6442.  
  6443. #include <iostream.h>
  6444.  
  6445. class float_array
  6446. {
  6447. public:
  6448. float_array(size_t n = 0);
  6449. float_array(const float_array &fa);
  6450. ~float_array();
  6451. float_array &operator=(const float_array &fa);
  6452. float &operator[](size_t i) const;
  6453. inline size_t length() const;
  6454. private:
  6455. float *array;
  6456. size_t len;
  6457. };
  6458.  
  6459. ostream &operator<<(ostream &os, float_array &fa);
  6460.  
  6461. inline size_t float_array::length() const
  6462. {
  6463. return len;
  6464. }
  6465.  
  6466. /* End of File */
  6467.  
  6468.  
  6469. Listing 2 float_array::operator[] as a const member function
  6470. // fa2.cpp - a dynamic array of float
  6471.  
  6472. #include "fa2.h"
  6473. #include <assert.h>
  6474.  
  6475. // other float_array member function definitions ...
  6476.  
  6477. float &float_array::operator[](size_t i) const
  6478. {
  6479. assert(i < len);
  6480. return array[i];
  6481. }
  6482. /* End of File */
  6483.  
  6484.  
  6485. Listing 3 Class definition of float_array with operator[] overloaded as const
  6486. and non-const
  6487. // fa3.h - a dynamic array of float with operator[]
  6488. // as both const and non-const member functions
  6489.  
  6490. #include <iostream.h>
  6491.  
  6492. class float_array
  6493. {
  6494. public:
  6495. float_array(size_t n = 0);
  6496. float_array(const float_array &fa);
  6497. ~float_array();
  6498. float_array &operator=(const float_array &fa);
  6499.  
  6500. const float &operator[](size_t i) const;
  6501. float &operator[](size_t i);
  6502. inline size_t length() const;
  6503. private:
  6504. float *array;
  6505. size_t len;
  6506. };
  6507.  
  6508. ostream &operator<<
  6509. (ostream &os, const float_array &fa);
  6510.  
  6511. inline size_t float_array::length() const
  6512. {
  6513. return len;
  6514. }
  6515. /* End of File */
  6516.  
  6517.  
  6518. Listing 4 float_array function definitions
  6519. // fa3.cpp - a dynamic array of float with operator[]
  6520. // as both const and non-const member functions
  6521.  
  6522. #include "fa3.h"
  6523. #include <assert.h>
  6524.  
  6525. // other float_array member function definitions ...
  6526.  
  6527. const float &float_array::operator[](size_t i) const
  6528. {
  6529. assert(i < len);
  6530. return array[i];
  6531. }
  6532.  
  6533. float &float_array::operator[](size_t i )
  6534. {
  6535. assert(i < len);
  6536. return array[i];
  6537. }
  6538.  
  6539. /* End of File */
  6540.  
  6541.  
  6542. Listing 5 An implementation of float_array that extends the size of the array
  6543. when it detects subscript out-of-bounds
  6544. // float_array::operator[] that extends the array on
  6545. // subscript out of bounds
  6546. float &float_array::operator[](size_t i)
  6547. {
  6548. if (i >= len)
  6549. {
  6550. float *new_array = new float[i + 1];
  6551. assert(new_array != 0);
  6552. size_t j;
  6553. for (j = 0; j < len; ++j)
  6554. new_array [j] = array[j];
  6555. for (; j < i + 1; ++j)
  6556. new_array[i] = 0;
  6557. delete [] array;
  6558. array = new_array;
  6559. len = i + 1;
  6560.  
  6561. }
  6562. return array[i];
  6563. }
  6564. /* End of File */
  6565.  
  6566.  
  6567. Listing 6 A trivial float_array test program
  6568. #include <iostream.h>
  6569. #include "fa4.h"
  6570.  
  6571. void display(const char *s, const float_array &fa)
  6572. {
  6573. cout << s <<" = "<< fa << endl;
  6574. }
  6575.  
  6576. int main()
  6577. {
  6578. size_t i, size;
  6579. cout << "size? ";
  6580. cin >> size;
  6581.  
  6582. float_array fa(size);
  6583. for (i = 0; i < fa.length(); ++i)
  6584. fa[i] = i;
  6585. display("fa", fa);
  6586. float_array fb = fa;
  6587. display("fb", fb);
  6588. for (i = 0; i < 2 * size; ++i)
  6589. {
  6590. fb[i] = i * i;
  6591. display("fb", fb);
  6592. }
  6593. const float_array fc = fa;
  6594. display("fc", fc);
  6595. cout << "fc[size] = " << fc[size] << '\n';
  6596. return 0;
  6597. }
  6598. /* End of File */
  6599.  
  6600.  
  6601. Listing 7 Another float_array test program
  6602. #include <iostream.h>
  6603. #include "fa4.h"
  6604.  
  6605. void display(const char *s, const float_array &fa)
  6606. {
  6607. cout << s << " = " << fa << endl;
  6608. }
  6609.  
  6610. int main()
  6611. {
  6612. size_t i, size;
  6613. cout << "size? ";
  6614. cin >> size;
  6615.  
  6616. float_array fa(size);
  6617. for (i = 0; i < fa.length(); ++i)
  6618. fa[i] = i;
  6619. display("fa", fa);
  6620.  
  6621. i = fa.length();
  6622. fa[i] = fa[i - 1] = 123;
  6623. display("fa", fa);
  6624. i = fa.length();
  6625. fa[i - 1] = fa[i] = 456;
  6626. display("fa", fa);
  6627. return 0;
  6628. }
  6629. /* End of File */
  6630.  
  6631.  
  6632. Listing 8 Class definition for float_array using a subscripting class
  6633. // fa5.h - a dynamic array of float using a
  6634. // subscripting object
  6635.  
  6636. #include <iostream.h>
  6637.  
  6638. class fa_index
  6639. {
  6640. friend class float_array;
  6641. public:
  6642. fa_index &operator=(float f);
  6643. operator float();
  6644. private:
  6645. fa_index(float_array *f, size_t i);
  6646. float_array *fa;
  6647. size_t ix;
  6648. };
  6649.  
  6650. class float_array
  6651. {
  6652. friend class fa_index;
  6653. public:
  6654. float_array(size_t n = 0);
  6655. float_array(const float_array &fa);
  6656. ~float_array();
  6657. float_array &operator=(const float_array &fa);
  6658. float operator[](size_t i) const;
  6659. fa_index operator[](size_t i);
  6660. inline size_t length() const;
  6661. private:
  6662. void extend(size_t i);
  6663. float *array;
  6664. size_t len;
  6665. };
  6666.  
  6667. ostream &operator<<(ostream &os, const float_array &fa);
  6668.  
  6669. inline size_t float_array::length() const
  6670. {
  6671. return len;
  6672. }
  6673. /* End of File */
  6674.  
  6675.  
  6676. Listing 9 Implementation of float_array using a subscripting object
  6677. // fa5.cpp - a dynamic array of float using a
  6678. // subscripting object
  6679.  
  6680.  
  6681. #include "fa5.h"
  6682. #include <assert.h>
  6683.  
  6684. fa_index::fa_index(float_array *f, size_t i)
  6685. : fa(f), ix(i) { }
  6686.  
  6687. fa_index &fa_index:: operator=(float f)
  6688. {
  6689. if (ix >= fa->len)
  6690. fa->extend(ix);
  6691. fa->array[ix] = f;
  6692. return *this;
  6693. }
  6694.  
  6695. fa_index::operator float()
  6696. {
  6697. if (ix >= fa->len)
  6698. fa->extend(ix);
  6699. return fa->array[ix];
  6700. }
  6701.  
  6702. fa_index float_array::operator[](size_t i)
  6703. {
  6704. return fa_index(this, i);
  6705. }
  6706.  
  6707. float float_array::operator[](size_t i) const
  6708. {
  6709. assert(i < len);
  6710. return array[i];
  6711. }
  6712.  
  6713. ffloat_array::float_array(size_t n)
  6714. {
  6715. if ((len = n) == 0)
  6716. array = 0;
  6717. else
  6718. {
  6719. array = new float[n];
  6720. assert(array != 0);
  6721. for (int i = 0; i < n; ++i)
  6722. array[i] = 0;
  6723. }
  6724. }
  6725.  
  6726. float_array::float_array (const float_array &fa)
  6727. {
  6728. if ((len = fa.len) == 0)
  6729. array = 0;
  6730. else
  6731. {
  6732. array = new float[len];
  6733. assert(array != 0);
  6734. for (int i = 0; i < len; ++i)
  6735. array[i] = fa.array[i];
  6736. }
  6737. }
  6738.  
  6739. float_array::~float_array()
  6740.  
  6741. {
  6742. delete [] array;
  6743. }
  6744.  
  6745. float_array &float_array::operator=(const float_array &fa)
  6746. {
  6747. if (this != &fa)
  6748. return *this;
  6749. if (len != fa.len)
  6750. {
  6751. delete [] array;
  6752. if ((len = fa.len) == 0)
  6753. array = 0;
  6754. else
  6755. {
  6756. array = new float[len];
  6757. assert(array != 0);
  6758. }
  6759. }
  6760. for (size_t i = 0; i < len; ++i)
  6761. array[i] = fa.array[i];
  6762. return *this;
  6763. }
  6764.  
  6765. ostream &operator<<(ostream &os, const float_array &fa)
  6766. {
  6767. os << '{';
  6768. size_t i;
  6769. for (i = 0; i < fa.length(); ++i)
  6770. os << ' ' << fa[i];
  6771. return os << " }";
  6772. }
  6773.  
  6774. void float_array::extend(size_t i)
  6775. {
  6776. float *new_array = new float[i + 1];
  6777. assert(new_array ! = 0);
  6778. size_t j;
  6779. for (j = 0; j < len; ++j)
  6780. new_array [j] = array [j];
  6781. for (; j < i + 1; ++j)
  6782. new_array[i] = 0;
  6783. delete [] array;
  6784. array = new_array;
  6785. len = i + 1;
  6786. }
  6787.  
  6788. /* End of File */
  6789.  
  6790.  
  6791.  
  6792.  
  6793.  
  6794.  
  6795.  
  6796.  
  6797.  
  6798.  
  6799.  
  6800.  
  6801.  
  6802.  
  6803.  
  6804.  
  6805.  
  6806.  
  6807.  
  6808.  
  6809.  
  6810.  
  6811.  
  6812.  
  6813.  
  6814.  
  6815.  
  6816.  
  6817.  
  6818.  
  6819.  
  6820.  
  6821.  
  6822.  
  6823.  
  6824.  
  6825.  
  6826.  
  6827.  
  6828.  
  6829.  
  6830.  
  6831.  
  6832.  
  6833.  
  6834.  
  6835.  
  6836.  
  6837.  
  6838.  
  6839.  
  6840.  
  6841.  
  6842.  
  6843.  
  6844.  
  6845.  
  6846.  
  6847.  
  6848.  
  6849.  
  6850.  
  6851.  
  6852.  
  6853.  
  6854.  
  6855.  
  6856.  
  6857.  
  6858.  
  6859.  
  6860.  
  6861.  
  6862.  
  6863.  
  6864. Questions & Answers
  6865.  
  6866.  
  6867. Linked Lists, Strings, and Internationalization
  6868.  
  6869.  
  6870.  
  6871.  
  6872. Ken Pugh
  6873.  
  6874.  
  6875. Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++
  6876. language courses for corporations. He is the author of C Language for
  6877. Programmers and All On C, and was a member on the ANSI C committee. He also
  6878. does custom C programming for communications, graphics, image databases, and
  6879. hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707.
  6880. You may fax questions for Ken to (919) 489-5239. Ken also receives email at
  6881. kpugh@dukemvs.ac.duke.edu (Internet).
  6882.  
  6883.  
  6884. Q
  6885. Just got the August CUJ, so please excuse the response delay. To sort a linked
  6886. list quickly, count the elements, allocate an array to hold them all, then
  6887. sort the array. Pick off the elements to rebuild the list. Or else use a radix
  6888. ("Postman's") sort, if possible. Or use a merge sort.
  6889. Any sort not relying on element exchange will serve to sort a linked list,
  6890. though it will of course need minor modifications. Quicksort becomes a merge
  6891. sort, except that partitioning is based on a pivot rather than being blind
  6892. (half the elements go in each sublist).
  6893. Next, in your "array of strings" answer you have the statement:
  6894. printf(get_string(1));
  6895. SHAME! If there are any % characters in the string, nasty things may happen.
  6896. Use:
  6897. puts(get_string(i)); /*adds \n */
  6898. or
  6899. printf ("%s", get_string(1));
  6900. Keep up the (otherwise) good work.
  6901. David Chapman
  6902. San Jose, CA
  6903. A
  6904. Thanks for your reply. As I suggested in my original article, if one needs to
  6905. sort a linked list, it might be just as simple to keep an array of pointers to
  6906. the data items and sort that instead.
  6907. Thanks also for pointing out the potential problem with the format specifier.
  6908. Most likely, the percent character (%) will not appear in text followed by any
  6909. character other than a space. This will be an invalid conversion specifier
  6910. whose behavior is undefined by the ANSI standard. This is another "little
  6911. gotcha" of the type described in the answer to the next question.
  6912. On some machine/compiler combinations, the percent sign will show up on the
  6913. output. On others, it will disappear. Of course if % is followed by a
  6914. legitimate conversion character, garbage will appear on the output or the
  6915. program may bomb.
  6916. If the specifier was for a float number (e.g. %f), then it is possible that
  6917. the value picked up will be an invalid floating-point number and a
  6918. floating-point exception will occur.
  6919. In practice, the return value of get_string will not usually be passed to
  6920. printf. Instead, it will be a parameter to some display function that will
  6921. display it with an attribute and/or in some window.
  6922. For those further interested in making programs portable, you might want to
  6923. investigate the internationalization functions that are specified under the
  6924. Xopen Portability Guidelines (XPG). These functions are available under OSF
  6925. UNIX, and probably are available on several other systems. Xopen is a
  6926. consortium of European computer vendors.
  6927. A brief description of the design of the functions may help you develop your
  6928. own if you are on a system that does not support them.
  6929. The functions use a message catalog file that is specific to a single
  6930. language. The catalog file contains all strings that need to be translated to
  6931. another language. The files for each language are kept in a separate
  6932. directory. The strings in the catalog are arranged in sets. Each set has an
  6933. identifying number and each string within the set has a unique number within
  6934. that set.
  6935. The initial function, catopen, opens the appropriate file corresponding to the
  6936. filename passed to it. The directory for the file (i.e. the language of the
  6937. file) is derived using the environment variables NLSPATH and LANG. catopen
  6938. returns a catalog identifier used by the remainder of the functions.
  6939. The function corresponding to the get_string example is catgets. Its four
  6940. parameters are the catalog identifier, a set number within the catalog, a
  6941. string number with the set, and a default string. It returns a pointer to the
  6942. corresponding string. If the routine is unable to access the particular
  6943. string, it returns a pointer to the default string.
  6944. Listing 1 and Listing 2 give an example of the use of the file. The example
  6945. code produces:
  6946. String to output
  6947. on standard output if the file is accessible and
  6948. I can't find it
  6949. if the file is not accessible.
  6950. The message catalog is actually composed of two files. The first file is an
  6951. ASCII file. A compilation program uses the ASCII file as input and creates the
  6952. actual file read by the program. This makes the lookup quicker as either a
  6953. single or double seek is all that is required. The compiled file can be read
  6954. in as a whole if it is not too large.
  6955. The catalog can use symbolic names, rather than absolute numbers. The
  6956. compilation program generates a header file for use by the calling functions.
  6957. Obviously this is a far superior method, as it is easier to match the strings
  6958. with their intended usage. Listing 3 and Listing 4 give an example of the use
  6959. of symbols.
  6960.  
  6961.  
  6962. Variable Argument Lists, Portability, and Gotchas
  6963.  
  6964.  
  6965. Q
  6966. Regarding the answer you gave to reader Willi Fleischer of Moerfelden, Germany
  6967. (CUJ, September 1992, page 114) on the use of variable parameter list, I have
  6968. tried to solve a problem of the same kind: to pass the variable arguments from
  6969. a function to a function (which can be nested). The solution I found, though
  6970. it needs to be verified on different platforms (I tested on SGI and IBM UNIX
  6971. workstations), is to call the function that needs the variable argument list
  6972. and to prevent it from adding any element to the calling function's stack.
  6973. For example, in Listing 5, Msg will call PrintString in order to convert the
  6974. printf argument list into a static string. As you can see, PrintString is the
  6975. first function/statement to be executed in Msg after declaring line. If not,
  6976. you will fail (try if(1) line = PrintString;).
  6977. I understand this is somewhat restrictive, but it can be handled easily in
  6978. many cases. Its advantages are: making PrintString syntax simple (no need for
  6979. _direct and _va_list functions of the same kind), and making the functions
  6980. that call it not have to deal with va_start and so on.
  6981. Joseph Z. Wang
  6982. White Plains, NY
  6983. A
  6984. I consider this in the land of serendipitous trouble. A particular set of
  6985. operations works on a couple of computers, but is not really part of ANSI
  6986. standard. The dead giveaway is the fact that it does not work if another
  6987. statement is inserted into the code.
  6988.  
  6989. PrintString is only being passed one parameter (fmt). The way the parameters
  6990. are passed on this particular set of machines, the va_start macro
  6991. coincidentally sets up args to point to the first parameter after fmt. If the
  6992. stack pointer is altered by the calling function (such as you suggested), the
  6993. coincidence disappears.
  6994. Unfortunately (or fortunately, depending on how you look at it), the only
  6995. portable ways to pass the variable parameter list is by the method suggested
  6996. or one that follows along a similar line.
  6997. I teach a class in how to make C portable. It turns out there are a few
  6998. "gotchas" that can appear while using the language. Code appears portable, as
  6999. it works on a few machines, but in reality, it is not. Your code is one
  7000. example. The previous question regarding the % in a string passed to printf is
  7001. another example of non-portable behavior.
  7002. The C standard lists nine pages of unspecified, undefined, or
  7003. implementation-defined behavior. Many of the items concern situations that a
  7004. good C programmer would avoid anyway. Some others are of the "gotcha" variety.
  7005. For example, the order of two equal members in an array sorted by qsort is
  7006. unspecified. Some implementations may leave equal members in the original
  7007. order. Going to an implementation that does not may produce some unexpected
  7008. surprises if your programming was counting on the original behavior.
  7009. Many of the portability problems can be pointed out by a good lint. For those
  7010. new to C, this program is a source-code analysis program that comes with many
  7011. UNIX systems and is available separately on PC systems. I have used PC-Lint by
  7012. Gimpel and found it very satisfactory, in many cases superior to UNIX lints I
  7013. have tried.
  7014. For example, the following loop may set each element of the array to the value
  7015. of its index:
  7016. int array[10];
  7017. int i;
  7018. for (i = 0; i < 10;)
  7019. {
  7020. array[i] = i++;
  7021. }
  7022. Then again, it may not. The value of i may be incremented before or after it
  7023. is used as the index value for array. The order of evaluation of the
  7024. assignment operator is not specified by the language. Nor is the time
  7025. specified that the increment will take place except that it is after the value
  7026. of i on the right hand side is obtained and before the semicolon is reached.
  7027. Any of the lint programs should point out this error of dependence on order of
  7028. evaluation.
  7029. My suggestion for writing portable code is to keep it simple. Another author
  7030. suggests that "well written code is portable." In a paper by A. Dolenc, A.
  7031. Lemmke, D. Keepl and G.V. Reilly, "Notes on Writing Portable Programs in C,"
  7032. the authors state that the expression f(p=&a, p=&b, p=&c) is ambiguous. The
  7033. standard permits it to be interpreted as f(&a, &b, &c) or f(&c, &c, &c) [or
  7034. many others--pjp]. My immediate response is that one should have coded it in
  7035. whichever of the forms was really required, followed by the appropriate
  7036. assignment for p.
  7037. By the way, the paper includes some excellent suggestions for making your
  7038. program portable. They would permit your program to be backfitted to old K&R
  7039. compilers, if you need to do that.
  7040. I will admit that I do not code in an absolute portable fashion. It takes a
  7041. lot of hard work. The one facet which I ignore entirely is the limitation of
  7042. six characters of significance in an external name. Although the ANSI C
  7043. library must adhere to this limitation, I find it difficult to obey it while
  7044. writing a maintainable program. If I come across that odd system that requires
  7045. it, I will simply make up some header files that look like:
  7046. #define my_reasonably_named_function(a,b,c)\
  7047. IHE345(a,b,c)
  7048. #define another_useful_function(d,e) IHE346(d,e)
  7049.  
  7050.  
  7051. Truncating a File in Place and Portability
  7052.  
  7053.  
  7054. Is it possible to truncate a file in place using ANSI C? Currently, I am
  7055. copying the truncated data to a temporary file, removing the original file,
  7056. and renaming the temporary file to the original file's name. Is there a more
  7057. sophisticated method? BSD4.3 UNIX provides the (non-portable) system function
  7058. calls truncate and ftruncate. Do these implement the method described above?
  7059. Jeff Dragovich
  7060. Urbana, Illinois
  7061. A
  7062. The answer is as you have just described it. The BSD functions can play games
  7063. with the inode (which contains the length of the file and data
  7064. block-allocation information). Obviously you can run out of space when you
  7065. truncate the file, as you will be making a copy of it.
  7066. Since our theme is portability this month, let me suggest how to make
  7067. functions like these portable. You can create a compatibility library of
  7068. functions that you commonly use. For example, instead of using ftruncate, you
  7069. might make up functions as:
  7070. int file_truncate_by_fd(int file_descriptor,\
  7071. unsigned int size);
  7072. int file_truncate_by_name(char *file_name,\
  7073. unsigned int size);
  7074. These functions would be kept in a package of file functions whose
  7075. implementation might vary from machine to machine. A header file, say file.h
  7076. would be used for the function prototypes and any other #defines or typedefs
  7077. required by your functions.
  7078. In your BSD version of the library, these functions could call truncate and
  7079. ftruncate. Alternatively, you could #define them in the header file to be the
  7080. appropriate call, so the functions will be called inline, without an extra
  7081. function call layer. For the MS-DOS version, the functions would perform the
  7082. operations you listed.
  7083. For all versions, the functions should return an error indication if the
  7084. truncation was unable to be performed. Note that it is logically impossible
  7085. for the BSD version to fail to truncate, while the MS-DOS version may fail if
  7086. there is insufficient disk space.
  7087.  
  7088.  
  7089. scanf and ANSI
  7090.  
  7091.  
  7092. I have some comments about the sscanf question in the September 1992 CUJ. I
  7093. realize this is probably the thousandth letter you've received about this, but
  7094. I've run into similar sscanf problems in the past and have a vested interest
  7095. in these questions.
  7096. According to my reading of Harbison and Steele, (I don't have a copy of the
  7097. ANSI Standard document), sscanf should return 4 on string[0], and 5 on
  7098. string[1]. I suspect the return of 4 may be the result of an error in the code
  7099. as published (should string[0] b e garbageR A 3 0 4 ?), but, as you pointed
  7100. out, string[1] scanning using the [^PR] conversion operator will stop
  7101. immediately upon seeing the R; no conversion will take place. Since the
  7102. assignment-suppression operator is involved, this won't affect the rest of the
  7103. conversion. No pointers would be consumed in either case. Conversion will then
  7104. pick up at the first %c, and continue with the other char and the three ints,
  7105. all of which are present in string[1]. Am I correct in this, or am I missing
  7106. yet one more subtlety in sscanf formatting?
  7107. By the way, sscanf behaves this way in Borland C++ version 3.1; previous
  7108. versions of Turbo C/C++ would, believe it or not, cause a null-pointer error
  7109. with this usage of sscanf.
  7110. Thanks for your attention in this.
  7111. Dan Rempel
  7112. Victoria, B.C. Canada
  7113. A
  7114. Thank you for your sharp eye in catching the code error. I'll repeat the
  7115. relevant code here, so our readers won't have to go back. The strings (with
  7116. your correction) are:
  7117. char *string[] = {
  7118. "some garbageR A 3 0 4",
  7119. "R A 3 0 4"
  7120. };
  7121. The sscanf with its format looked like:
  7122. const char *format = "%*[^PR]%c %c %d %d %d";
  7123. ret = sscanf (string[i], format, &l, &q, &k, &m, &n);
  7124. When scanning string[0], the * suppresses the assignment of any set of
  7125. characters which match the [^PR] specifier. That specifier matches any
  7126. nonempty sequence of characters that are not P or R. This directive eats up
  7127. the characters "some garbage" in string[0]. The value R is then assigned to 1,
  7128. and the remaining assignments take place normally.
  7129. For string[1], there is no sequence of characters that match the initial [^PR]
  7130. specifier. The directive fails with a matching failure (inappropriate input,
  7131. according to the standard). When that directive fails, sscanf returns with a
  7132. zero value as no assignments have taken place.
  7133.  
  7134. As I read the C Standard, if a match to a directive fails, even if there is
  7135. assignment suppression, the function terminates at that point. The order of
  7136. the operations as specified by the C Standard is to match the specifier and
  7137. convert it to the corresponding type. It then checks for assignment
  7138. suppression.
  7139.  
  7140. Listing 1 Message catalog file example
  7141. .../english/example.cat
  7142.  
  7143. Set ID = 10
  7144. ...
  7145. Set ID = 24
  7146. 1 "String to output"
  7147. 2 "Another string to output"
  7148.  
  7149. /* End of File */
  7150.  
  7151.  
  7152. Listing 2 Accessing the message catalog
  7153. message_file = catopen("example.cat", 0);
  7154. printf("%s", catgets(message_file, 24, 1,
  7155. "I can't find it");
  7156. catclose(message_file);
  7157.  
  7158. /* End of File */
  7159.  
  7160.  
  7161. Listing 3 Message catalog file example with symbols
  7162. .../english/example.cat
  7163.  
  7164. Set ID = ERROR_MESSAGES
  7165. ...
  7166. Set ID = SOME_STRINGS
  7167. FIRST_STRING "String to output"
  7168. SECOND_STRING "Another string to output"
  7169.  
  7170. /* End of File */
  7171.  
  7172.  
  7173. Listing 4 Accessing the message catalog symbolically
  7174. #include "example.h"
  7175. ...
  7176. message_file = catopen("example.cat", 0);
  7177. printf("%s", catgets(message_file, SOME_STRINGS,
  7178. FIRST_STRING, "I can't find it");
  7179. catclose(message_file);
  7180.  
  7181. /* End of File */
  7182.  
  7183.  
  7184. Listing 5 Msg will call PrintString in order to convert the printf argument
  7185. list into a static string.
  7186. #include <stdarg.h>
  7187.  
  7188. char *PrintString(const char *fmt, ...)
  7189. /* Convert printf() arguments into a static string. */
  7190. {
  7191. va_list args;
  7192. static char line[999];
  7193.  
  7194. va_start(args, fmt);
  7195. vsprintf(line, fmt, args);
  7196. va_end(args);
  7197. return line;
  7198.  
  7199.  
  7200. }
  7201.  
  7202. void Msg(const char *fmt, ...)
  7203. /* Send message to stdout user. */
  7204. {
  7205. char *line;
  7206.  
  7207. line = PrintString(fmt);
  7208. print("%s\n", line);
  7209. ...
  7210. }
  7211.  
  7212. /* End of File */
  7213.  
  7214.  
  7215.  
  7216.  
  7217.  
  7218.  
  7219.  
  7220.  
  7221.  
  7222.  
  7223.  
  7224.  
  7225.  
  7226.  
  7227.  
  7228.  
  7229.  
  7230.  
  7231.  
  7232.  
  7233.  
  7234.  
  7235.  
  7236.  
  7237.  
  7238.  
  7239.  
  7240.  
  7241.  
  7242.  
  7243.  
  7244.  
  7245.  
  7246.  
  7247.  
  7248.  
  7249.  
  7250.  
  7251.  
  7252.  
  7253.  
  7254.  
  7255.  
  7256.  
  7257.  
  7258.  
  7259.  
  7260.  
  7261.  
  7262. On The Networks: Special Issue: USENET Network News Update
  7263.  
  7264.  
  7265. Sydney S. Weinstein
  7266.  
  7267.  
  7268. Sydney S. Weinstein, CDP, CCP is a consultant, columnist, author, and
  7269. president of Datacomp Systems, Inc., a consulting and contract programming
  7270. firm specializing in databases, data presentation and windowing, transaction
  7271. processing, networking, testing and test suites, and device management for
  7272. UNIX and MS-DOS. He can be contacted care of Datacomp Systems, Inc., 3837
  7273. Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail on the
  7274. Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
  7275. Internet addressing).
  7276.  
  7277.  
  7278. This is my third anniversary in writing this column, and its time to update
  7279. the prior January columns. In January 1990 (CUJ Vol. 8, No. 1), I wrote about
  7280. the internet, the Internet, USENET, and Network News. In January 1991 (CUJ
  7281. Vol. 9, No. 2), I wrote about obtaining USENET Network News. In January 1992
  7282. (CUJ Vol. 10, No. 2), I wrote about obtaining the sources referred to in this
  7283. column. This year, I am going to present a quick review and update to all
  7284. three past columns. I do not intend to provide an in-depth review, so
  7285. newcomers to USENET might want to try and seek out back issues of those
  7286. articles.
  7287. "On The Networks" covers articles posted to several of the source groups on
  7288. USENET Network News: comp. sources.games, comp.sources.misc, comp.sources.
  7289. reviewed, comp.sources.unix, comp.sources.x, and alt.sources. Each of these
  7290. groups is like a section of a large electronic magazine called USENET Network
  7291. News. I call it a magazine, and not a bulletin board partly because unlike a
  7292. bulletin board, where each reader accesses a central machine to read the
  7293. messages, USENET Network News is delivered on a subscription basis to each
  7294. computer, and the articles are read locally.
  7295. I try and limit my coverage to the highlights of the articles posted to each
  7296. group. In addition, I also generally restrict my coverage to
  7297. freely-distributable C and C++ sources. Freely-distributable means that you
  7298. can freely (and for free) make copies of the software, use it as you see fit,
  7299. and give it away as you desire. It does not mean the software is in the public
  7300. domain. Most of the software is copyrighted. This means you cannot pretend you
  7301. wrote it, or include code from it in a product you are selling. However, the
  7302. authors have allowed you to use and distribute it for free. If you make
  7303. changes, most authors do not let you call the changed version by the name of
  7304. the original. This is to avoid confusion as to what is and is not part of the
  7305. product and to reduce the authors' support headaches.
  7306.  
  7307.  
  7308. Internet Update
  7309.  
  7310.  
  7311. First an update. USENET, often times referred to as "the net" is a loose
  7312. collection of cooperating computers. In the long-distant past, all USENET
  7313. computers ran UNIX, but now they could be running anything from MS/DOS to
  7314. VAX/VMS. It also used to be that to be considered a computer on USENET, you
  7315. communicated via the UNIX to UNIX Communications Protocol (UUCP) to another
  7316. computer. That also has changed. Now there are many protocols in use. So, to
  7317. be considered a member of the USENET network, one must be able to exchange
  7318. Electronic Mail with other computers on the USENET network.
  7319. If your computer is part of a network, and that network has a gateway to any
  7320. other network, you are considered on an internet, short for inter-network.
  7321. Note this internet is spelled with a lower case i. The Internet (capital I) is
  7322. the computer network whose addressing (name space) is loosely managed by the
  7323. DDN Network Information Center in Chantilly, VA. It is a collection of
  7324. networks that grew out of the Defense Department's ARPANET (Advanced Projects
  7325. Research Agency Network).
  7326. The Internet now includes not only the MILNET and NSFnet member networks
  7327. (direct descendents of the ARPANET), but also several commercial TCP/IP
  7328. networks which are members of the Commercial Internet Exchange (CIX). The
  7329. Internet is mostly a set of networks with leased lines permanently connecting
  7330. them to regional and backbone networks. Some outlying networks use gateways
  7331. with dial-up links to reach their regional network, but most links are 56K,
  7332. 1.544M, or 45M bits/second leased lines (that's megabits per second).
  7333. Whereas only mail and news is usually available over the USENET via UUCP, the
  7334. Internet runs the TCP/IP protocol and supports news (NNTP, Network News
  7335. Transfer Protocol), mail (SMTP, Simple Mail Transfer Protocol), Wide Area
  7336. Information Service (WAIS), remote access lookup (archie), remote logins to
  7337. any computer on the network provided you have an account there (telnet), and
  7338. remote file transfer (FTP, file transfer protocol) in addition to many other
  7339. services. All of these services coexist and work in real time.
  7340.  
  7341.  
  7342. Network News Update
  7343.  
  7344.  
  7345. The sources mentioned in this column are released via several groups
  7346. distributed as part of USENET Network News. USENET Network News is both a
  7347. method of distributing information between a very large group of computers,
  7348. and a somewhat organized collection of that information into news groups that
  7349. are distributed via the news software.
  7350. First, the software. There are now two current Network News transport software
  7351. suites: the older, C News, and a newcomer, INN.
  7352. The current version of the traditional Network News transport software is
  7353. named C News, not because it is written in C but because it follows A News
  7354. (previously called News) and B News as the third rewrite of the transport
  7355. software. It supports transfer of the news articles (the individual messages)
  7356. between every member of the USENET network. The C News software recently had a
  7357. performance-enhancement release, and a "cleanup" release is expected shortly.
  7358. C News is best suited for smaller sites that mostly have UUCP feeds, and feed
  7359. a limited set of neighbors.
  7360. New for the major backbone sites, especially those with Internet connections,
  7361. is Internet Network News (INN) from Rich Salz. INN is largely responsible in
  7362. cutting down the time it takes for an article to be propagated throughout the
  7363. backbone networks. Where C news uses batching to save articles for
  7364. distribution in bulk, INN uses an immediate transfer to its NNTP (TCP/IP-based
  7365. network news protocol) neighbors. Thus an article now reaches most of the
  7366. backbone and regional network sites in only 1-5 minutes. (Just three years ago
  7367. it took close to a day). INN is relatively new, and takes advantage of the
  7368. decline in the price of RAM. It boosts performance by keeping everything it
  7369. can in memory.
  7370. Second, there is the volume. At this time last year, the average was
  7371. twenty-five megabytes per day. Now it is up to approximately forty megabytes
  7372. per day. The number of newsgroups is also growing. Currently there are over
  7373. 2,400 of them.
  7374. Lastly, there is a new distribution method, CD-ROM. Sterling Software, 1404
  7375. Fort Crook Road S., Bellevue, NE 68005, publishes a set of ISO-9660 format
  7376. CD-ROMs with almost all the groups of USENET Network News. The CD-ROMs come
  7377. out when a new one fills up, which is about every 10-14 days. They even
  7378. include a modified news reader that will access the information as stored on
  7379. the CD-ROM. Sterling can be reached at (800) 643-NEWS, or (402) 291-2108 from
  7380. outside of the US. A subscription runs about $350/year + shipping. Shipping
  7381. varies from $60/year in the US up to $200/year for some overseas areas.
  7382.  
  7383.  
  7384. How to Use This Column
  7385.  
  7386.  
  7387. This column normally reports on articles in the comp.sources and alt.sources
  7388. sub hierarchies. All of the groups I currently report on but alt.sources are
  7389. moderated. For the moderated groups, the authors of the software submit their
  7390. packages to the moderator for posting. Each group has its own rules for
  7391. acceptance. alt.sources is unmoderated and a free-for-all. Sources in that
  7392. group are posted directly by the author.
  7393. For the moderated groups, when I list a package, I provide five pieces of
  7394. information for each package. The Volume, Issue(s), archive name, the
  7395. contributor's name, and electronic mail address. The Volume and Issue are
  7396. specifically named in the listing. The archive name is in italics, and the
  7397. contributor's name is followed by an electronic mail address, in < >'s.
  7398. To locate a package via WAIS or archie, use the archive name. This is the
  7399. short one-word name in italics given with each listing. To find the file at an
  7400. archive site, use the group name (from the section of the column you are
  7401. reading--I place all listings for each group together in the column), the
  7402. volume and the archive name. Most archive sites store the postings as
  7403. group/volume/archive-name. The issue numbers tells you in how many parts the
  7404. package was split when posted. This way you can be sure to get all of the
  7405. parts.
  7406. In addition, I report on patches to prior postings. These patches also include
  7407. the volume, issue(s), archive name, the contributors name, and electronic-mail
  7408. address. Patches are stored differently by different archive sites. Some store
  7409. them along with the original volume/archive name of the master posting. Some
  7410. store them by the volume/archive name of the patch itself. The archive name
  7411. listed is the same for both the patch and the original posting.
  7412. alt.sources, being unmoderated, does not have volume and issue numbers. So I
  7413. report on the date in the Date: header of the posting and the number of parts
  7414. in which it appeared. If the posting was assigned an archive-name by the
  7415. contributor, I also report on that archive name. Archive sites for alt.sources
  7416. are harder to find, but they usually store things by the archive name.
  7417.  
  7418.  
  7419. How to Find an Archive Site
  7420.  
  7421.  
  7422. With that much information flowing into each site every day, most sites cannot
  7423. keep the information on their local disks for very long, usually only a couple
  7424. of days. So, by the time you read my articles, the sources have been deleted
  7425. from the machines in the network to make room for newer articles. So, many
  7426. sites have agreed to archive the source groups and keep these archives for
  7427. several years.
  7428. The problem then is finding out which sites archive which groups, and how to
  7429. access these archives. I again refer to the articles by Jonathan I. Kames of
  7430. the Massachusetts Institute of Technology posted to comp.sources.wanted and
  7431. news.answers. These articles, appear weekly and explain how to find sources.
  7432. As a quick review, here are the steps:
  7433. 1. Figure out in what group, Volume, and Issue(s) the posting appeared. Also
  7434. try and determine its archive name. If you know these items, it's usually easy
  7435. to find an archive site that keeps that group. Most archive sites keep their
  7436. information in a hierarchy ordered first on the group, then on the volume and
  7437. last on the archive name. These together usually make up a directory path, as
  7438. in comp.sources.unix/volume22/elm2 .3. In that directory you will find all of
  7439. the articles that made up the 2.3 release of the Elm Mail User Agent that was
  7440. posted in Volume 22 of the comp.sources.unix newsgroup. If you do not know the
  7441. archive name, but do know the volume, each volume also has an Index file that
  7442. can be retrieved and read to determining the archive name. One common publicly
  7443. accessible archive site for each of the moderated groups in this article is
  7444. UUNET.
  7445. 2. If you do not know which sites archive the groups, or even if any site is
  7446. archiving that item, but they are not archiving the entire group, consult
  7447. Archie. (CUJ August 1991, Vol. 9, No. 8). Archie is a mail-response program
  7448. that tries to keep track of sites reachable via FTP (file transfer protocol, a
  7449. TCP/IP protocol used by internet sites) that have sources available for
  7450. distribution. Even if you cannot access the archive site directly via FTP, it
  7451. is worth knowing that the archive site exists because there are other ways of
  7452. retrieving sources only available via FTP.
  7453. 3. If you know the name of the program, but do not know to what group it was
  7454. posted, try using Archie and search based on the name. Since most sites store
  7455. the archives by group and volume, the information returned will tell you what
  7456. newsgroup and volume it was posted in. Then you can retrieve the item from any
  7457. archive site for that newsgroup.
  7458. 4. If you do not even know the name, but know you are looking for source code
  7459. that does xxx, retrieve the indexes for each of the newsgroups and see if any
  7460. of the entries (usually listed as the archive name and a short description of
  7461. the function) look reasonable. If so, try those. Or, make a query to archie
  7462. based on some keywords from the function of the software and perhaps it can
  7463. find items that match.
  7464. 5. Next you have to determine what access methods the archive machine allows
  7465. to retrieve software. Most archive sites are internet-based, and support the
  7466. FTP service. If you have access to FTP on the internet, this is the easiest
  7467. and fastest way of retrieving the sources. If you don't, perhaps a local
  7468. college is a member of the internet and can assist you in using FTP to
  7469. retrieve the sources you need.
  7470. Other sites support anonymous UUCP access. This is access via the UUCP
  7471. protocol where you don't need to register in advance to call in. UUNET
  7472. Communications Services supplies this using the (900) GOT-SRCS number at a
  7473. per-minute charge for nonsubscribers. Many other archive sites provide it for
  7474. just the cost of your long-distance telephone call. If you cannot use FTP,
  7475. this is the next best method to use. Many anonymous UUCP archive sites list
  7476. what they carry in the Nixpub listing of public access UNIX sites maintained
  7477. by Phil Eschallier. The Nixpub listing is posted in comp.misc and alt.bbs
  7478. periodically. If you don't get News and need a copy, it can be retrieved via
  7479. electronic mail using the periodic-posting mail-based archive "server." This
  7480. is run by MIT on the system pit-manager.mit.edu. To use the server, send an
  7481. electronic mail message to the address mail-server@pitmanager.mit.edu with the
  7482. subject help and it will reply with instructions on how to use the server. If
  7483. you are not on USENET, sending electronic mail to sites on the internet is
  7484. also possible via the commercial mail services. CompuServe, MCI-Mail,
  7485. ATT-Mail, and Sprint-Mail all support sending messages to Internet addresses.
  7486. Contact the support personnel at the commercial mail service you use for
  7487. details on how to send messages to Internet addresses.
  7488. The last way to access the sources is via electronic mail. Several sites also
  7489. make their archives available via automated mail-response servers. Note, this
  7490. can be a very expensive way of accessing the information, and due to the load
  7491. it places on the networks, most archive servers heavily restrict the amount of
  7492. information they will send each day. The most commonly used mail-response FTP
  7493. server is ftpmail@decwrl.dec.com. Digital Equipment Corporation runs a
  7494. mail-based archive server that will retrieve sources via FTP and then mail
  7495. them to you. To find out how to use this service, send it a message with the
  7496. word help in the body.
  7497.  
  7498.  
  7499.  
  7500. CD-ROM Archives Available
  7501.  
  7502.  
  7503. Also available, are CD-ROMs with the archives of sources posted via USENET and
  7504. from other sources as well. Two of the larger publishers are Walnut Creek
  7505. CD-ROM and Prime Time Freeware.
  7506. Walnut Creek CD-ROM, 1547 Palos Verdes Mall, Suite 260, Walnut Creek, CA (800)
  7507. 786-9907 or (510) 947-5996 publishes several CD-ROMs each year. Some contain
  7508. the Simtel20 MS-DOS Archive, others the X and GNU archives, and still others
  7509. MS-Windows sources and other collections of sources and binaries. Disks run
  7510. from $25 to $60 each (varies by title) plus shipping. In addition, the offer
  7511. those hard to find CD-caddys at reasonable prices.
  7512. Prime Time Freeware, 370 Altair Way, Suite 150, Sunnyvale, CA 94086, (408)
  7513. 738-4832, FAX: (408) 738-2050, <ptf@cfcl.com>, publishes twice a year a
  7514. collection of freely distributable source code, including the complete USENET
  7515. archives. Their disks run about $60 each set plus shipping. The latest issue,
  7516. August 1992, has over 3MB of source code spread over two disks. They also
  7517. offer a standing subscription plan at a discount.
  7518. Hopefully, this special edition of my column has given you a hint as to how to
  7519. read my column and track down the sources. Note, I have been asked many times
  7520. if I can make floppies or tapes containing the software mentioned in my
  7521. column. I cannot spare the time to do this. I also have to work (and teach)
  7522. for a living. If I started doing this, I could easily spend all my time trying
  7523. to fulfill the requests and never get any of my work done.
  7524. However, what I have offered to do in the past, and am still willing to do, is
  7525. provide a list of USENET sites in your area code. Send me a self-addressed,
  7526. stamped envelope (my address is in the bio attached to this column). Those
  7527. living in major metropolitan areas, please include two stamps on your letter.
  7528. Note: I can only offer this service for US area codes. If you have net access,
  7529. but need a news neighbor, I will also reply to Electronic Mail asking for
  7530. nearby news sites.
  7531.  
  7532.  
  7533.  
  7534.  
  7535.  
  7536.  
  7537.  
  7538.  
  7539.  
  7540.  
  7541.  
  7542.  
  7543.  
  7544.  
  7545.  
  7546.  
  7547.  
  7548.  
  7549.  
  7550.  
  7551.  
  7552.  
  7553.  
  7554.  
  7555.  
  7556.  
  7557.  
  7558.  
  7559.  
  7560.  
  7561.  
  7562.  
  7563.  
  7564.  
  7565.  
  7566.  
  7567.  
  7568.  
  7569.  
  7570.  
  7571.  
  7572.  
  7573.  
  7574.  
  7575.  
  7576.  
  7577.  
  7578.  
  7579.  
  7580.  
  7581.  
  7582.  
  7583.  
  7584. Code Capsules
  7585.  
  7586.  
  7587. Time and Date Processing in C
  7588.  
  7589.  
  7590.  
  7591.  
  7592. Chuck Allison
  7593.  
  7594.  
  7595. Chuck Allison is a software architect for the Family History Department of the
  7596. Church of Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake
  7597. City. He has a B.S. and M.S. in mathematics, has been programming since 1975,
  7598. and has been teaching and developing in C since 1984. His current interest is
  7599. object-oriented technology and education. He is a member of X3J16, the ANSI
  7600. C++ Standards Committee. Chuck can be reached on the Internet at
  7601. allison@decus.org, or at (801)240-4510.
  7602.  
  7603.  
  7604. Most operating systems have some way of keeping track of the current date and
  7605. time. ANSI C makes this information available in various formats through the
  7606. library functions defined in time.h. The time function returns a value of type
  7607. time_t (usually a long), which is an implementation-dependent encoding of the
  7608. current date and time. You in turn pass this value to other functions which
  7609. decode and format it.
  7610. The program in Listing 1 uses the functions time, localtime and strftime to
  7611. print the current date and time in various formats. The localtime function
  7612. breaks the encoded time down into
  7613. struct tm
  7614. {
  7615. int tm_sec; /* (0 - 61) */
  7616. int tm_min; /* (0 - 59) */
  7617. int tm_hour; /* (0 - 23) */
  7618. int tm_mday; /* (1 - 31) */
  7619. int tm_mon; /* (0 - 11) */
  7620. int tm_year; /* past 1900 */
  7621. int tm_wday; /* (0 - 6) */
  7622. int tm_yday; /* (0 - 365) */
  7623. int tm_isdst; /* daylight
  7624. savings
  7625. flag */
  7626. };
  7627. local time overwrites a static structure each time you call it, and returns
  7628. its address (therefore only one such structure is available at a time in a
  7629. program without making an explicit copy). The ctime function returns a pointer
  7630. to a static string which contains the full time and date in a standard format
  7631. (including a terminating newline). strftime formats a string according to user
  7632. specifications (e.g., %A represents the name of the day of the week). See
  7633. Table 1 for the complete list of format descriptors.
  7634.  
  7635.  
  7636. Time/Date Arithmetic
  7637.  
  7638.  
  7639. You can do time/date arithmetic by changing the values in a tm structure. The
  7640. program in Listing 2 shows how to compute a date a given number of days in the
  7641. future, as well as the elapsed execution time in seconds. Note the optional
  7642. alternate syntax for the time function (the time_t parameter is passed by
  7643. reference instead of returned as a value). The mktime function alters a tm
  7644. structure so that the date and time values are within the proper ranges, after
  7645. which the day-of-week (tm_wday) and day-of-year (tm_yday) fields are updated
  7646. accordingly. mktime brings the date and time values in the tm structure into
  7647. their proper ranges, and updates the day of week (tm-wday) and day of year
  7648. (tm-yday) values accordingly. This occurs when a date falls outside the range
  7649. that your implementation supports. My MS-DOS-based compiler, for example,
  7650. cannot encode dates before January 1, 1970, but VAXC can process dates as
  7651. early as the mid-1800s. The asctime function returns the standard string for
  7652. the time represented in tm parameter (so ctime (&tval) is equivalent to
  7653. asctime (localtime(&tval)). The function difftime returns the difference in
  7654. seconds between two time_t encodings as a double.
  7655. If you need to process dates outside your system's range or calculate the
  7656. interval between two dates in units other than seconds, you need to roll your
  7657. own date encoding. The application in Listing 3 through Listing 5 shows a
  7658. technique for determining the number of years, months and days between two
  7659. dates, using a simple month-day-year structure. It subtracts one date from
  7660. another, much as you might have done in elementary school (i.e., it subtracts
  7661. the days first, borrowing from the month's place if necessary, and so on).
  7662. Note that leap years are taken into account. For brevity, the date_interval
  7663. function assumes that the dates are valid and that the first date entered
  7664. precedes the second. Following the lead of the functions in time.h, It returns
  7665. a pointer to a static Date structure which holds the answer.
  7666.  
  7667.  
  7668. File Time/Date Stamps
  7669.  
  7670.  
  7671. Most operating systems maintain a time/date stamp for files. At the very
  7672. least, you can find out when a file was last modified. (The common make
  7673. facility uses this information to determine if a file needs to be recompiled,
  7674. or if an application needs to be relinked). Since file systems vary across
  7675. platforms, there can be no universal function to retrieve a file's time/date
  7676. stamp, so the ANSI standard doesn't define one. However, most popular
  7677. operating systems (including MS-DOS and VAX/VMS) provide the UNIX function
  7678. stat, which returns pertinent file information, including the time last
  7679. modified expressed as a time_t. The program in Listing 6 uses stat and
  7680. difftime to see if the file time1.c is newer than (i.e., was modified more
  7681. recently than) time2.c.
  7682. If you need to update the time/date stamp of a file to the current time,
  7683. simply overwrite the first byte of a file with itself. Although the contents
  7684. haven't changed, your file system will think it has, and will update the
  7685. time/date stamp accordingly. (Know your file system! Under VAX/VMS, you get a
  7686. newer version of the file, while the older version is retained). This is
  7687. sometimes called "touching" a file. The implementation of touch in Listing 7
  7688. creates the file if it doesn't already exist. Note that the file is opened in
  7689. "binary" mode (indicated by the character b in the open mode string--I'll
  7690. discuss file processing in detail in a future capsule).
  7691. Table 1 Format descriptors for strftime
  7692. Code Sample Output
  7693. ---------------------------------------------
  7694. %a Wed
  7695. %A Wednesday
  7696. %b Oct
  7697. %B October
  7698. %c Wed Oct 07 13:24:27 1992
  7699. %d 07 (day of month [01-31])
  7700. %H 13 (hour in [00-23])
  7701. %I 01 (hour in [01-12])
  7702. %j 281 (day of year [001-366])
  7703.  
  7704. %m 10 (month [01-12])
  7705. %M 24 (minute [00-59])
  7706. %p PM
  7707. %S 27 (second [00-59] )
  7708. %U 40 (Sunday week of year [00-52])
  7709. %w 3 (day of week [0-6])
  7710. %W 40 (Monday week of year [00-52])
  7711. %x Wed Oct 7, 1992
  7712. %X 13:24:27
  7713. %y 92
  7714. %Y 1992
  7715. %Z EDT (daylight savings indicator)
  7716.  
  7717. Listing 1 time1.c -- prints the current date and time in various formats
  7718. #include <stdio.h>
  7719. #include <time.h>
  7720.  
  7721. #define BUFSIZE 128
  7722.  
  7723. main()
  7724. {
  7725. time_t tval;
  7726. struct tm *now;
  7727. char buf[BUFSIZE];
  7728. char *fancy_format =
  7729. "Or getting really fancy:\n"
  7730. "%A, %B %d, day %j of %Y.\n"
  7731. "The time is %I:%M %p.";
  7732.  
  7733. /* Get current date and time */
  7734. tval = time(NULL);
  7735. now = localtime(&tval);
  7736. printf("The current date and time:\n"
  7737. "%d/%02d/%02d %d:%02d:%02d\n\n",
  7738. now->tm_mon+1, now->tm_mday, now->tm_year,
  7739. now->tm_hour, now->tm_min, now->tm_sec);
  7740. printf("Or in default system format:\n%s\n",
  7741. ctime(&tval));
  7742. strftime(buf,sizeof buf,fancy_format,now);
  7743. puts(buf);
  7744.  
  7745. return 0;
  7746. }
  7747.  
  7748. /* Output
  7749. The current date and time:
  7750. 10/06/92 12:58:00
  7751.  
  7752. Or in default system format:
  7753. Tue Oct 06 12:58:00 1992
  7754.  
  7755. Or getting really fancy:
  7756. Tuesday, October 06, day 280 of 1992.
  7757. The time is 12:58 PM.
  7758. */
  7759.  
  7760. /* End of File */
  7761.  
  7762.  
  7763.  
  7764. Listing 2 time2.c -- shows how to compute a date a given number of days in the
  7765. future, as well as the elapsed execution time in seconds
  7766. #include <stdio.h>
  7767. #include <stdlib.h>
  7768. #include <time.h>
  7769.  
  7770. main()
  7771. {
  7772. time_t start, stop;
  7773. struct tm *now;
  7774. int ndays;
  7775.  
  7776. /* Get current date and time */
  7777. time(&start);
  7778. now = localtime(&start);
  7779.  
  7780. /* Enter an interval in days */
  7781. fputs("How many days from now? ",stderr);
  7782. if (scanf("%d",&ndays) !=1)
  7783. return EXIT_FAILURE;
  7784. now->tm_mday += ndays;
  7785. if (mktime(now) != -1)
  7786. printf("New date: %s",asctime(now));
  7787. else
  7788. puts("Sorry. Can't encode your date.");
  7789.  
  7790. /* Calculate elapsed time */
  7791. time(&stop);
  7792. printf("Elapsed program time in seconds: %f\n",
  7793. difftime(stop,start));
  7794.  
  7795. return EXIT_SUCCESS;
  7796. }
  7797.  
  7798. /* Output
  7799. How many days from now? 45
  7800. New date: Fri Nov 20 12:40:32 1992
  7801. Elapsed program time in seconds: 1.000000
  7802. */
  7803.  
  7804. /* End of File */
  7805.  
  7806.  
  7807. Listing 3 date.h -- a simple date structure
  7808. struct Date
  7809. {
  7810. int day;
  7811. int month;
  7812. int year;
  7813. };
  7814. typedef struct Date Date;
  7815.  
  7816. Date* date_interval(const Date *, const Date *);
  7817. /* End of File */
  7818.  
  7819.  
  7820. Listing 4 date_int.c -- computes time interval between two dates
  7821. /* date_int.c: Compute duration between two dates */
  7822.  
  7823. #include "date.h"
  7824.  
  7825.  
  7826. #define isleap(y) \
  7827. ((y)%4 == 0 && (y)%100 != 0 (y)%400 == 0)
  7828.  
  7829. static int Dtab [2][13] =
  7830. {
  7831. {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  7832. {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  7833. };
  7834.  
  7835. Date *date_interval(const Date *d1, const Date *d2)
  7836. {
  7837. static Date result;
  7838. int months, days, years, prev_month;
  7839.  
  7840. /* Compute the interval - assume d1 precedes d2 */
  7841. years = d2->year - d1->year;
  7842. months = d2->month - d1->month;
  7843. days = d2->day - d1->day;
  7844.  
  7845. /* Do obvious corrections (days before months!)
  7846. *
  7847. * This is a loop in case the previous month is
  7848. * February, and days < -28.
  7849. */
  7850. prev_month = d2->month - 1;
  7851. while (days < 0)
  7852. {
  7853. /* Borrow from the previous month */
  7854. if (prev_month == 0)
  7855. prev_month = 12;
  7856. --months;
  7857. days += Dtab[isleap(d2->year)][prev_month--];
  7858. }
  7859.  
  7860. if (months < 0)
  7861. {
  7862. /* Borrow from the previous year */
  7863. --years;
  7864. months += 12;
  7865. }
  7866.  
  7867. /* Prepare output */
  7868. result.month = months;
  7869. result.day = days;
  7870. result.year = years;
  7871. return &result;
  7872. }
  7873. /* End of File */
  7874.  
  7875.  
  7876. Listing 5 tdate.c -- illustrates the date_interval function
  7877. /* tdate.c: Test date_interval() */
  7878.  
  7879. #include <stdio.h>
  7880. #include <stdlib.h>
  7881. #include "date.h"
  7882.  
  7883. main()
  7884.  
  7885. {
  7886. Date d1, d2, *result;
  7887. int nargs;
  7888.  
  7889. /* Read in two dates - assume 1st precedes 2nd */
  7890. fputs("Enter a date, MM/DD/YY> ",stderr);
  7891. nargs = scanf("%d/%d/%d%*c", &d1.month,
  7892. &d1.day, &d1.year);
  7893. if (nargs != 3)
  7894. return EXIT_FAILURE;
  7895.  
  7896. fputs("Enter a later date, MM/DD/YY> ",stderr);
  7897. nargs = scanf("%d/%d/%d%*c", &d2.month,
  7898. &d2.day, &d2.year);
  7899. if (nargs != 3)
  7900. return EXIT_FAILURE;
  7901.  
  7902. /* Compute interval in years, months, and days */
  7903. result = date_interval(&d1, &d2);
  7904. printf("years: %d, months: %d, days: %d\n",
  7905. result->year, result->month, result->day);
  7906. return EXIT_SUCCESS;
  7907.  
  7908. }
  7909. /* Sample Execution:
  7910. Enter a date, MM/DD/YY> 10/1/51
  7911. Enter a later date, MM/DD/YY> 10/6/92
  7912. years: 41, months: 0, days: 5 */
  7913. /* End of File */
  7914.  
  7915.  
  7916. Listing 6 ftime.c -- determines if time1.c is newer than time2.c
  7917. /* ftime.c: Compare file time stamps */
  7918.  
  7919. #include <stdio.h>
  7920. #include <stdlib.h>
  7921. #include <sys/stat.h>
  7922. #include <time.h>
  7923.  
  7924. main()
  7925. {
  7926. struct stat fs1, fs2;
  7927.  
  7928. if (stat("time1.c",&fs1) == 0 &&
  7929. stat("time2.c",&fs2) == 0)
  7930. {
  7931. double interval =
  7932. difftime(fs2.st_mtime,fs1.st_mtime);
  7933.  
  7934. printf("time1.c %s newer than time2.c\n",
  7935. (interval < 0.0) ? "is" : "is not");
  7936. return EXIT_SUCCESS;
  7937. }
  7938. else
  7939. return EXIT_FAILURE;
  7940. }
  7941. /* Output
  7942. time1.c is not newer than time2.c */
  7943. /* End of File */
  7944.  
  7945.  
  7946.  
  7947. Listing 7 touch.c -- updates time stamp by overwriting old file or creating
  7948. the file if it doesn't exist
  7949. /* touch.c: Update a file's time stamp */
  7950.  
  7951. #include <stdio.h>
  7952.  
  7953. void touch(char *fname)
  7954. {
  7955. FILE *f = fopen(fname,"r+b");
  7956. if (f != NULL)
  7957. {
  7958. char c = getc(f);
  7959. rewind(f);
  7960. putc(c,f);
  7961. }
  7962. else
  7963. fopen(fname,"wb");
  7964.  
  7965. fclose(f);
  7966. }
  7967.  
  7968. /* End of File */
  7969.  
  7970.  
  7971.  
  7972.  
  7973.  
  7974.  
  7975.  
  7976.  
  7977.  
  7978.  
  7979.  
  7980.  
  7981.  
  7982.  
  7983.  
  7984.  
  7985.  
  7986.  
  7987.  
  7988.  
  7989.  
  7990.  
  7991.  
  7992.  
  7993.  
  7994.  
  7995.  
  7996.  
  7997.  
  7998.  
  7999.  
  8000.  
  8001.  
  8002.  
  8003.  
  8004.  
  8005.  
  8006.  
  8007.  
  8008.  
  8009. CUG New Releases
  8010.  
  8011.  
  8012. Dynamic Link Library for Text Windows
  8013.  
  8014.  
  8015.  
  8016.  
  8017. Steven Graham
  8018.  
  8019.  
  8020. Steven K. Graham has worked for Hewlett-Packard, served on the Faculty at
  8021. UMKC, and is currently a Senior Engineer with CSI.
  8022.  
  8023.  
  8024. TextView, CUG375 (one disk) is a free Dynamic Link Library (DLL) for
  8025. simplified manipulation of text windows under Microsoft Windows, written by
  8026. Alan Phillips (Lancaster, United Kingdom). Alan Phillips is a systems
  8027. programmer at the Lancaster University Computer Centre, where he writes UNIX
  8028. communications software. Alan can be contacted at a.phillips@lancaster.ac.uk.
  8029. Similar to WinDosIO, a previous CUG volume, TextView handles the details of
  8030. window operations, permitting users to call functions for writing text (such
  8031. as TVOutputText) in much the same way printf would be called in an MS-DOS
  8032. application (with the exception of an extra parameter to identify the window
  8033. where the text will be written). TextView can create multiple, independent
  8034. windows that can be resized, minimized, maximized, and scrolled horizontally
  8035. and vertically. A thoroughly-documented demonstration program illustrates the
  8036. use of TextView windows to provide tracing and debugging information during
  8037. application development. TextView requires the use of a compiler (such as
  8038. Microsoft C) which can generate Windows code. The TextView volume includes a
  8039. readable and carefully-organized 42-page manual. The TextView functions follow
  8040. the same conventions as the Windows API, and the manual uses the same layout
  8041. as the Microsoft Windows Programmer's Reference. TextView function names all
  8042. begin with TV. The functions use Pascal calling conventions and must be
  8043. declared FAR.
  8044. Function prototypes are contained in the file textview.h. Adding this file to
  8045. your source selects the right calling mode and performs necessary casts to far
  8046. pointers. The TextView import library textview.lib must be included in the
  8047. list of libraries to be linked. The stack size required for your application
  8048. may need to be increased. Some functions in the TextView import library must
  8049. be statically linked.
  8050.  
  8051.  
  8052. Dual-Mode tools
  8053.  
  8054.  
  8055. Volume 376 (four disks) adds OS/2 tools to the CUG library. Martii Ylikoski,
  8056. of Helsinki, Finland, has provided a large number of free, dual-mode tools
  8057. that support both OS/2 and MS-DOS. The tools are remarkably well packaged.
  8058. Each tool includes accompanying source, makefile, documentation, and demo
  8059. files, along with files (.bat or .cmd) to install and uninstall the tools. For
  8060. OS/2 there is also a tools2.inf file, in the standard format for OS/2 help
  8061. files. Full source code is included, generally with a single file per utility.
  8062. The makefiles (<toolname>.mak) indicate the required dependencies. A library
  8063. was used in building the tools, and is included in two forms--mtoolsp.lib for
  8064. protected mode and mtoolsr.lib for real mode. No documentation for the
  8065. libraries exists, other than the examples of function use provided in the
  8066. source code for the tools. The collection of 54 utilities provides a variety
  8067. of functions such as: find file (ff), disk usage (du), head, tail, set
  8068. priority (setprty), touch, cat, and scan (a find-like utility that searches
  8069. for files and executes commands once the files are found).
  8070.  
  8071.  
  8072. Hard-To-Find Information on Floppy Disks
  8073.  
  8074.  
  8075. Diskette manipulations are the core of CUG 377 (one disk), provided by Ian
  8076. Ashdown, P. Eng., of West Vancouver. This volume provides a wealth of
  8077. information about diskette device-service routine (DSR) functions. The
  8078. documentation addresses a variety of quirks in diskette access, and provides
  8079. considerable hard-to-find information on floppy diskettes, diskette
  8080. controllers, and the diskette DSR functions. The volume also provides
  8081. extensive example and test routines, with source code (in both C and C++
  8082. versions), for reading, writing, formatting, and verifying almost any IBM
  8083. System 34 format diskette on a PC compatible. The code includes support and
  8084. interface functions that increase the diskette DSR's reliability and provide a
  8085. consistent programming interface across PC platforms. The information was
  8086. largely determined through extensive use of an in-circuit emulator and other
  8087. debugging tools, along with careful study of various machines and various DOS
  8088. and BIOS versions. Given the variety of ROM BIOSes available, and the
  8089. necessity to derive the information by experimentation, the material in this
  8090. volume cannot cover every case, but certainly provides a thorough and careful
  8091. treatment.
  8092.  
  8093.  
  8094. C++ Matrix Operations
  8095.  
  8096.  
  8097. From Robert Davies, a consultant and researcher in mathematics and computing
  8098. from New Zealand, formerly with the New Zealand Department of Scientific and
  8099. Industrial Research (DSIR), we get NEWMAT (CUG 378, one disk), a C++ matrix
  8100. package. This volume was written for scientists and engineers who need to
  8101. manipulate a variety of matrices using standard matrix operations. It was
  8102. developed by a scientist (Robert Davies has a Ph.D. from the University of
  8103. California at Berkeley) to support real work. NEWMAT emphasizes operations
  8104. supporting statistical calculations. Functions include least squares,
  8105. linear-equation solve, and eigenvalues.
  8106. Matrix types supported include: Matrix (rectangular matrix),
  8107. UpperTriangularMatrix, LowerTriangularMatrix, DiagonalMatrix, SymmetricMatrix,
  8108. BandMatrix, UpperBandMatrix, LowerBandMatrix, SymmetricBandMatrix, and
  8109. RowVector and ColumnVector (derived from Matrix). Only one element type (float
  8110. or double) is supported. Supported matrix operations include: *, +, --,
  8111. inverse, transpose, conversion between types, submatrix, determinant, Cholesky
  8112. decompositions, Householder triangularization, singular value decomposition,
  8113. eigenvalues of a symmetric matrix, sorting, fast Fourier transform, printing,
  8114. and an interface compatible with Numerical Recipes in C. NEWMAT supports
  8115. matrices in the range of 4x4 to the machine-dependent, maximum array size
  8116. 90x90 double elements or 125x125 float elements for machines whose limit for
  8117. contiguous arrays is 64K. NEWMAT works for very small matrices, but is rather
  8118. inefficient.
  8119. NEWMAT works with Borland and Glockenspiel C++. The version current at this
  8120. writing (NEWMAT03) doesn't work with GNU C++, but a new version (NEWMAT06) is
  8121. expected (by November 1992) that will work with GNU C++. Robert Davies
  8122. suggests the following as criteria for interest in NEWMAT: first, a desire for
  8123. matrix operations expressed as operators; second, a need for various matrix
  8124. types; third, a need for only a single element type; fourth, use of matrix
  8125. sizes between 4x4 and 90x90; and fifth, tolerance for a large and complex
  8126. package. There is a fairly large file documenting the package, which broadly
  8127. addresses issues from particulars of functions and interactions with various
  8128. compilers, through design issues in building a matrix package. If you fit the
  8129. profile described, then NEWMAT may be the matrix tool you need.
  8130.  
  8131.  
  8132. Archiving and Compression Program
  8133.  
  8134.  
  8135. The final CUG volume for this month (CUG 379, one disk) is ZOO (version 2.1),
  8136. a file archiving and compression program (standard extension .zoo), written by
  8137. Rahul Dhesi, with assistance from J. Brian Walters, Paul Homchick, Bill
  8138. Davidsen, Mark Alexander, Haruhiko Okumura, Randal L. Barnes, Raymond D.
  8139. Gardner, Greg Yachuk, and Andre Van Dalen. This volume includes C source,
  8140. executable, and documentation. Zoo is used to create and maintain collections
  8141. of files in compressed form. It uses the Lempel-Ziv compression algorithm
  8142. which yields space savings from 20% to 80% depending on the file data. Zoo can
  8143. manage multiple generations of the same file and has numerous options
  8144. accompanied by lengthy descriptions in the manuals. Zoo supports a range of
  8145. hardware and operating systems, and includes makefiles with various options.
  8146. Zoo is part of the GNUish MS-DOS project, an attempt to provide a GNU-like
  8147. environment for MS-DOS, with GNU ports and MS-DOS replacements for non-ported
  8148. GNU software.
  8149.  
  8150.  
  8151.  
  8152.  
  8153.  
  8154.  
  8155.  
  8156.  
  8157.  
  8158.  
  8159.  
  8160.  
  8161.  
  8162.  
  8163.  
  8164.  
  8165.  
  8166.  
  8167. C Programming Guidelines and C ++ Programming Guidelines
  8168.  
  8169.  
  8170. Dwayne Phillips
  8171.  
  8172.  
  8173. The author works as a computer and electronics engineer with the U.S.
  8174. Department of Defense. He has a PhD in Electrical and Computer Engineering
  8175. from Louisiana State University. His interest include computer vision,
  8176. artificial intelligence, software engineering, and programming languages.
  8177.  
  8178.  
  8179. C Programming Guidelines and C++ Programming Guidelines are comprehensive
  8180. style manuals that list specific standards for all aspects of C and C++
  8181. programming. They are good books for any group of programmers trying to work
  8182. together to produce consistently high-quality, portable software. C
  8183. Programming Guidelines covers standard ANSI C while C++ Programming Guidelines
  8184. covers general C++. C++ Programming Guidelines repeats large parts of C
  8185. Programming Guidelines, but it covers C++ specifics and has excellent material
  8186. concerning migrating from C to C++.
  8187.  
  8188.  
  8189. Purpose
  8190.  
  8191.  
  8192. These books list coding standards, that is, rules governing the details of how
  8193. to write source code. C and C++ are flexible languages and programmers can
  8194. write correct, working programs using a wide variety of "styles" (including
  8195. the obfuscated style). A coding standard offers rules and guidelines that help
  8196. different developers to produce programs in the same style. The authors give
  8197. three primary reasons for using a coding standard: reliability,
  8198. readability/maintainability, and portability. If programmers follow a sound
  8199. coding standard, the result will be more reliable, readable, and portable
  8200. software. Some programmers argue that while noble, these attributes ruin
  8201. program speed and efficiency. Although a legitimate argument in some special
  8202. situations, long-term performance and monetary costs support the authors'
  8203. reasons for advocating a coding standard.
  8204.  
  8205.  
  8206. Audience
  8207.  
  8208.  
  8209. Most C and C++ programmers could use these books. For example, if you are
  8210. starting a software company, these books could aid your quality control
  8211. program. In a university research lab, these books could help standardize the
  8212. work of the beginners. A government employee wanting to specify software
  8213. standards to a contractor could simply hand these over. A programmer working
  8214. alone could use these to minimize the tendency to drift into a personal
  8215. programming style.
  8216. The publisher offers machine-readable versions of both books. This enables you
  8217. to place them online as ready references or fold them into your group's own
  8218. coding standard.
  8219.  
  8220.  
  8221. Organization
  8222.  
  8223.  
  8224. Because both books are references, the they are organized along similar lines.
  8225. The chapters contain one- to ten-page sections with the majority being two
  8226. pages long. These sections resemble man pages on UNIX systems. Each section
  8227. has subsections titled STANDARD or GUIDELINE, JUSTIFICATION, EXAMPLE,
  8228. ALTERNATIVES, and REFERENCES. The authors use STANDARDs as rules that you must
  8229. follow while GUIDELINEs are suggestions.
  8230. The subsections contain short, direct paragraphs about the subject at hand
  8231. that resemble cohesive subroutines. This style of writing is appropriate for
  8232. such reference books. These are not the types of books you cuddle up with in
  8233. front of the fire and read from cover to cover. Nevertheless, the authors do
  8234. an excellent job of tying all the sections together around the three primary
  8235. reasons for using a coding standard.
  8236. Sometimes the authors are too efficient. They often refer to other books
  8237. published by Plum Hall for details. I prefer reading the information in the
  8238. book at hand over holding my place with one finger while thumbing through a
  8239. different book.
  8240. The programming guidelines in the books fall into one of two categories. The
  8241. first category comprises syntax rules (use of tabs, spaces, lines per
  8242. function, lines per file). The second category comprises style rules
  8243. (functional cohesiveness, functions per file, program and problem structure).
  8244. This may seem to be a lengthy statement of the subject since most books on
  8245. programming devote only one chapter to programming style. The subject,
  8246. however, is more complicated than most people realize and does require one
  8247. book per language.
  8248. One criticism of the text is a shortage of code examples. The authors do give
  8249. code examples, but not in every section. Programmers sometimes do not
  8250. translate words into action well. They do, however, understand code and I wish
  8251. the these books had more source code.
  8252.  
  8253.  
  8254. Highlights
  8255.  
  8256.  
  8257. There are several sections in each deserving special attention.
  8258.  
  8259.  
  8260. C Programming Guidelines
  8261.  
  8262.  
  8263. In C Programming Guidelines, the author discusses byte order and how this
  8264. affects program portability. When you venture outside the PC world, you find
  8265. the different machines store numbers with a different byte order. This is
  8266. especially true with floating-point numbers. C Programming Guidelines suggests
  8267. converting data to a canonical form before storing to disk and converting back
  8268. immediately after reading from disk.
  8269. One section discusses the different methods of placing braces ({}) in C
  8270. programs. The author gives three different methods with many examples. This
  8271. may sound trivial, but the author claims this is a highly-charged issue with
  8272. most programmers.
  8273. Another section states how to use a local standard header for a project to
  8274. improve portability. I have been the victim of this mistake. Someone once gave
  8275. me a program that had 40 C files and each started with #include
  8276. /usr/users/dave/all.h. When we moved the program to a new environment we had
  8277. to replace each of these with #include all.h. This may sound trivial, but it
  8278. can be very time-consuming.
  8279. Plum advocates the use of code reviews to ensure correctness and portability.
  8280. Plum describes a "first-order correctness review" that produces a list of test
  8281. cases for the software. You use these for first-order correctness testing and
  8282. later for regression testing.
  8283. A final section to note covers the environment of Standard C. In this section
  8284. Plum defines a "strictly portable" C program as producing "identical behavior
  8285. in any Standard C environment." Strictly portable programs must be careful of
  8286. word sizes and number representations. Plum lists several dozen rules that
  8287. make your program strictly portable.
  8288. C Programming Guidelines concludes with a description of all the library
  8289. functions in Standard C. This will not replace a vendor's reference manual. It
  8290. is, however, a good, concise description of the standard library.
  8291.  
  8292.  
  8293. C++ Programming Guidelines
  8294.  
  8295.  
  8296. C++ Programming Guidelines repeats several dozen sections from C Programming
  8297. Guidelines. Nevertheless, C++ Programming Guidelines contains significant
  8298. additions about C++ items.
  8299. C++ Programming Guidelines dedicates considerable attention to migrating from
  8300. C to C++, a very important topic given that most of us C programmers will be
  8301. using C++ in five years if not sooner. We would all like the luxury of not
  8302. having to produce anything of value for six months while we learn C++, but
  8303. that will not happen. We must remain productive while migrating.
  8304.  
  8305. The authors describe three levels in the migration: Typesafe C, Object-Based
  8306. C++, and Full C++. Typesafe C is the common subset of Standard C and C++. You
  8307. should use it when the target platform currently supports C and will support
  8308. C++ by the time you field your system. Object-based C++ is a form of
  8309. beginner's C++ using basic classes and other C++ extensions. It is not full
  8310. C++ in that you do not use class derivation and virtual functions. The authors
  8311. describe these levels amply and as a programmer and manager of programmers I
  8312. endorse their slow but sure approach to this problem.
  8313. C++ Programming Guidelines does not dodge the issue of portable C++ programs.
  8314. The ANSI C standard makes portable C possible. C++ does not yet share such a
  8315. standard, so portable C++ programs are more difficult to realize. The authors
  8316. discuss how to work around this throughout the text and recommend using Ellis
  8317. and Stroustrup's text as a de facto standard (see reference below).
  8318. C++ Programming Guidelines has several sections that discuss code reuse and
  8319. management of reusable code. One of the selling points of full C++,
  8320. object-oriented programming is reusable code. You do not receive the full
  8321. advantages of reusable code without following certain rules in coding and
  8322. managing code. The authors do a commendable job of explaining this subject and
  8323. sprinkling tips through the entire book.
  8324. C++ Programming Guidelines has one short but vital page on the importance of
  8325. design in object-oriented, C++ programming. In C++ you create base classes
  8326. from which you derive many other classes. If you change a base class, then
  8327. this change can ripple through many derived classes and you have a quandary.
  8328. The solution is design. You must spend a much larger proportion of time and
  8329. effort when designing your base classes. You need more reviews and more
  8330. outsiders asking questions about your design. The result is shorter overall
  8331. development time and higher quality software.
  8332. The authors conclude C++ Programming Guidelines with two benchmark programs.
  8333. These programs produce a table of execution times for basic operations in C
  8334. and C++. They also provide you with a sense of execution times for basic
  8335. operations so you can estimate program performance.
  8336.  
  8337.  
  8338. Conclusion
  8339.  
  8340.  
  8341. These are working books that spell out coding standards. A coding standard
  8342. will help improve the quality and portability of your group's software by
  8343. helping you to have consistent source code in all your projects. If your group
  8344. does not have a coding standard, it needs one. If your group needs a coding
  8345. standard, start with one of these books. You may not agree with everything in
  8346. the books, but that's not necessary. Use the books as a foundation and cut,
  8347. paste, add, and delete until you have a standard that will work for you. This
  8348. will improve the quality of your software, and we all need to do that.
  8349. Reference
  8350. Ellis, Margaret A. and Bjarne Stroustrup. 1990. The Annotated C++ Reference
  8351. Manual. Reading, MA: Addison-Wesley.
  8352. Book Information
  8353. C Programming Guidelines, 2nd Edition
  8354. by Thomas Plum
  8355. Price: $30.00
  8356. ISBN: 0-911537-07-4
  8357. C++ Programming Guideliness
  8358. by Thomas Plum and Dan Saks
  8359. Price: $34.95
  8360. ISBN: 0-911537-10-4
  8361. Publisher: Plum Hall Inc.
  8362. To order: (913) 841-1631 (books),
  8363. (808) 885-6663 (machine-readable text)
  8364.  
  8365.  
  8366.  
  8367.  
  8368.  
  8369.  
  8370.  
  8371.  
  8372.  
  8373.  
  8374.  
  8375.  
  8376.  
  8377.  
  8378.  
  8379.  
  8380.  
  8381.  
  8382.  
  8383.  
  8384.  
  8385.  
  8386.  
  8387.  
  8388.  
  8389.  
  8390.  
  8391.  
  8392.  
  8393.  
  8394.  
  8395.  
  8396.  
  8397.  
  8398.  
  8399.  
  8400.  
  8401.  
  8402. Editor's Forum
  8403. Just two issues back, I was telling you about all the exciting things going on
  8404. in the international standards arena, at least as related to the C programming
  8405. language. (See the Editor's Forum, CUJ November 1992.) The past few weeks have
  8406. seen a different kind of excitement welling up in the ANSI arena. I fear the
  8407. result won't be nearly as positive, however.
  8408. Standards, as you probably know, are produced largely by the effort of
  8409. volunteers. Many of us who developed the C standard put in half a year of
  8410. meeting time over a period of six years, not to mention a comparable
  8411. investment in work between meetings. Throw in the cost of travel and lodging
  8412. for two dozen meetings and you're looking at a serious investment in both time
  8413. and money. (Multiply that by 50 or 60 active participants and you begin to
  8414. appreciate the staggering cost of developing a programming language standard.)
  8415. For the privilege of doing all this grunt work, we pay an annual fee to CBEMA,
  8416. the secretariat to the ANSI-authorized committee X3 that oversees our efforts.
  8417. That fee has climbed over the years to a hefty $300 per year. Not outrageous
  8418. for those of us whose companies pick up the tab, but noticeable. As an
  8419. independent, I shelled out $650 last year for membership in X3J11 (C) and
  8420. X3J16 (C++). The extra $50 encourages CBEMA to distribute documents for X3J11.
  8421. But now CBEMA has begun issuing a flurry of additional bills, retroactive to
  8422. last year. The typical charge is an additional $300 per year for each X3
  8423. committee we participate in -- ostensibly to cover the costs of our
  8424. international activities. I've been asked to pay an additional $1,200 for a
  8425. variety of reasons. I can look forward to an additional $1,850 in bills for
  8426. 1993 before you even read these words. Needless to say, a number of us
  8427. standards drones are more than a little upset.
  8428. I can't judge whether these additional fees are reasonable, by some metric. I
  8429. do know that they were decided with no public debate and they were made
  8430. retroactive for most of a year. That is not a good way to treat volunteers,
  8431. techie or otherwise. The short term distraction is trifling compared to the
  8432. loss of talent this can cause in the longer term. And it makes us all leery
  8433. about what might happen next.
  8434. For my part, I am resigning a number of posts that I have long valued. I shall
  8435. continue as Convenor of WG14 -- I feel a responsibility for the stewardship of
  8436. C for at least the near future. I plan to keep up with the X3 activities in C
  8437. and C++. But I don't expect to travel quite as much or work quite as hard in
  8438. the standards arena from now on. I hope that not too many others feel pinched
  8439. enough to do likewise.
  8440. P.J. Plauger
  8441. pjp@plauger.com
  8442.  
  8443.  
  8444.  
  8445.  
  8446.  
  8447.  
  8448.  
  8449.  
  8450.  
  8451.  
  8452.  
  8453.  
  8454.  
  8455.  
  8456.  
  8457.  
  8458.  
  8459.  
  8460.  
  8461.  
  8462.  
  8463.  
  8464.  
  8465.  
  8466.  
  8467.  
  8468.  
  8469.  
  8470.  
  8471.  
  8472.  
  8473.  
  8474.  
  8475.  
  8476.  
  8477.  
  8478.  
  8479.  
  8480.  
  8481.  
  8482.  
  8483.  
  8484.  
  8485.  
  8486.  
  8487.  
  8488.  
  8489.  
  8490.  
  8491.  
  8492.  
  8493.  
  8494. New Products
  8495.  
  8496.  
  8497. Industry-Related News & Announcements
  8498.  
  8499.  
  8500.  
  8501.  
  8502. TauMetric Upgrades C++ Compilers for DEC/MIPS
  8503.  
  8504.  
  8505. TauMetric Corporation announced upgrades to its Oregon C++ Development Systems
  8506. for SPARC and for DECstations and DECsystems workstations. The Oregon C++
  8507. system provides direct code generation, C++ level 2.1 compatibility, and a C++
  8508. debugger. The Oregon C++ compiler includes modes for both ANSI C and K&R C.
  8509. Oregon C++ generates object code directly and the system provides a revised
  8510. version of Oregon Debugger (ODB) for C++. Major new features include: nested
  8511. class and type scopes, operator delete [] syntax, and overloading postfix ++
  8512. and --.
  8513. Oregon C++ is available for VAX/VMS, SPARC, MIPS (DECstation), HP 9000/300,
  8514. and Sun-3 Systems. Contact TauMetric Corporation, 8765 Fletcher Parkway, Suite
  8515. 301, La Mesa, CA 91942, (619) 697-7607, or (800) 874-8501; FAX: (619)
  8516. 697-1140.
  8517.  
  8518.  
  8519. TFS Releases C++ Class Library and Hypertext Help System
  8520.  
  8521.  
  8522. The Friendly Solutions (TFS) Company has released C*Drive++, a C++ class
  8523. library, and FASTVIEW, a hypertext help system and help text compiler.
  8524. C*Drive++ contains low-level routines for windowing, pop-up and pull-down menu
  8525. functions, form entry, and printer control. The package also includes classes
  8526. for screen I/O, mouse/ keyboard support, string handling, color control, and
  8527. directories. Source code is provided. The class library is compatible with
  8528. Borland and Microsoft compilers. FASTVIEW allows developers to create TSR help
  8529. text. The help engine works with C*Drive++ or as a stand alone system. Source
  8530. for the FASTVIEW engine is included.
  8531. C*Drive++ is priced at $140, and FASTVIEW (with royalty free distribution) is
  8532. priced at $199. Contact The Friendly Solutions Company, 6309 Chimney Wood Ct.,
  8533. Alexandria, VA 22306, (703) 765-0654.
  8534.  
  8535.  
  8536. Quarterdeck Provides X11 and Motif Software Development Toolkits for
  8537. DESQview/X
  8538.  
  8539.  
  8540. Quarterdeck Office Systems, Inc. has announced its DESQview/X X11 and Motif
  8541. Software Development Toolkits. Also available at no charge with the toolkits
  8542. is the GNU C/C++ compiler. With the X11 Toolkit, developers will be able to:
  8543. port X clients from other X Window System environments to DESQview/X, create
  8544. new X clients, perform network-independent communication between machines,
  8545. access Adobe Type Manager to use Type 1 scalable fonts, and customize
  8546. DESQview/X through utility programs and configuration files.
  8547. The DESQview/X X11 Starter Toolkit, for use with the GNU C/C++ compiler only,
  8548. includes the X11 R4 programming libraries, the DESQview/X system library with
  8549. Berkeley Socket Interface, sample programs, make files for GNU, the DESQview/X
  8550. Road-map documentation, and the GNU C/C++ compiler. The starter kit sells for
  8551. $50. The complete DESQview/X X11 Toolkit, priced at $750, includes (in
  8552. addition): make files and library support for Microsoft C, Borland C++,
  8553. Zortech C++, Rational Instant C, Watcom C/386, and MetaWare High C compilers,
  8554. O'Reilly X Reference and Programming Guides (Volumes 1, 2, 4, and 5), Rational
  8555. Systems DOS/4GX DOS extender and tools, Instant C development environment,
  8556. Oxygen, and Quarterdeck's Developer Passport Support. The toolkit components
  8557. are available separately.
  8558. Quarterdeck has made the Quarterdeck X libraries for DESQview/X available to
  8559. GNU for free distribution with its C/C++ compiler. For information, contact
  8560. Quarterdeck Office Systems, Inc., 150 Pico Boulevard, Santa Monica, CA 90405,
  8561. (800) 354-3222 or (310) 392-9851, FAX: (310) 399-3802.
  8562.  
  8563.  
  8564. Innovative Data Solutions Upgrades PARAGen Code Generator for PARADOX
  8565.  
  8566.  
  8567. Innovative Data Solutions, Inc., has upgraded its PARAGen code generator for
  8568. the PARADOX engine. PARAGen creates C, C++, standard Pascal, object-oriented
  8569. Pascal, and Pascal for Windows (TPW) code for accessing PARADOX database
  8570. applications. PARAGen creates OOP code with classes and member functions that
  8571. manipulate those classes. PARAGen creates an internal record structure that
  8572. represents the actual structure of the PARADOX table, enabling record and
  8573. search operations to populate the structure and access values stored in the
  8574. transfer buffer immediately. Optimization, error checking, and generated code
  8575. style are configurable. Users can create and examine PARADOX tables from
  8576. within PARAGen, allowing productive work without the neeed to load PARADOX on
  8577. the machine. PARAGen's look and feel has been changed to conform to the
  8578. CUA/SAA compliant interface standard.
  8579. PARAGen retails for $179 and comes in a DOS specific version and a native
  8580. Windows 3.x version. Contact Innovative Data Solutions, Inc., 1757 Eastwood
  8581. Court, Suite 9, Schaumburg, IL 60195, (708) 882-3713.
  8582.  
  8583.  
  8584. Non Standard Logics Implements XFaceMaker X/Motif GUI Builder for SCO Open
  8585. Desktop
  8586.  
  8587.  
  8588. Non Standard Logics SA (NSL) has implemented its XFaceMaker (XFM) interactive
  8589. graphical interface builder for The Santa Cruz Operation's (SCO) Open Desktop.
  8590. For information, contact the US office of NSL at 99 Bedford Street, Boston, MA
  8591. 02111, (617) 482-6393; FAX: (617) 482-0357. NSL European headquarters offices
  8592. are at 57-59 Rue Lhomond, 75005 Paris France; telephone (33-1) 43 36 77 50,
  8593. FAX: (33-1) 43 36 59 78.
  8594.  
  8595.  
  8596. Quarterdeck Adds DOS Protected Mode Interface (DPMI) Host to QEMM-386
  8597.  
  8598.  
  8599. Quarterdeck Office Systems, Inc., has announced their DPMI Host, which
  8600. supports virtual memory and provides a companion product to QEMM-386, their PC
  8601. memory-management software. Quarterdeck's DPMI Host is compatible with
  8602. Microsoft C/C++, Borland C++, and Intel's Code Builder Kit. The Quarterdeck
  8603. DPMI Host requires QEMM-386, DESQview-386, or DESQview/X. The DPMI Host
  8604. supports running multiple DPMI programs inside Quarterdeck's DESQview
  8605. products.
  8606. Quarterdeck DPMI Host is a full implementation of versoin 0.9 of the DPMI
  8607. specification, including DOS extensions. Quarterdeck will make the DPMI Host
  8608. available to registered Quarterdeck users at no cost, through the company's
  8609. user BBS: (310) 314-3227. Online documentation is available. QEMM users can
  8610. also download the DPMI Host from various online services. Users who want disks
  8611. and hard copy documentation can order DPMI Host directly for $30 (or free to
  8612. Quarterdeck Passport Support Subscribers). For information, contact
  8613. Quarterdeck Office Systems, Inc., 150 Pico Boulevard, Santa Monica, CA 90405,
  8614. (800) 354-3222 or (310) 392-9851; FAX: (310) 399-3802.
  8615.  
  8616.  
  8617. Algorithmic Solutions Releases IFF Indexed File Management Library for UNIX
  8618. System V
  8619.  
  8620.  
  8621. Algorithmic Solutions has released IFF,the Indexed File Management Library for
  8622. C programming environments. IFF is a C library that supports indexed files,
  8623. with up to 16 keys, both unique and non-unique keys, seven key data types,
  8624. user-defined key data type support, segmented keys with up to 10 components,
  8625. file and record locking, and automatic, internal lock management that enables
  8626. both single and multiuser access. IFF uses a single file that incorporates
  8627. both data and indexing structure, avoiding the need for special utilities for
  8628. copying or moving files.
  8629. IFF prices begin at $595, and SCO UNIX, SPARC and HP-UX are supported. Contact
  8630. Algorithmic Solutions, P.O. Box 382, Simpsonville, MD 21150-0382, (301)
  8631. 421-0134; FAX: (301) 942-1452.
  8632.  
  8633.  
  8634.  
  8635. Software Through Pictures Ships on SCO Open Desktop Release 2.0
  8636.  
  8637.  
  8638. Interactive Development Environments, Inc. (IDE) has announced the shipment of
  8639. Software through Pictures Integrated Structured Environment (ISE) Release 4.2D
  8640. for SCO Open Desktop Release 2.0. IDE's Software through Picture is a
  8641. multiuser, integrated CASE environment that supports development for technical
  8642. and commercial applications.
  8643. A single license for Software through Pictures ISE for SCO Open Desktop ranges
  8644. from $5,000 to $21,000, depending on the configuration. Suggested hardware
  8645. configuration includes a 33MHz 486 PC, 300MB hard drive, and 16MB RAM. For
  8646. information, contact Interactive Development Environments, 595 Market Street,
  8647. 10th Floor, San Francisco, CA 94105, (800) 888-IDE1, (415) 543-0900; FAX:
  8648. (415)543-0145.
  8649.  
  8650.  
  8651. Sequiter Software Announces Clipper to C Translator
  8652.  
  8653.  
  8654. Sequiter Software has released CodeTranslator 2.0, which translates Clipper
  8655. '87 and dBASE III PLUS applications into C, and has announced plans for a
  8656. November release of CodeBase 5.0. CodeTranslator generates ANSI compliant C
  8657. code, designed to correspond to the original dBASE/Clipper application,
  8658. preserving specific variable and function names, as well as familiar commands
  8659. like goto and skip. CodeBase 5.0 is a C/C++ library which has file and
  8660. multiuser compatibility with dBASE, FoxPro, and Clipper. New CodeBase features
  8661. include bit optimization technology (for faster queries) and CodeReporter, a
  8662. Microsoft Windows report design tool.
  8663. CodeTranslator retails for $245 and CodeBase retails for $395. Contact
  8664. Sequiter Software, Inc., Suite 209, 9644 - 54 Avenue, Edmonton, Alberta,
  8665. Canada, T6E 5V1, (403) 437-2410, FAX: (403) 436-2999.
  8666.  
  8667.  
  8668. RTXC and PC/104 Unite for Real-time Embedded Applications
  8669.  
  8670.  
  8671. A. T. Barrett & Associates has announced real-time kernel support for the
  8672. PC/104 Consortium with its real-time executive, RTXC. RTXC is a real-time
  8673. multitasking executive for use in embedded systems requiring a deterministic
  8674. design with pre-emptive scheduling for event-driven operation. RTXC is written
  8675. primarily in ANSI C and supports all the standard PC-compatible resources.
  8676. RTXC provides a library of services including task, memory, and resource
  8677. management, intertask communication and synchronization, and timer support.
  8678. RTXC is available in three configurations, Basic Library (26 kernel services),
  8679. Advanced Library (38 kernel services), or Extended Library (55 kernel
  8680. services). Source code is supplied and there are no royalties for continued
  8681. use in multiple products. For information, contact A.T. Barett & Associates,
  8682. 11501 Chimney Rock, Suite R, Houston, TX 77035, (713) 728-9688.
  8683.  
  8684.  
  8685. AccSys for dBASE Version 2 Provides Faster and More Flexible API
  8686.  
  8687.  
  8688. Copia International, Ltd., has released version 2.0 of AccSys for dBASE. Their
  8689. announcement stated that the new version runs five times faster than previous
  8690. versions, for multiuser applications with index files. Version 2 adds high
  8691. level functions that can read, write, update, pack, index, and re-index the
  8692. database. The AccSys for dBASE Expression Analyzer allows programmers to read,
  8693. write, and update "foreign databases". Version 2 adds dEparse, dErun, and
  8694. dEeval, enabling AccSys for dBASE to parse, evaluate, and interpret dBASE key
  8695. and index expression. Over 70 dBASE look-alike functions have also been added.
  8696. AccSys for dBASE is priced at $395 or $995 with source. Contact Copia
  8697. International, Ltd., 1342 Avalon Court, Wheaton, IL 60187, (708) 682-8898;
  8698. FAX: (708) 665-9841.
  8699.  
  8700.  
  8701. Symantec Announces MultiScope Debuggers for Borland and Microsoft C++
  8702. Applications
  8703.  
  8704.  
  8705. Symantec Corporation has announced version 2.0 of its MultiScope Debuggers,
  8706. which supports Borland C++ and Microsoft C/C++ (6.0 and 7.0) for programming
  8707. Windows and DOS applications. The MultiScope Debuggers offer a Windows-hosted
  8708. (3.1) graphical user interface. The CommandBar provides quick access to
  8709. frequently used debugging commands. The DOS debuggers can be Windows-hosted
  8710. and allow debugging of DOS applications in a DOS window. A Symantec
  8711. representative described the MultiScope Debuggers as designed specifically for
  8712. debugging C++ code and noted the inclusion of features such as class browsing,
  8713. automatic object mapping, object-oriented breakpoints, and name unmangling.
  8714. MultiScope includes both Run-Time Debugging for controlling program execution
  8715. and a Crash Analyzer System for examining the aftermath of program crashes.
  8716. MultiScope Debuggers for Windows is priced at $379. The DOS version runs $179.
  8717. For information, contact Symantec Corporation, 10201 Torre Avenue, Cupertino,
  8718. CA 95014-2132, (800) 999-8846 or (408) 253-9600; FAX: (408) 253-4092; Telex:
  8719. 9103808778.
  8720.  
  8721.  
  8722. EnQue Upgrades UltraWin Text Windowing Library
  8723.  
  8724.  
  8725. EnQue Software has upgraded UltraWin 2.10, its text windowing library, whose
  8726. features include unlimited overlapping windows, background printing, PC timer
  8727. control, mouse and graphic support, and enhanced data entry capabilities,
  8728. along with a hypertext help engine and an EGA/VGA font editor. EnQue has also
  8729. released InTUItion 1.10, a textual user-interface library which includes a
  8730. tool-supporting interactive interface design. EnCom 1.00, a COMM library is a
  8731. new introduction from EnQue, with interrupt driven transmit and receive,
  8732. support for COMM ports one through four, CRC and message formatting
  8733. facilities.
  8734. UltraWin, EnCom, and InTUItion, with small model libraries are available free
  8735. from various online services, or by sending $2 for shipping and handling to
  8736. EnQue. Complete source with large model libraries and printed manuals are
  8737. available for EnCom ($100), UltraWin ($100), or both UltraWin and InTUItion
  8738. ($200). Contact EnQue Software, Route 1, Box 116C, Pleasant Hill, MO 64080,
  8739. Voice/FAX: (816) 987-2515; EnQue BBS: (816) 353-0991.
  8740.  
  8741.  
  8742. Library Technologies Releases Version 3.0 of C-Heap Memory Management Library
  8743.  
  8744.  
  8745. Library Technologies has released version 3.0 of its C-Heap memory management
  8746. library for Microsoft and Borland C/C++ compilers. C-Heap now supports the
  8747. allocation of memory from upper memory blocks (UMBs) through malloc, either
  8748. via MS-DOS calls or the XMS driver transparently. C-Heap can use 64K of
  8749. expanded (EMS) memory as heap space. Version 3.0 of C-Heap adds local heaps to
  8750. its list of memory tools. Local heaps are similar to Microsoft-based heaps,
  8751. but are not restricted to 64K. Any block of heap memory of any size can be
  8752. used as a local heap and allocated from as required. If the block is
  8753. associated with a particular object, the block may be swapped out of memory to
  8754. expanded, extended, or virtual memory as required. C-Heap is priced at $229,
  8755. including source. Contact Library Technologies, P.O. Box 56031, Madison, WI
  8756. 53705-9331, (800) 267-6171.
  8757.  
  8758.  
  8759. SilverWare Inc. Adds COM Port Control to dBASE IV
  8760.  
  8761.  
  8762. SilverWare, Inc., has released SilverComm 3.00 (SPCS), a communications
  8763. library (written in C and assembly) that provides xBASE programmers with
  8764. access to COM port control. xBASE programs can auto detect and invoke the
  8765. 16550 FIFO UART, increasing data throughput and reliability at baud rates up
  8766. to 115K. SilverComm 3.0 includes support for the IBM PS/2 Dual Async Multi COM
  8767. Adapter and shared interrupt (IRQ) handling on Micro Channel adapters. Other
  8768. new features include support for high number interrupts on multi COM boards,
  8769. YMODEM Batch file transfer protocol. SilverComm also supports XMODEM, YMODEM
  8770. and ASCII transfers, ANSI and TTY terminal emulations, and AST or STB multi
  8771. COM boards. SilverComm 3.0 is priced at $249 (upgrages $89) and includes
  8772. complete C and assembly source code. Contact SilverWare, Inc., 3010 LBJ
  8773. Freeway, Suite 740, Dallas, TX 75234, (214) 247-0131, FAX: (214) 406-9999.
  8774.  
  8775.  
  8776. Pryor and Pryor Releases Communications Libraries
  8777.  
  8778.  
  8779. Pryor and Pryor, Inc., has released three communications products for C and
  8780. assembly programmers. The SPI library is a communications library module for
  8781. serial ports which supports sharing IRQs. SPI and a mouse can coexist on
  8782. either IRQ3 or IRQ4 at rates up to 4,800 baud. The FCL library is a
  8783. file-compression module that can be used in dynamically compressing
  8784. file-transfer programs as well as simple disk file-compression programs. The
  8785. SDA serial Data Analyzer program converts a PC into a tool for monitoring and
  8786. displaying data flows over an RS232 link. The user may select display modes,
  8787. capture and store data in real-time, playback captured data, and print
  8788. information. Prices range from $49 (SPI, FCL) to $229 (SDA). For information,
  8789. contact Pryor and Pryor, Inc., 602 -- 1230 Comox Street, Vancouver, B.C.,
  8790. Canada, Voice/FAX: (604) 669-2609.
  8791.  
  8792.  
  8793.  
  8794. MetaCard Upgrades UNIX Development Environment
  8795.  
  8796.  
  8797. MetaCard Corporation has upgraded their hypermedia and rapid application
  8798. development environment, MetaCard 1.2. The release is Motif-compliant and will
  8799. be supported on nine UNIX/X11 platforms, including new support for Silicon
  8800. Graphics IRIS, Data General AViiON, and IBM RS/6000 workstations. A C-based
  8801. extension allows MetaCard to be used for computationally-intensive or
  8802. hardware-dependent applications. C functions can be called directly from
  8803. MetaCard; no relinking or other processing is required. A save-disabled
  8804. product is available free via Internet. MetaCard is self-contained, no C
  8805. compiler is required. Single-user licenses for MetaCard cost $495, with site
  8806. and volume licensing available. Contact MetaCard Corporation, 4710 Shoup
  8807. Place, Boulder, CO 80303, (303) 447-3936.
  8808.  
  8809.  
  8810. Adaptive Solutions Ships Parallel Processor CNAPS Computer Systems
  8811.  
  8812.  
  8813. Adaptive Solutions, Inc., has begun shipping its parallel-processing CNAPS
  8814. computer systems, designed for pattern recognition and signal-processing
  8815. applications. CNAPS systems can be configured with 128, 256, or 512
  8816. processors. The CNAPS system connects to a UNIX host via Ethernet. The CodeNet
  8817. development tools provide Motif interfaces and include the CNAPS-C compiler,
  8818. with extensions to support massive parallelism and scaled fixedpoint
  8819. arithmetic of the CNAPS architecture. Other tools include the CNAPS
  8820. Programming Language (CPL) assembler and debugger, command-line and windowing
  8821. interfaces, and data conversion utilities. Contact Adaptive Solutions, Inc.,
  8822. 1400 NW Compton Drive, Suite 340, Beaverton, Oregon 97006, (503) 690-1236;
  8823. FAX: (503) 690-1249.
  8824.  
  8825.  
  8826. SMDS Updates Aide-De-Camp and Allies with Centerline
  8827.  
  8828.  
  8829. Software Maintenance and Development Systems, Inc. (SMDS), has released
  8830. version 8.0 of the Aide-De-Camp (ADC) software-configuration management
  8831. system. Version 8.0 includes the Lakota scripting language. ADC is
  8832. object-based and can track software changes at a user-defined level, including
  8833. file, subroutine, procedure, or components of individual changes. SMDS also
  8834. announced a joint marketing and product integration alliance with Centerline
  8835. Software, Inc. (formerly Saber Software). The integration effort will link ADC
  8836. and Centerline's CodeCenter and ObjectCenter UNIX programming environments, to
  8837. provide support for developing and managing large C/C++ software inventories.
  8838. Contact Software Maintenance & Development Systems, Inc., P.O. Box 555,
  8839. Concord, MA 01742, (508) 369-7398; FAX: (508) 369-8272.
  8840.  
  8841.  
  8842. Promula Development Corporation Announced Fortran for COHERENT
  8843.  
  8844.  
  8845. Promula Development Corporation and Mark Williams Company have announced
  8846. Promula's PROMULA FORTRAN compiler for COHERENT. PROMULA FORTRAN supports
  8847. FORTRAN 66, FORTRAN 77, VAX FORTRAN, PRIME FORTRAN, SUN FORTRAN, and some
  8848. FORTRAN 90 extensions, and passes the Federal Software Testing Center's
  8849. FORTRAN Compiler Validation suite, version 2. PROMULA FORTRAN provides natural
  8850. integration with C-based libraries on COHERENT and is compatible with the
  8851. PROMULA FORTRAN to C Translator, which generates C source. PROMULA FORTRAN for
  8852. COHERENT is priced at $95. Contact Promula Development Corporation, 3620 North
  8853. High Street, Suite 301, Columbus, OH 43214, (614) 263-5454; FAX: (614)
  8854. 263-5573.
  8855.  
  8856.  
  8857. Knowledge Dynamics Corp's INSTALL Improves Data Compression in Version 3.2
  8858.  
  8859.  
  8860. Knowledge Dynamics Corportion has updated INSTALL, with a new compression
  8861. algorithm, Reduce, that compresses data to 60% for most products. Version 3.2
  8862. has an enhanced script language with fully general assignment statements, 12
  8863. new string-handling functions, on-the-fly modificaiton of environment
  8864. variables, and the ability to cold or warm boot the end-user's computer. An
  8865. option for INSTALL 3.2 is the HyperText Help System, a TSR which provides
  8866. access to the INSTALL manual, requires 2K RAM with EMS/XMS, and is Norton
  8867. Guides-compatible. Contact Knowledge Dynamics Corporation, P.O. Box 1558,
  8868. Canyon Lake, TX 78130-1558, (512) 964-3994.
  8869.  
  8870.  
  8871. Microtec Research Introduces Software Development Environment for Embedded
  8872. Systems
  8873.  
  8874.  
  8875. Microtec Research, Inc., has introduced XRAY MasterWorks, a C/C++ integrated
  8876. software-development environment specifically designed for embedded-systems
  8877. engineers. XRAY MasterWorks is a package of program building, code generation,
  8878. debugging, and analysis tools that provides push-button control over a variety
  8879. of development tasks. Tight integration provides features such as rebuilding a
  8880. program and reloading it into the debugger with a single command. XRAY
  8881. MasterWorks contains XRAY Master, XRAY Debugger, and the C/C++ cross-compiler
  8882. package. XRAY Master includes the following components: an environment
  8883. manager, XRAY Control Panel (configuration control), XRAY Source Explorer,
  8884. XRAY Make, and Object Format Converter (converts code to formats usable with
  8885. logic analyzers and PROM programmers). XRAY Debugger supports multiple windows
  8886. and can debug code in various execution environments, including
  8887. instruction-set simulation, in-circuit emulation, and in-circuit monitor. The
  8888. ANSI C and C++ Cross Compiler Package generates ROMable, reentrant code, and
  8889. includes compiler, macro assembler, liner, and librarian.
  8890. XRAY MasterWorks is hosted on SPARCstations and initially supports embedded
  8891. systems based on Motorola 68x0 and 683xx processors. For information, contact
  8892. Microtec Research Inc., 2350 Mission College Boulevard, Santa Clara, CA 95054,
  8893. (800) 950-5554; FAX: (408) 982-8266.
  8894.  
  8895.  
  8896. Borland Ships BRIEF 3.1 for DOS and OS/2
  8897.  
  8898.  
  8899. Borland International, Inc., has begun shipping BRIEF 3.1 for DOS and OS/2.
  8900. Formerly sold by Solution Systems as separate products Borland will bundle the
  8901. two versions of the programmer's editor in a single package. New features in
  8902. BRIEF 3.1 include mouse support, EMS, Redo, and Dialog Box support. Brief has
  8903. a C-like macro language; statement completion; integration with compilers;
  8904. language support including Borland C++, Turbo Pascal, dBASE and Assembly; and
  8905. multiple, resizable windows. Contact Borland International, Inc., 1800 Green
  8906. Hills Road, P.O. Box 660001, Scotts Valley, CA 95067-0001, (408) 439-4825.
  8907.  
  8908.  
  8909. Softbridge Announces Automated Test Facility 2.0
  8910.  
  8911.  
  8912. Softbridge, Inc., has announced Automated Test Facility (ATF) 2.0. ATF is a
  8913. software system designed for unattended testing of applications written under
  8914. Windows, OS/2, and DOS, either standalone or in networked environments. ATF
  8915. 2.0 includes interactive menu-based test script building, online hypertext
  8916. help, revamped user interface, and enhanced test management. ATF 2.0 provides
  8917. a more open architecture, with Dynamic Link Library (DLL) functions. The SQL
  8918. Server database required in earlier versions is now an optional feature.
  8919. Contact Softbridge, Inc., 125 Cambridge Park Drive, Cambridge, MA 02140, (617)
  8920. 576-2257; FAX: (617) 864-7747.
  8921.  
  8922.  
  8923. CHAMPS Software Announces CHAMPS/CASE
  8924.  
  8925.  
  8926. CHAMPS Software, Inc, has announced CHAMPS/CASE, a tool providing a framework
  8927. for Rapid Application Development. CHAMPS/CASE is designed as a VAX Rdb/VMS
  8928. layered product and should be available first quarter 1993. CHAMPS/CASE was
  8929. designed to expedite the transition from FMS, RMS, and Cobol-based development
  8930. to IFDL, Rdb, and ANSI C. CHAMPS/CASE generates 100% of the C source code for
  8931. applications and can create ANSI C, ANSI SQL, and IFDL programs. CHAMPS is
  8932. NAS, ANSI/SQL, FIMS, and POSIX compliant and supports DEC's COHESION
  8933. environment. Contact CHAMPS Software, Inc. 1255 North Vantage Point, Crystal
  8934. River, FL 34429, (904) 795-2362; FAX: (904) 795-9100.
  8935.  
  8936.  
  8937.  
  8938.  
  8939.  
  8940.  
  8941.  
  8942.  
  8943.  
  8944.  
  8945.  
  8946.  
  8947.  
  8948.  
  8949.  
  8950.  
  8951.  
  8952.  
  8953.  
  8954.  
  8955.  
  8956.  
  8957.  
  8958.  
  8959.  
  8960.  
  8961.  
  8962.  
  8963.  
  8964.  
  8965.  
  8966.  
  8967.  
  8968.  
  8969.  
  8970.  
  8971.  
  8972.  
  8973.  
  8974.  
  8975.  
  8976.  
  8977.  
  8978.  
  8979.  
  8980.  
  8981.  
  8982.  
  8983.  
  8984.  
  8985.  
  8986.  
  8987.  
  8988.  
  8989.  
  8990.  
  8991.  
  8992.  
  8993.  
  8994.  
  8995.  
  8996.  
  8997.  
  8998.  
  8999.  
  9000.  
  9001.  
  9002.  
  9003. We Have Mail
  9004. Dear Mr. Plauger:
  9005. I have been reading the C Users Journal since it was little more than a
  9006. newsletter for the users group. The one thing I have noticed in C programmers
  9007. is the use of the functions "FOO" and "FOOBAR" within a test module. Where are
  9008. the origins of such naming conventions, and to what are they attributed?
  9009. Although, not a technical question, it has aroused my curiosity over the
  9010. years.
  9011. Sincerely,
  9012. Mr. Philip Felice
  9013. U.S. Trust Company of New York
  9014. 770 Broadway
  9015. New York, NY 10003
  9016. The terms foo, bar, and foobar all derive from the old U.S. Navy slang FUBAR,
  9017. their answer to the Army's SNAFU. FUBAR leaked into the programming community
  9018. through MIT. The (somewhat laundered) Navy term is an acronym for "fouled up
  9019. beyond all repair," while the Army term is short for "situation normal, all
  9020. fouled up." -- pjp
  9021. Mr Plauger,
  9022. Thank you for doing such a fine job over the years. My first subscription
  9023. issue came Saturday. Exciting. Exciting--an article on neural nets--then on
  9024. page 85 the article falls off the face of the earth????? How beastly
  9025. clever--they have implemented a '50s movie cliff hanger? (OK. OK. Just
  9026. kidding.)
  9027. I'm sure you have really heard about this problem by now. I hope that the end
  9028. of the article will be in next month's issue. (Well OK, deadlines being what
  9029. they are--the month after?)
  9030. This is one issue that I am rushing a check for the source code disk. I really
  9031. appreciate the cost of the disk to be affordable!
  9032. Thanks & Cheers,
  9033. deryl
  9034. I can't claim credit for that particular gaffe. Some days, there's enough
  9035. embarrassment to go around. -- pjp
  9036. Diane Thomas, Managing Editor replies:
  9037. Profuse apologies for our FUBAR. You can find the correction to that article
  9038. on page 102 of the October 1992 issue. If you don't care to purchase the
  9039. October issue, please call or write and I will fax or mail the correction to
  9040. you.
  9041. Editor:
  9042. I am constantly impressed with the articles and the quality of The C Users
  9043. Journal and I want to thank you for all the work that you do. It always
  9044. disappoints me to read some flame from a reader who is mostly demonstrating
  9045. his lack of tact or inability to express an opinion without insulting someone.
  9046. I hope you take all flames with a grain of salt and recognize that most people
  9047. that are satisfied or happy don't write. I, personally, rarely get the time.
  9048. It's also great to see that the Gods and Gurus make mistakes, too. I noticed a
  9049. goof that you'll probably get a hundred letters on and is one of the ones I've
  9050. read a number of other gurus do commonly. In your "Bugs" article, your example
  9051. of violation of coding standards showed the "improved version" as:
  9052. char fn[L_tmpnam], *s;
  9053. if ((str = fopen ((const char *)
  9054. tmpnam(fn), "wb+")) == NULL)
  9055. ;
  9056. else if ((s = (char *)
  9057. malloc(sizeof (fn) + 1))
  9058. == NULL)
  9059. fclose (str); str = NULL;
  9060. else
  9061. str->_Tmpnam = strcpy(s, fn);
  9062. The erroneous line is the one with the fclose on it. You will notice that
  9063. there are two statements between the else if and the else with no curly
  9064. braces. Tsk, tsk, tsk. :-)
  9065. I've gotten into the habit of always using curly braces just because I made
  9066. this mistake all the time. Also, I frequently want to put a print statement
  9067. into the if statement somewhere and then have to put curly braces around it
  9068. anyway.
  9069. Thanks again for all the very interesting articles and wealth of information.
  9070. Chris Carlson
  9071. carlson@sgi.com
  9072. Howdy;
  9073. I just got the latest issue of The C Users Journal, and was zipping through
  9074. your "Standard C" column when I noticed that one of your "fixes" contains yet
  9075. another bug. The offending code appears on page 12. [same code as in previous
  9076. letter follows -- pjp] I doubt this will even compile as written.
  9077. I'm pretty sure you intended to write:
  9078. fclose(str), str = NULL;
  9079. Don't you just hate when that happens :-)
  9080. Timothy G. Northrup
  9081. ...!rutgers!brspyr1!jrsalb1!tim
  9082. Yup. Luckily, the error occurred in transcription for publication in the
  9083. magazine (my fault). The actual code does compile and run its test cases. --
  9084. pjp
  9085. Dear Mr. Plauger,
  9086. I enjoyed reading your article "Bugs" in the September '92 issue of The C
  9087. Users Journal. Your book, The Standard C Library is quite informative.
  9088. I did notice one example which, as written, has undefined behavior according
  9089. to The Standard as I understand it. On page 209 your va_fputs function returns
  9090. without calling va_end on a write error. In your implementation of the library
  9091. it doesn't make any difference, but as the text points out that may not always
  9092. be the case.
  9093. While I'm on the subject, I don't recall ever reading a discussion of an
  9094. implementation of the Standard C library suitable for use by multi- threaded
  9095. programs. I program on the Amiga where spinning off multiple threads is quite
  9096. easy to do, and while the system shared libraries are written to expect to be
  9097. called from various tasks at any time, the C linker libraries for the
  9098. compilers I've looked at expect to have only one caller at a time. Some
  9099. functions work just fine anyway (strlen for example) while others like strtok
  9100. (which make use of an internal static variable in the implementations I've
  9101. seen) are clearly not going to like having multiple asynchronous callers. So
  9102. if you're looking for an article idea you may want to consider "Library Use by
  9103. Multiple Concurrent Threads."
  9104. Otherwise, it's good to have The Editor reachable via e-mail. Now if we only
  9105. had a tutorial on the correct spelling of "Plauger." (Wow! Your own domain! Is
  9106. that anything like the fat jokes, "He's as big as New Jersey" or "He has his
  9107. own zip code?")
  9108. Todd M. Lewis
  9109. utoddl@guitar.oit.unc.edu
  9110. That's a bug all right. It's already fixed in the second printing, but thanks
  9111. for reporting it. My brother, Dave Plauger, has just finished making a
  9112. thread-safe C++ library for Hewlett Packard. I'm trying to coax him into
  9113. writing an article on what he did. -- pjp
  9114. Dear Mr. Plauger,
  9115. This is an answer to Mr. Grabbe's letter in the July issue of CUJ.
  9116. The point editor for the PC is available for free using anonymous ftp.
  9117. Address: unmvax.cs.unm.edu.
  9118. login: anonymous
  9119. password: id
  9120.  
  9121. It is located in /pub/Point/ptpc.tar.Z. This is a compressed archive file that
  9122. will be extracted on (hopefully) any UNIX system using
  9123. zcat ptpc.tar.Z tar xvf -
  9124. Note: there is also an X-Windows version of point at the same location (best
  9125. UNIX editor I have ever seen besides emacs/epoch).
  9126. As far as I know point has been posted to comp.sources.*. Right, Mr.
  9127. Weinstein? By the way, where is your column? Continue tracking comp.sources.x!
  9128. PS: The last issues of CUJ have become somewhat "less academic" maybe due to
  9129. the discussion some months ago. To me this is a serious drawback for the
  9130. quality of your magazine.
  9131. Yours truly,
  9132. Gerhard Wilhelms
  9133. Lehrstuhl fuer Informatik I
  9134. Universitaet Augsburg
  9135. Universitaetsstr. 2
  9136. W-8900 Augsburg
  9137. GERMANY
  9138. Which is a serious drawback, that the magazine is less or more academic? You'd
  9139. think we could get 50,000 readers to agree on something. Seriously, all we can
  9140. do is keep striving for a good balance in selecting from submissions. We
  9141. appreciate your continuing input to help us tune that balance. -- pjp
  9142. Editor:
  9143. In the article titled "Bit Arrays with C++" in the July 1992 issue of CUJ,
  9144. authors A. Giancola and L. Baker chose an approach that seemed very unorthodox
  9145. to me. If possible I would appreciate a clarification of their design
  9146. rationale.
  9147. The code given in the article cannot, according to the authors themselves, be
  9148. compiled with a licensed derivative of the AT&T C++ compiler. A second
  9149. compiler, Borland's C++ v3.0, does compile the code but issues a warning
  9150. message. The compilation problems stem directly from the use of a nested class
  9151. inside a template class, a design that Giancola and Baker chose in order to
  9152. "prevent inadvertent misuse of objects." Why do they consider this possibility
  9153. to be of such importance? Of what value is "safe" code if it cannot be
  9154. compiled, or compiles with warnings?
  9155. The authors' stated goal was to create a bit array that could be addressed
  9156. using arithmetic assignment statements of the form A(i,j) = x;. This was quite
  9157. a difficult problem and required approximately a hundred lines of clever code
  9158. to solve. An alternate approach would have been to access array elements
  9159. through an assignment function. this would have required perhaps ten or twenty
  9160. lines of straightforward code. There is no question that it also would have
  9161. the advantage of faster execution. To call an access function, one must write
  9162. a statement of the form A.set(i,j,x); instead of A(i,j) = x;. Is the second
  9163. form so much cleaner that it is worth the coding effort and the sacrifice in
  9164. execution speed?
  9165. In the first sentence of the article, Giancola and Baker explain that they
  9166. developed this code while working in a "Go" program. Their task was to
  9167. represent the Go board, a 19x19 grid in which each location can be either
  9168. white, black, or empty. This could have been done by using an array of char,
  9169. e.g., char board[19][19], requiring 361 bytes of storage. No special code
  9170. would be needed since access to 2-dimensional char arrays is built into the
  9171. C/C++ language. The implementation chosen by the authors requires only 113
  9172. bytes of storage, but at the expense of added complexity.
  9173. Unless the program must store a large number of Go positions simultaneously,
  9174. the modest savings in storage space achieved by the authors hardly seems worth
  9175. the effort. Furthermore, storing and retrieving values would be relatively
  9176. efficient for a 19x19 array of characters; packing the information into 113
  9177. bytes makes accessing of data about an order of magnitude more complex (and
  9178. thus slower). In these days where PCs are typically equipped with megabytes of
  9179. memory, the authors' apparently extreme desire to conserve storage space is
  9180. difficult to understand.
  9181. If storage space was indeed at a premium for some reason, it is odd that
  9182. Giancola and Baker chose an implementation in which all of the bit vector code
  9183. is in the form of inline functions. The inline expansion of every bit vector
  9184. function call will consume code space; why not use non-inline functions to
  9185. reduce the program's code size?
  9186. At a detailed level the code is not as efficent or direct as it could be. This
  9187. fragment:
  9188. int i;
  9189.  
  9190. tmp_mask = 1;
  9191. for (i=1;i<N;i++) {
  9192. tmp_mask <<=1;
  9193. tmp_mask =1;
  9194. }
  9195. could be replaced by the single line:
  9196. tmp_mask = (1 << NUM_BITS) - 1;
  9197. The code for class BIT_TMP declares two BYTE arrays with a fixed-dimension of
  9198. eight. One of the arrays (MASK) is never accessed except for its zeroth
  9199. element (the other elements appear only as temporaries in a calculation and
  9200. could be eliminated). The second array (INV_MASK) does not need to be eight
  9201. characters long in all cases; its length could be computed at compile-time as
  9202. INV_MASK[8/NUM_BYTES]. These two changes could save as many as 13 bytes of
  9203. storage per object with no penalty.
  9204. In the constructor for class BIT_TMP a temporary double (num_sub_fields) is
  9205. used where an int or unsigned char would work fine. This would speed execution
  9206. with no penalty.
  9207. The two operator= functions defined in BIT_TMP end with an assignment to a
  9208. member variable (value). These functions are of return type void. Since
  9209. BIT_TEMP objects only exist as temporaries, these assignments are useless; the
  9210. object will be destroyed after the closing brace of operator+().
  9211. While I salute the ingenuity of Giancola's and Baker's idea in this article, I
  9212. am disturbed by the apparent impracticality of their code. They started with a
  9213. simple, real-world problem--representing a Go board--and evolved a very
  9214. complicated, non-portable solution involving templates and nested classes.
  9215. Straightforward approaches were possible. Why were they rejected?
  9216. I realize that C++ is a relatively new language and that questions of
  9217. programming style are not easily resolved. Magazine articles provide an
  9218. excellent means for programmers to study how another software designer
  9219. approaches a problem. In this case it seems to me that Giancola and Baker went
  9220. to a lot of work in order to achieve a slight improvement in notational
  9221. elegance and to avoid a far-fetched possibility of accidental code misuse.
  9222. This is not the kind of methodology that will solve the software crisis.
  9223. Have I missed something?
  9224. Sincerely,
  9225. Paul A. Cornelius
  9226. 1261 Fernside St.
  9227. Redwood City, CA 94061
  9228. Golly. Sounds to me like you favor functional notation over operator notation
  9229. if efficiency is an issue. Also, you favor using the basic C data types
  9230. instead of C++ class constructs if that too is more efficient. Those are the
  9231. arguments I expect from a C programmer arguing against the excesses of C++.
  9232. You do indeed raise some valid objections. But I like your last paragraph
  9233. best. I chose that article because it explores some serious style issues in
  9234. writing C++. (The C++ standards committee is still haggling over ways to make
  9235. overloaded subscript operators safer, for example. We're not alone.) The only
  9236. way we'll develop good style(s) in C++ is to try out a few. -- pjp
  9237. L. Baker and A. Giancola respond:
  9238. While the code details mentioned by Mr. Cornelius are reasonable and necessary
  9239. for peak efficiency, his stylistic comments did seem to miss the point. We
  9240. will address these in the order presented in his letter.
  9241. The published code does indeed compile under both the Comeau C++ 3.0, a
  9242. licensed AT&T Cfront, and Borland C++ 3.0. The article discussed differences
  9243. noted in the order of function calls in these implementations, which could not
  9244. have been observed if only Borland successfully compiled the code. The only
  9245. reason the nested class causes any problems at all is that current
  9246. implementations impose an additional restriction on the language not present
  9247. in the Annotated Reference Manual (ARM by Ellis and Stroustrup). Nested class
  9248. functions must be defined in the class declaration, which makes them inline.
  9249. The ARM allows them to be defined at global scope (ARM 9.7 p. 187). In any
  9250. case, the 'inline' directive is only a hint to the compiler (ARM 7.1.2), and
  9251. may be ignored. It appears to be a bug to fail to compile because of a hint
  9252. used for efficiency. The warning from Borland, that it failed to inline code
  9253. we did not explicitly ask it to inline, is at worst a petty annoyance. A
  9254. message that a hint has been ignored could be considered useful information.
  9255. When compilers are available that fully support the ARM version of C++, this
  9256. problem will disappear. We thought it better to illustrate the principle
  9257. rather than what the current implementation will tolerate. We have not
  9258. determined whether Cfront 3.0.1 fixes this bug.
  9259. The value of operator overloading is the ability to write things like A=B to
  9260. set A, and do so in a type-safe manner. We cited as references to our article
  9261. other publications that either used A.set (B) or used type coersion which
  9262. would not guarantee safety. This is not a minor point, in our opinion.
  9263. Operator overloading loses much of its value if one cannot use the assignment
  9264. operator to assign, or if one must sacrifice safety to do so. Similarly, we
  9265. desired to nest classes to hide one class from direct user access. If the
  9266. reader does not agree with this premise, then the reader would not see the
  9267. purpose to the article.
  9268. In a Go program, one may need to examine tens or hundreds of thousands of
  9269. positions. Storage is critical, even if bought at the cost of some code
  9270. complexity; i. e., reducing required storage by a factor of more than three,
  9271. would be extremely valuable. We did not emphasize this point in the article,
  9272. thinking it obvious, but with hindsight we should have.
  9273. If by software crisis Cornelius means the problem of producing reliable
  9274. software containing millions of lines of code, by large teams of programmers,
  9275. then we would claim to have made a small contribution by providing (in one
  9276. case) an intuitive notation for assignment (A=B) that is type safe in place of
  9277. methods that lack at least one of these features. C++, as presently designed,
  9278. is probably not the ultimate solution to the software crisis. As discussed in
  9279. Ellis and Stroustrup, it controls access but not visibility. Thus, C++ is not
  9280. truly suitable for programming in the large. For example, if two modules,
  9281. written by two teams, used the same variable name in different classes, there
  9282. would be conflicts even if only one of those variables were accessible (ARM
  9283. 11, p. 240).
  9284. Similarly, by including the statements:
  9285. #define private public
  9286. #define protected public
  9287. before any include directives in a file, a user could subvert any information
  9288. hiding! When any source for a class interface, even the private part, is
  9289. changed and recompiled, every user of that class must be recompiled! The
  9290. information hiding of the object- oriented programming paradigm is lost.
  9291. Contrast this with the information hiding of Ada. If an Ada package body (the
  9292. implementation) is recompiled, but the the package specification (external
  9293. interface) is unchanged, none of the users of that package need be recompiled,
  9294. merely relinked with the new body. Data private to the package is not only not
  9295. accessible outside the package, it is invisible. These features may produce
  9296. somewhat less-efficient code in some cases, but the gain in safety, and the
  9297. benefits for team programming in the large, should be obvious. It would be
  9298. desirable if C++, as it evolves, picks up some of these features.
  9299. Sincerely,
  9300. L. Baker and A. Giancola
  9301. Dear Mr. Plauger:
  9302. Last year I was an early purchaser of your book, The Standard C Library, with
  9303. the optional diskette containing the library source code. Recently I tried to
  9304. use the time functions and ran into some obvious bugs in the code (e.g.,
  9305. xgetzone.c's reformat function formats the timezone abbreviation backwards). I
  9306. called The C Users Bookstore order line to ask if an updated version of the
  9307. diskette was available and was told that it was, but the price quoted was $50
  9308. for an exchange with my original diskette, or $99 for the diskette alone. I
  9309. doubt if you get many takers on the latter offer given that the book and
  9310. diskette are currently advertised in the magazine for only $77.95.
  9311. Needless to say, it's a lot cheaper for me to just fix the problems that I
  9312. found and annotate my copy of the book, but I'm also curious why your pricing
  9313. strategy for the code diskette is so prohibitive. For example, in the same C
  9314. Users Bookstore advertisement the code diskette for Illustrated C adds only
  9315. $10 to the price of the book. If the diskette pricing is designed as an
  9316. alternative to strict enforcement of a licensing policy for commercial use of
  9317. the code, then I'm lodging a protest on behalf of those of us whose use of it
  9318. is strictly non-commercial.
  9319. Regards,
  9320. Brian Johnson
  9321.  
  9322. BJ Inc.
  9323. 109 Minna St, Ste 215
  9324. San Francisco, CA 94105
  9325. I agreed to a price increase for the code disk for a variety of reasons that I
  9326. won't recite here. The main reason was to bring the single-copy price more in
  9327. line with the quantity license terms. (You need such a license if you put the
  9328. code on a multiuser system, if you put it on a network, or if you distribute
  9329. copies of the source or unlinked binaries.) I knew the higher price would
  9330. annoy some people, particularly individuals who just want to play with the
  9331. code and don't want to have to type it in. I couldn't figure out how to please
  9332. such diverse constituencies, so I agreed to take the heat. -- pjp
  9333. P.J.:
  9334. Two chapters into The Standard C Library and my disillusionment with C is
  9335. nearly complete. Maybe now I can (belatedly) start using it as a tool instead
  9336. of a god.
  9337. About copyrights--I wish you had assigned the code (at least) in the book to
  9338. either the standards committees or the public domain. I know Prentice-Hall
  9339. needs their piece of the action, and I acknowledge that copyrights allow
  9340. greater flexibility than patents in derived works. But if, having read your
  9341. book, I implement a C library for, say, OS-9, how do I avoid having to put the
  9342. derivation notice in my code? The algorithms, data structures, and identifier
  9343. names are going to be derivative of the Standard and therefore reminiscent of
  9344. your code.
  9345. Joel Rees
  9346. 565 E. Mansfield Ave.
  9347. South Salt Lake City, UT 84106-1205
  9348. All a copyright covers is unique expression. If you can show that the choice
  9349. of algorithms, data structures, and/or identifier names is natural, or derives
  9350. directly from the C Standard, similarities to my code don't matter. Crib
  9351. anybody's code directly and you infringe, however. (I managed to replicate
  9352. UNIX under the watchful gaze of AT&T, and after having had access to UNIX
  9353. source, without infringing their copyright. It can be done.)
  9354. The problem I've stumbled into is the one I outlined in my previous response.
  9355. I started out simply to make "exemplary" code for all to see and enjoy. Before
  9356. I knew it, I had created another marketplace among people who need a Standard
  9357. C library. Now it seems that whatever I do, I upset somebody. -- pjp
  9358. Editor:
  9359. I recently purchased The C Toolbox book by William James L. Hunt (Addison
  9360. Wesley), with its "Ready To Run Programs in Turbo C, Microsoft C, and Quick
  9361. C." While quickly perusing the book, I noticed some serial-communication
  9362. programs that looked interesting. I committed myself to purchase the book.
  9363. After having purchased the book, I examined the programs only to find out that
  9364. they referred to other functions which were not available anywhere in the
  9365. book. They do, however, offer a source disk package which I suspect must have
  9366. these functions in it. I feel that the book, by itself, is completely useless
  9367. and is misrepresentative.
  9368. I feel that other potential buyers of this book should know this.
  9369. Bill Casey
  9370. Woodside, NY
  9371.  
  9372.  
  9373.  
  9374.  
  9375.  
  9376.  
  9377.  
  9378.  
  9379.  
  9380.  
  9381.  
  9382.  
  9383.  
  9384.  
  9385.  
  9386.  
  9387.  
  9388.  
  9389.  
  9390.  
  9391.  
  9392.  
  9393.  
  9394.  
  9395.  
  9396.  
  9397.  
  9398.  
  9399.  
  9400.  
  9401.  
  9402.  
  9403.  
  9404.  
  9405.  
  9406.  
  9407.  
  9408.  
  9409.  
  9410.  
  9411.  
  9412.  
  9413.  
  9414.  
  9415.  
  9416. Date Conversions
  9417.  
  9418.  
  9419. David Burki
  9420.  
  9421.  
  9422. David Burki is a Programmer/Analyst employed by PDA, Inc. His recent projects
  9423. include application development under OS/2 Presentation Manager as well as
  9424. MS-Windows and MS-DOS. You may contact him at PDA, Inc. 11600 College Blvd.,
  9425. Suite 100, Overland Park, KS 66210, (913) 469-8700.
  9426.  
  9427.  
  9428. Whether you are programming business applications, calculating the position of
  9429. celestial objects, or just trying to figure your personal biorhythm, the need
  9430. to perform arithmetic with calendar dates is crucial to computations involving
  9431. time. Calendar dates are used everywhere. Interest (and penalties) are
  9432. calculated, subscription renewal notices are mailed, and your physical,
  9433. emotional, and intellectual biorhythmic peaks and slumps are just a few things
  9434. based on dates. The problem is that there is no direct method to perform
  9435. arithmetic on calendar dates. The routines presented here will allow you to
  9436. determine the number of days between two dates, calculate the calendar date
  9437. some number of days before or after a given date, determine the day of the
  9438. week a certain date falls on, and determine whether a given year is a leap
  9439. year. In addition, the internal representation of these dates provide a
  9440. compact method for storing dates.
  9441.  
  9442.  
  9443. The Trouble with Dates
  9444.  
  9445.  
  9446. The problem with date computations stems from the way we represent calendar
  9447. dates. The Gregorian calendar (see the sidebar, "A Brief History of the
  9448. Calendar"), combines three different numbering units to create a calendar
  9449. date: the number of days in a month, the number of months in a year, and the
  9450. number of years since the beginning of the Christian era. Each of these
  9451. "digits" are of a different base. Years are base 10, months are base 12, and
  9452. days are either base 28, 29, 30 or 31. To easily do arithmetic on a calendar
  9453. date, it must first be converted into a single number. Converting a calendar
  9454. date into a single number yields a unique Julian Day number for that date.
  9455. Notice I said Julian Day, not Julian date. A Julian Day number is a method
  9456. astronomers use to identify dates. The Julian Day method was developed by
  9457. Joseph Scaliger about 1577 A.D. as a means by which all days within recorded
  9458. time would be assigned consecutive numbers (Harvey 1983). Contrary to popular
  9459. belief, Scaliger named his method after his father, not Julius Caesar. A
  9460. Julian date, on the other hand, is a calendar date based on the Julian
  9461. calendar.
  9462.  
  9463.  
  9464. Conversions
  9465.  
  9466.  
  9467. At the heart of date manipulation is the ability to convert a calendar date to
  9468. a Julian Day number and back. Converting a Gregorian date to a Julian Day
  9469. number is accomplished by the function ToJul in Listing 1. ToJul takes the
  9470. month, day, and year as parameters and returns the Julian Day number as a long
  9471. integer. The constants used in the function were derived by the algorithm's
  9472. originator and are critical to being able to successfully convert the Julian
  9473. Day number back to a Gregorian date. Converting a Julian Day number back to a
  9474. Gregorian date is accomplished by the function FromJul (Listing 1). Arguments
  9475. for FromJul are the Julian Day number, and pointers to the variables which
  9476. will hold the resultant month, day, and year. With only these two functions it
  9477. is possible to subtract two dates and add or subtract some number of days to a
  9478. given date. Once a Gregorian date has been converted to a Julian Day number,
  9479. it is possible to determine the day of the week that date falls on. The
  9480. function DayOfWeek (Listing 1) returns an integer ranging from 0 to 7
  9481. representing Monday through Sunday, simply by computing the remainder of the
  9482. Julian Day number divided by 7. The IsLeapYear function in Listing 1 returns a
  9483. boolean value which indicates whether the year passed as a parameter is a leap
  9484. year or not.
  9485. If you have the need to know the phase of the moon on a given date, an
  9486. approximation can be obtained using the Julian Day number. If the Julian Day
  9487. number divided by 29.530588 yields a decimal remainder of or proximate to
  9488. 0.83, that day is a full moon. A decimal remainder proximate to 0.33 indicates
  9489. a new moon (Harvey 1983).
  9490. The conversion routines presented here have been tested using a broad range of
  9491. dates. Testing has included round tripping every valid date from 4000 B.C. to
  9492. beyond 5000 A.D., as well as random checking of the day of the week against
  9493. published calendars. Use caution with B.C. dates. The year immediately
  9494. preceding 1 A.D. is 1 B.C. There is no year 0. Years prior to 1 A.D. are
  9495. numbered beginning at -1.
  9496. I would like to extend a special thanks to Jeff Betts, president of Creative
  9497. Programming for providing the references to the algorithms used to create the
  9498. Julian Day routines in the Vitamin C Library. Without his assistance, the
  9499. research would have been much more painful.
  9500. You may not need routines to manipulate dates every day, but each one of us
  9501. tries to build a software arsenal which we can use to conquer the daily
  9502. challenges. These date routines are one more weapon to be added to your cache.
  9503.  
  9504.  
  9505. Bibliography
  9506.  
  9507.  
  9508. Fliegl, Henry F. and Van Flanders, Thomas C. "A machine algorithm for
  9509. processing dates." Communications of the ACM, Volume 11, Number 10, October
  9510. 1968, page 657.
  9511. Friedman, Howard S. 1989. The Development of the Gregorian Calendar. (This is
  9512. a text file (kalend.zip) found in the IBM Programmers Forum on CompuServe.)
  9513. Harvey, O. L. 1983. Calendar Conversions by Way of the Julian Day Number.
  9514. Philadelphia, PA: American Philosophical Society.
  9515. A Brief History of the Calendar
  9516. The Gregorian calendar in use today is based on a calendar created by the
  9517. Egyptian astronomer, Sosigenes, at the direction of Julius Caesar. This
  9518. calendar was placed into effect the year we now call 45 B.C. (Friedman 1989).
  9519. Sosigenes calculated the length of the tropical year (equinox to equinox) at
  9520. 365.25 days, and proposed that the normal year have 365 days, and an extra day
  9521. be inserted every fourth year to make up for the extra one quarter day each
  9522. year. The actual length of the tropical year is 365.2421987 days calculated
  9523. with reference to a cesium atomic clock in 1956 by the Harvard University
  9524. astronomer, Simon Newcomb (Fliegl 1968). The effect of Sosigenes tropical year
  9525. being shorter than the actual tropical year becomes apparent after several
  9526. centuries. By the time of Pope Gregory XIII, in the 16th century, the
  9527. difference between the calendar vernal equinox and the actual vernal equinox
  9528. amounted to 10 days. Under Pope Gregory, Christopher Clavis developed the
  9529. rules (based on calculations made by astronomer Aloysius Lilus) for the
  9530. Gregorian calendar. This is the calendar used by most of the nations of the
  9531. world today. Each calendar year would be 365 days in length, and an extra day
  9532. would be inserted every fourth year (any year evenly divisible by 4) except in
  9533. centennial years (years evenly divisible by 100) unless the centennial year
  9534. was also evenly divisible by 400. See Table 1 if that was a bit confusing.
  9535. Since the current calendar date was off by 10 days, Pope Gregory ordered that
  9536. the final day of the era of the Julian calendar would be Thursday, October 4,
  9537. 1582 A.D. The following day would be Friday, October 15, 1582 A.D., The first
  9538. day of the era of the Gregorian calendar (Friedman 1989). This edict took
  9539. effect almost immediately in the Papal States, but it took many years for
  9540. other European nations to adopt the new calendar.
  9541. Table 1 Leap year table
  9542. year mod 4 == 0 year mod 100 == 0 year mod 400 == 0 Is leap year
  9543. -------------------------------------------------------------------
  9544.  T T T T
  9545.  T T F F
  9546.  T F Don't care T
  9547.  F Don't care Don't care F
  9548.  
  9549. Listing 1 Calendar conversion routines
  9550. /*
  9551. ; #defines
  9552. */
  9553. #define BOOL int
  9554. #define TRUE 1
  9555. #define FALSE 0
  9556.  
  9557. /*
  9558. ; Function prototypes
  9559. */
  9560. long ToJul( int, int, int );
  9561. void FromJul( long, int *, int *, int * );
  9562.  
  9563. BOOL IsLeapYear( int );
  9564. int DayOfWeek( long );
  9565.  
  9566. /*
  9567. ;
  9568. ; USAGE: long ToJul( int, int, int );
  9569. ; int month; Gregorian calendar date month
  9570. ; int day; Gregorian calendar date day
  9571. ; int year; Gregorian calendar date year
  9572. ;
  9573. ; DESCRIPTION: Converts Gregorian calendar date to a
  9574. ; julian day number.
  9575. ;
  9576. ; ALGORITHM: adaptation of the FORTRAN code used to
  9577. ; implement the algorithm presented by
  9578. ; H. Fliegl and T. Van Flanders,
  9579. ; Communications of the ACM, Vol. 11,
  9580. ; No. 10, October, 1968, page 657.
  9581. ;
  9582. ;RETURNS: long int representing the julian day number
  9583. */
  9584. long ToJul( int month, int day, int year )
  9585. {
  9586. long jul_day, // returned julian day
  9587. lmonth = (long)month, // cast them once
  9588. lday = (long)day, // instead of
  9589. lyear = (long)year; // each usage
  9590.  
  9591. jul_day = lday - 32075L + 1461L *
  9592. (lyear + 4800 + (lmonth - 14L) / 12L) /
  9593. 4L + 367L * (lmonth - 2L - (lmonth - 14L) /
  9594. 12L * 12L) / 12L - 3L * ((lyear + 4900L +
  9595. (lmonth - 14L) / 12L) / 100L) / 4L;
  9596.  
  9597. return jul_day;
  9598. }
  9599.  
  9600. /*
  9601. ;
  9602. ; USAGE: void FromJul( long, int *, int *, int * );
  9603. ; long jul_day; Julian Day number to convert
  9604. ; int *month; Gregorian calendar month (return)
  9605. ; int *day; Gregorian calendar day (return)
  9606. ; int *year; Gregorian calendar year (return)
  9607. ;
  9608. ; DESCRIPTION: Converts Julian Day number to its
  9609. ; corresponding Gregorian calendar date
  9610. ; components.
  9611. ;
  9612. ; ALGORITHM: adaptation of the FORTRAN code used to
  9613. ; implement the algorithm presented by
  9614. ; H. Fliegl and T. Van Flanders,
  9615. ; Communications of the ACM, Vol. 11,
  9616. ; No. 10, October, 1968, page 657.
  9617. ;
  9618. ; RETURNS: Nothing, updates variables pointed to by
  9619. ; year, month and day.
  9620. */
  9621. void FromJul( long jul_date,
  9622.  
  9623. int *month,
  9624. int *day,
  9625. int *year )
  9626. {
  9627. long t1, // temporary work variables
  9628. t2,
  9629. yr,
  9630. mo;
  9631.  
  9632. t1 = jul_date + 68569L;
  9633. t2 = 4L * t1 / 146097L;
  9634. t1 = t1 - (146097L * t2 + 3L) / 4L;
  9635. yr = 4000L * (t1 + 1) / 1461001L;
  9636. t1 = t1 - 1461L * yr / 4L + 31;
  9637. mo = 80L * t1 / 2447L;
  9638. *day = (int)(t1 - 2447L * mo / 80L);
  9639. t1 = mo / 11L;
  9640. *month = (int)(mo + 2L - 12L * t1);
  9641. *year = (int){100L * (t2 - 49L) + yr + t1);
  9642. }
  9643.  
  9644. /*
  9645. ; USAGE: int IsLeapYear( int );
  9646. ; int year; year to check
  9647. ;
  9648. ; DESCRIPTION: Determines whether a given year is a
  9649. ; leap year.
  9650. ;
  9651. ; RETURNS:
  9652. ; TRUE (non-zero) year is a leap year
  9653. ; FALSE (zero) year is not a leap year
  9654. */
  9655. BOOL IsLeapYear( int year )
  9656. {
  9657. return (year % 4 == 0 &&
  9658. (year % 100 != 0 year % 400 == 0))
  9659. }
  9660.  
  9661. /*
  9662. ; USAGE: int DayOfWeek( long );
  9663. ; long jul_day; julian day number
  9664. ;
  9665. ; DESCRIPTION: Determine the day of the week a given
  9666. ; julian day number falls on.
  9667. ;
  9668. ; RETURNS: 0 ... 7, where 0 is a Monday, 7 is a Sunday.
  9669. */
  9670. int DayOfWeek( long jul_day )
  9671. {
  9672. return (int)(jul_day % 7L);
  9673. }
  9674.  
  9675. /* End of File */
  9676.  
  9677.  
  9678.  
  9679.  
  9680.  
  9681.  
  9682.  
  9683.  
  9684.  
  9685.  
  9686.  
  9687.  
  9688.  
  9689.  
  9690.  
  9691.  
  9692.  
  9693.  
  9694.  
  9695.  
  9696.  
  9697.  
  9698.  
  9699.  
  9700.  
  9701.  
  9702.  
  9703.  
  9704.  
  9705.  
  9706.  
  9707.  
  9708.  
  9709.  
  9710.  
  9711.  
  9712.  
  9713.  
  9714.  
  9715.  
  9716.  
  9717.  
  9718.  
  9719.  
  9720.  
  9721.  
  9722.  
  9723.  
  9724.  
  9725.  
  9726.  
  9727.  
  9728.  
  9729.  
  9730.  
  9731.  
  9732.  
  9733.  
  9734.  
  9735.  
  9736.  
  9737.  
  9738.  
  9739.  
  9740.  
  9741.  
  9742.  
  9743.  
  9744.  
  9745.  
  9746. Real-Number Approximation for Real Programmers
  9747.  
  9748.  
  9749. Mark Gingrich
  9750.  
  9751.  
  9752. Mark currently works on real-time, embedded software in the medical device
  9753. industry. Although trained in physics and computer science, he occasionally
  9754. "moonlights" as an amateur astronomer. He can be reached at 355 Estabrook St.,
  9755. Apt. 403, San Leandro, CA 94577.
  9756.  
  9757.  
  9758.  
  9759.  
  9760. Introduction
  9761.  
  9762.  
  9763. Real-world numerical calculations rarely, if ever, call for floating-point
  9764. precision to the gazillionth digit. Indeed, do you really need to know your
  9765. automobile's gas mileage to one-part-per-billion resolution?
  9766. Of course, such practicalities influence the design of device software. And
  9767. living at one computational extreme are those lean, mean
  9768. microcontroller-embedded machines which often abstain from floating-point
  9769. code. Yet it is frequently the case that an oddball transducer scaling factor
  9770. or proportionality constant is required--oddball in refusing to conform to our
  9771. pristine integer-only universe. Hence the need to link in extra bytes from the
  9772. floating-point math library into what is, all too often, a nearly full ROM
  9773. space.
  9774. This article describes a simple utility that computes integer-ratio
  9775. approximations for floating-point constants. Code requiring only a few
  9776. instances of floating point for scaling may instead use nearly equivalent
  9777. integer ratios. The result is speed and size benefits by avoiding
  9778. floating-point routines altogether.
  9779. For example, instead of scaling a quantity by, say, 0.66666666, you could
  9780. multiply by 2, then divide by 3. But this is a trivial case. A much more
  9781. interesting question is: What integer ratios best approximate those
  9782. perennially popular (and irrational) constants, such as the square root of 2,
  9783. or e, or p?
  9784.  
  9785.  
  9786. Approximating Pi by the Slice
  9787.  
  9788.  
  9789. Let's try p. The first few decimal digits are:
  9790. 3.14159265358979323846...
  9791. Chopping after the first two digits suggests the crude integer-ratio
  9792. approximation 31/10. Reality differs by little more than one percent in this
  9793. instance.
  9794. But maybe that's not accurate enough. So tack on another digit. p then
  9795. approximates to 314/100 (157/50 when reduced to lowest terms), just 0.05
  9796. percent from the truth. Continuing in this fashion for four more iterations
  9797. yields the ratio:
  9798. 3141593/1000000
  9799. with a mere 0.00001 percent error. Not bad for such a straightforward (I'll
  9800. call it radix-10) method. Yet p is better approximated without so many digits.
  9801. Although not immediately apparent, the ratio 355/113 is superior to
  9802. 3141593/1000000
  9803. There are two compelling reasons why. First, 355/113 is nearer to p. Second,
  9804. it has a smaller numerator (355 versus 3141593, the latter not even within
  9805. 16-bit number range). Here is a crucial computational advantage. When a ratio
  9806. is applied as a scaling factor, a smaller numerator permits larger
  9807. multiplicands without overflow hassles.
  9808. So 355/113 is better. But is it just a freakish occurrence? Table 1 implies
  9809. otherwise. It shows a sequence of radix-10 rational approximations for p
  9810. juxtaposed with a few "hand-picked" ratios (355/113 being one). Each of the
  9811. latter provides better accuracy with smaller integers--more accuracy per bit.
  9812. Certainly it's not difficult to devise successively more accurate radix-10
  9813. ratios by inspection (31/10, 314/100, 3142/1000,...), but discovering these
  9814. preferred "hand-picked" ratios is more subtle. There is a way to do so. It
  9815. employs a mathematical device known as a continued fraction.
  9816.  
  9817.  
  9818. Fractions, to be Continued
  9819.  
  9820.  
  9821. The continued-fraction form is a real number's alter ego. I'll intuitively
  9822. illustrate the concept by creating a continued fraction for p. Take p's
  9823. decimal-digit sequence and break it into a sum of two components--the integer
  9824. part and the fractional part.
  9825. 3 + 0.1415926535897932...
  9826. Ignoring the fractional part for a moment, I could quit right here and claim
  9827. that p is equal to 3--a first-cut estimate. Instead, I'll show a bit more
  9828. initiative and improve the approximation. Rewrite the fractional part as
  9829. follows:
  9830. Click Here for Equation
  9831. Fixating on the denominator (7.06525133059310477...), I note again that I'm
  9832. dealing with a real number. So break it into a sum of integer and fractional
  9833. parts:
  9834. Click Here for Equation
  9835. Like before, I may ignore the fractional component and stop at this point,
  9836. leaving the result:
  9837. Click Here for Equation
  9838. Or, yet again, I can improve the approximation. Rewrite the fractional
  9839. denominator term:
  9840. Click Here for Equation
  9841. The pattern to this algorithm now should be conspicuous. Break the bottom-most
  9842. denominator into integer and fractional parts:
  9843. Click Here for Equation
  9844. Ignoring the fractional component, I see that the approximation becomes:
  9845. Click Here for Equation
  9846. For good measure, I'll do just one more iteration of this process:
  9847. Click Here for Equation
  9848. And so on, and so on. These first few approximations should look familiar.
  9849. They are the "hand-picked" ratios of Table 1. And I could continue generating
  9850. still better ratios seemingly forever, or stop when I achieve the desired
  9851. accuracy.
  9852. This construct of nested fractions:
  9853. Click Here for Equation
  9854.  
  9855. is called a simple continued fraction. It's "simple" because each numerator is
  9856. unity. Given any nonnegative real number, R, the ais result by fiat of
  9857. Algorithm A (Figure 1). Thus the ais needed to make p are:
  9858. [3, 7, 15, 1, 292, 1, 1, 1, 2, 1,
  9859. 3, 1, ...]
  9860. which is not as instantly recognizable as 3.14159..., but just as valid.
  9861. Experimenting with this algorithm and a judicious assortment of real numbers
  9862. reveals several notable continued-fraction characteristics:
  9863. Rational numbers are expressible with a finite number of ais (for instance,
  9864. 7/4 = 1.75 = [1, 1, 3]).
  9865. Irrational numbers require infinitely many ais.
  9866. The approximation of an irrational number improves with each successive ai,
  9867. always alternating between a-bit-too-large and a-bit-too-small values.
  9868. To handle negative real numbers, treat the value as if it were positive; then
  9869. negate a0 and the top-most numerator.
  9870. Algorithm A is simple and intuitive--but flawed in actual practice. The gotcha
  9871. is the finite floating-point representation of real numbers. After a dozen or
  9872. so iterations, erroneous ais are produced. The algorithm also can mess up
  9873. after only a few iterations. The comparison test at the top of the loop often
  9874. flubs because round-off prevents the fractional result from reaching zero
  9875. exactly. A further annoyance arises from having to collapse the continued
  9876. fraction to a simple fraction for use as a scaling factor.
  9877. Algorithm B (Figure 2) sidesteps these pitfalls. It is a recurrence method
  9878. using integers only. First you must decide how precisely--how many digits--you
  9879. want to specify the real number of interest, then express it as a radix-10
  9880. ratio. For example, 3.1416 is 31416/10000. The algorithm starts out by
  9881. producing a crude estimate, then grinds out additional ratios, p[i]/q[i], each
  9882. a simple fraction in lowest terms and more accurate than the previous one,
  9883. until the initial radix-10 ratio is achieved. (Algorithm mavens will note a
  9884. similarity to Euclid's algorithm. It's not a coincidence. See Knuth, 1981.)
  9885.  
  9886.  
  9887. Real-World Usage
  9888.  
  9889.  
  9890. Listing 1 details an implementation of Algorithm B in ANSI C. This short
  9891. utility, called fp2ratio, accepts a floating-point number as a command-line
  9892. argument and displays the ever-improving ratio approximations in succession.
  9893. Output for a 9-digit representation of p appears in Figure 3.
  9894. But before applying one of these ratios as a scaling factor, a caveat. Keep in
  9895. mind the round-off correction needed before division by the denominator
  9896. (denom). Positive values are scaled thusly:
  9897. Click Here for Equation
  9898. So much for the theory; let's try a practical application. Imagine a
  9899. relatively noiseless sinusoidal signal digitized to 10-bit precision. The
  9900. analog-to-digital converter scaling is 1 millivolt per least-significant bit.
  9901. You wish to display, to three decimal-digit precision, the signal's
  9902. root-mean-square (RMS) value in millivolts. Here's one strategy. Take the
  9903. difference between the minimum and maximum signal excursions--the peak-to-peak
  9904. value--then convert to RMS by scaling:
  9905. Click Here for Equation
  9906. The peak-to-peak value is guaranteed never to exceed decimal 1,023. (It's a
  9907. 10-bit result, remember.) So declare it an unsigned 16-bit word. As a guard
  9908. against 16-bit overflow during scaling, the selected ratio's numerator must
  9909. not exceed 64. Figure 4 shows fp2ratio's output for the scaling factor
  9910. sqrt2/4. The best ratio honoring our overflow constraint is 35/99.
  9911. Implementing the round-off correction, the scaling expression to appear in
  9912. code is:
  9913. Click Here for Equation
  9914.  
  9915.  
  9916. Conclusion
  9917.  
  9918.  
  9919. Another possible application of this approach is the "clean up" of numerical
  9920. values due to loss of fidelity. Take, for example, the computation of a matrix
  9921. inverse, where the matrix elements are known rationals. You crunch the
  9922. numbers. You get your inverse--along with unavoidable round-off and truncation
  9923. effects. Yes, the answer is very close, but it's not an exact rational result.
  9924. A bit of post-processing, a la the methods described, may help recover the
  9925. true rational answer.
  9926. Continued fractions, it turns out, are applied well beyond the ratio
  9927. approximation of real numbers. Functions, too, may be approximated, but with
  9928. polynomials replacing the integers in the numerators and denominators. This is
  9929. an alternative to a power-series approximation (Press, 1986).
  9930. But as handy as they are, continued fractions haven't achieved household-name
  9931. status. The author Petr Beckmann has lamented that they are, "part of the
  9932. 'lost mathematics,' the mathematics now considered too advanced for high
  9933. school and too elementary for college." (Beckmann 1971)
  9934. Real programmers are inveterate collectors of tricks and techniques. Consider
  9935. adding continued fractions to your algorithm toolbox.
  9936. References
  9937. Beckmann, Petr. 1971. A History of Pi. New York: Dorset Press.
  9938. Knuth, Donald E. 1981. The Art of Computer Programming. Volume 2:
  9939. Seminumerical Algorithms., 2nd ed. Reading, MA: Addison-Wesley.
  9940. Olds, C.D. 1963. Continued Fractions. New York: Random House, New Mathematical
  9941. Library.
  9942. Press, William H., Flannery, Brian P., Teukolsky, Saul A., Vetterling, William
  9943. T. 1986. Numerical Recipes. Cambridge, England: Cambridge University Press.
  9944. Sinnott, Roger W. January, 1989. "Continued Fractions and the Sky". Sky and
  9945. Telescope. 80-82.
  9946. Baseball, Mom, and Rational Pi
  9947. Divining the ratio of a circle's circumference to its diameter has been an
  9948. intellectual challenge for nearly four millennia. There's even an implied
  9949. specification for p in the Old Testament (1 Kings 7:23 and 2 Chronicles 4:2):
  9950. Then he made the molten sea; it was round, ten cubits from brim to brim, and
  9951. five cubits high, and a line of thirty cubits measured in circumference. 
  9952. Gauged empirically at first--perhaps with lengths of rope--then attacked
  9953. analytically over the years with increasing mathematical sophistication, the
  9954. noble goal was to find p's exact integer ratio. How close did they get? As the
  9955. 1600s commenced, p had been revealed to well over 30 decimal places. In the
  9956. early 1700s, it was known to over 100 decimal places--with no sign of
  9957. repeating digits. (A repeating digit sequence is the telltale clue of
  9958. rationality.)
  9959. Finally, in the 1760s, Lambert proved p to be an irrational number. A little
  9960. more than century later, it was proved also to be transcendental, a special
  9961. breed of irrational. (Transcendental numbers cannot be solutions to algebraic
  9962. equations with rational coefficients.)
  9963. But despite these breakthroughs, mathematical truth soon after experienced an
  9964. amusing episode of American democracy in action. In 1897, the state of
  9965. Indiana's House of Representatives voted 67 to 0, proclaiming p equal to 16/5,
  9966. exactly! The bill was proposed by a physician and amateur mathematician, a Dr.
  9967. Edwin Goodwin, who claimed this "discovery." And being a loyal Hoosier, he
  9968. offered Indiana free use of the result--all others would have to pay
  9969. royalties! Fortunately, last-minute lobbying by an alert mathematics professor
  9970. kept the bill from making it through the Indiana Senate. 
  9971. Figure 1 Given a real number, R, this algorithm computes the ais for a simple
  9972. continued fraction.
  9973. Algorithm A:
  9974.  
  9975.  x = R;
  9976.  i = O;
  9977.  a[ i ] = integer_part( x );
  9978.  
  9979.  while ( fractional_part( x ) != 0.0 )
  9980.  {
  9981.  x = 1.0 / fractional_part( x );
  9982.  i = i+1;
  9983.  a[ i ] = integer_part( x );
  9984.  }
  9985. Figure 2 Given a real number, R, this algorithm produces a sequence of simpler
  9986. -- but always improving -- ratio approximations.
  9987. Algorithm B:
  9988.  
  9989.  
  9990.  n = radix10_numerator( R );
  9991.  d = radix10_denominator( R );
  9992.  p[ 0 ] = 0;
  9993.  p[ 1 ] = 1;
  9994.  q[ 0 ] = 1;
  9995.  q[ 1 ] = 0;
  9996.  i = 2;
  9997.  
  9998.  do
  9999.  {
  10000.  p[ i ] = ( n div d ) * p[ i - 1] + p[ i - 2 ];
  10001.  q[ i ] = ( n div d ) * q[ i - 1] + q[ i - 2 ];
  10002.  i = i + 1;
  10003.  temp = d;
  10004.  d = n modulo d;
  10005.  n = temp;
  10006.  } while ( n modulo d != 0 );
  10007. Figure 3 A nine-digit representation of p is displayed as a sequence of
  10008. improving ratio approximations.
  10009. fp2ratio 3.14159265
  10010.  
  10011. 3.14159265:
  10012.  
  10013.  3 / 1 -4.5%
  10014.  22 / 7 +0.040%
  10015.  333 / 106 -0.0026%
  10016.  355 / 113 +0.0000086%
  10017.  102573 / 32650 -0.000000022%
  10018.  102928 / 32763 +0.0000000078%
  10019.  308429 / 98176 -0.0000000021%
  10020.  411357 / 130939 +0.00000000040%
  10021.  1542500 / 490993 -0.000000000094%
  10022.  1953857 / 621932 +0.000000000010%
  10023.  15219499 / 4844517 -0.00000000000033%
  10024.  62831853 / 20000000 Exactly!
  10025. Figure 4 sqrt2/4 is truncated to nine digits and rationally approximated.
  10026. fp2ratio .353553391
  10027.  
  10028. 0.353553391:
  10029.  
  10030.  1 / 2 +41.4%
  10031.  1 / 3 -5.7%
  10032.  5 / 14 +1.0%
  10033.  6 / 17 -0.17%
  10034.  29 / 82 +0.030%
  10035.  35 / 99 -0.0051%
  10036.  169 / 478 +0.00088%
  10037.  204 / 577 -0.00015%
  10038.  985 / 2786 +0.000026%
  10039.  1189 / 3363 -0.0000045%
  10040.  5741 / 16238 +0.00000064%
  10041.  6930 / 19601 -0.00000025%
  10042.  19601 / 55440 -0.000000015%
  10043.  104935 / 296801 -0.0000000021%
  10044.  124536 / 352241 +0.00000000062%
  10045.  354007 / 1001283 -0.00000000018%
  10046.  478543 / 1353524 +0.000000000024%
  10047.  2746722 / 7768903 -0.0000000000027%
  10048.  3225265 / 9122427 +0.0000000000013%
  10049.  5971987 / 16891330 -0.00000000000050%
  10050.  
  10051.  9197252 / 26013757 +0.00000000000014%
  10052.  24366491 / 68918844 -0.000000000000016%
  10053.  82296725 / 232770289 +0.0000000000000012%
  10054.  353553391 / 1000000000 Exactly!
  10055. Table 1 Comparing the accuracy of radix-10 versus "hand-picked" rational
  10056. approximations of p. (Overlined digits signify a repeating sequence.)
  10057.  Radix-10 "Hand Picked" Value Percent
  10058.  Ratio Ratio Error
  10059.  
  10060.  3 / 1 3 -4.5
  10061.  
  10062.  31 / 10 3.1 -1.3
  10063.  
  10064.  314 / 100 3.14 -0.051
  10065.  __________LINEEND____
  10066.  22 / 7 3.142857 +0.040
  10067.  
  10068.  3142 / 1000 3.142 +0.013
  10069.  
  10070.  333 / 106 3.1415094... -0.0026
  10071.  
  10072.  31416 / 10000 3.1416 +0.00023
  10073.  
  10074.  314159 / 100000 3.14159 0.000084
  10075.  
  10076. 3141593 / 1000000 3.141593 +0.000011
  10077.  
  10078.  355 / 113 3.1415929... +0.0000085
  10079.  
  10080. 31415927 / 10000000 3.1415927 +0.0000015
  10081. Table 2 Some notable (if not perverse) rational approximations of p.
  10082.  When By Whom Ratio Value Percent
  10083.  Error
  10084.  
  10085. c. 2000 B.C. Babylonians 25 / 8 3.125 -0.53
  10086.  
  10087. c. 2000 B.C. Egyptians 256 / 81 3.16049... +0.60
  10088.  
  10089.  c. 550 B.C. Old Testament,
  10090.  1 Kings 7:23 3 / 1 3 -4.5
  10091.  
  10092.  c. 220 B.C. Archimedes of Syracuse 211875 / 67441 3.14163... +0.0013
  10093.  _____LINEEND____
  10094.  c. 160 A.D. Ptolemy of Alexandria 377 / 120 3.1416 +0.0024
  10095.  
  10096.  263 A.D. Liu Hui (China) 157 / 50 3.14 -0.051
  10097.  
  10098.  c. 500 A.D. Aryabhata (India) 3927 / 1250 3.1416 +0.00023
  10099. __ ______LINEEND____
  10100.  1220 A.D. Fibonacci (Italy) 864 / 275 3.1418 +0.0072
  10101.  
  10102.  1573 A.D. Valentinus Otho
  10103.  (Prussia) 355 / 113 3.14159... +0.0000085
  10104.  
  10105.  1897 A.D. Indiana House of 16 / 5 3.2 +1.9
  10106.  Representatives (U.S.A.)
  10107.  
  10108. Listing 1 fp2ratio.c -- an implementation of Algorithm B in Figure 2 in ANSI C
  10109. /******************************************************
  10110. *
  10111.  
  10112. * fp2ratio.c
  10113. *
  10114. * Floating-point number to ratio approximation.
  10115. *
  10116. * Usage: fp2ratio number
  10117. *
  10118. * Compiler: Microsoft C 6.0 using inline
  10119. * floating-point emulator (option /FPi).
  10120. *
  10121. ******************************************************/
  10122.  
  10123. #include <stdio.h>
  10124. #include <stdlib.h>
  10125. #include <math.h>
  10126.  
  10127. /* Maximum argument significant digits. */
  10128. #define MAX_SIG_DIGITS 9
  10129.  
  10130. #define ARRAY_SIZE 2
  10131. #define I ( i ) % ARRAY_SIZE
  10132. #define I MINUS_1 ( i - 1 ) % ARRAY_SIZE
  10133. #define I_MINUS_2 ( i ) % ARRAY_SIZE
  10134.  
  10135. typedef unsigned long Ulong;
  10136. typedef long double Ldouble;
  10137.  
  10138. int main( int argc, char **argv )
  10139. {
  10140. int i, dec_digits, sign;
  10141. Ulong n0, n, d0, d, temp;
  10142. Ulong p[ARRAY_SIZE] = { 0, 1 };
  10143. Ulong q[ARRAY_SIZE] = { 1, 0 };
  10144. double x;
  10145. Ldouble percent_err;
  10146.  
  10147. /* Check for one command-line argument. */
  10148. if ( argc != 2 )
  10149. {
  10150. fprintf(stderr, "Usage: %s number\n", argv[0);
  10151. exit( EXIT_FAILURE );
  10152. }
  10153. else /* argc == 2 */
  10154. x = strtod( argv[1], (char **) NULL );
  10155.  
  10156. /* Handle zero and negative arguments. */
  10157. if ( x < 0.0 )
  10158. {
  10159. sign = -1;
  10160. x = -x;
  10161. }
  10162. else if ( x == 0.0 )
  10163. {
  10164. puts( "\n0:\n" );
  10165. puts( " 0 / 1 Exactly!" );
  10166. exit( EXIT_SUCCESS );
  10167. }
  10168. else /* x > 0.0 */
  10169. sign = 1;
  10170.  
  10171.  
  10172. /* Check for out-of-range arguments. */
  10173. if ( x >= pow( 10.0, (double) MAX_SIG_DIGITS ) )
  10174. {
  10175. fprintf( stderr, "%s: Magnitude is", argv[1] );
  10176. fprintf( stderr, " too large.\n" );
  10177. exit( EXIT_FAILURE );
  10178. }
  10179. else if ( x <=
  10180. pow( 10.0, (double) -MAX_SIG_DIGITS) / 2.0 )
  10181. {
  10182. fprintf( stderr, "%s: Magnitude is", argv[1] );
  10183. fprintf( stderr, "too small.\n" );
  10184. exit( EXIT_FAILURE );
  10185. }
  10186.  
  10187. /* Determine the argument's radix-10 ratio. */
  10188. d0 = (Ulong) pow( 10.0, (double) MAX_SIG_DIGITS -
  10189. ((x < 1.0) ? 0.0 : floor( 1.0 + log10( x ))));
  10190. n0 = (Ulong) ( ( x * (double) d0 ) + 0.5 );
  10191. printf( "\n%.*g:\n\n", MAX_SIG_DIGITS,
  10192. (double) n0 / (double) d0 );
  10193.  
  10194. /* Iteratively determine integer ratios. */
  10195. for ( i = 2, d = d0, n = n0 ; ;
  10196. i++, temp = d, d = n % d, n = temp )
  10197. {
  10198. p[I] = n / d * p[I_MINUS_1] + p[I_MINUS_2];
  10199. q[I] = n / d * q[I_MINUS_1] + q[I_MINUS_2];
  10200.  
  10201. /* Print ratios with non-zero numerators. */
  10202. if ( p[I] != 0 )
  10203. {
  10204. printf("%11ld / %-10lu", sign * p[I], q[I]);
  10205. if ( n % d == 0 )
  10206. {
  10207. printf(" Exactly!\n" );
  10208. break;
  10209. }
  10210.  
  10211. /*
  10212. * Compute the ratio's percent error
  10213. * (taking care to avoid significance
  10214. * loss when subtracting nearly equal
  10215. * values):
  10216. *
  10217. * percent error =
  10218. *
  10219. *
  10220. * 100 * ( p[i] * d0 - q[i] * n0 )
  10221. * -----------------------------------
  10222. * q[I] * n0
  10223. *
  10224. * Display in %f format with at least two
  10225. * significant digits.
  10226. */
  10227. percent_err = (Ldouble)p[I] * (Ldouble)d0;
  10228. percent_err -= (Ldouble)q[I] * (Ldouble)n0;
  10229. percent_err *= 100.0L;
  10230. percent_err /= (Ldouble)q[I] * (Ldouble)n0;
  10231.  
  10232.  
  10233. dec_digits =
  10234. ( fabs( (double) percent_err ) >= 10.0 ) ?
  10235. 1 : 1 + ( int ) ( fabs( floor(
  10236. log10( fabs((double) percent_err )))));
  10237. printf( "%+*.*Lf%%\n", dec_digits + 6,
  10238. dec_digits, percent_err );
  10239. }
  10240. }
  10241.  
  10242. exit( EXIT_SUCCESS );
  10243. }
  10244. /* End of File */
  10245.  
  10246.  
  10247.  
  10248.  
  10249.  
  10250.  
  10251.  
  10252.  
  10253.  
  10254.  
  10255.  
  10256.  
  10257.  
  10258.  
  10259.  
  10260.  
  10261.  
  10262.  
  10263.  
  10264.  
  10265.  
  10266.  
  10267.  
  10268.  
  10269.  
  10270.  
  10271.  
  10272.  
  10273.  
  10274.  
  10275.  
  10276.  
  10277.  
  10278.  
  10279.  
  10280.  
  10281.  
  10282.  
  10283.  
  10284.  
  10285.  
  10286.  
  10287.  
  10288.  
  10289.  
  10290.  
  10291.  
  10292.  
  10293.  
  10294.  
  10295. Sorting Networks
  10296.  
  10297.  
  10298. Frederick Hegeman
  10299.  
  10300.  
  10301. Frederick Hegeman is an amateur programmer and computer language hobyist. He
  10302. can be reached at P.O. Box 2368, Rapid City, SD 57709, telephone (605)
  10303. 343-7014.
  10304.  
  10305.  
  10306. A sorting network sorts n items by performing a predetermined set of
  10307. comparisons. A software implementation for a sequential computer might take
  10308. the form
  10309. swap(1,2); swap(4,5); swap(3,5);
  10310. swap(3,4); swap(1,4); swap(1,3);
  10311. swap(2,5); swap(2,4); swap(2,3);
  10312. where swap(a,b) = if(b < a)
  10313. exchange a and b
  10314. This is a nine-comparison network for sorting five items. You might try it
  10315. with pennies, nickels, dimes, quarters, and cheeseburgers. The best-, worst-,
  10316. and typical-case number of comparisons are the same. Only the number of
  10317. exchanges varies. Nine comparisons are, in fact, the fewest necessary to sort
  10318. five items by exchanges when any combination of items is possible. When n is
  10319. known to be small, these simple predetermined sequences out-perform the best
  10320. algorithmic sorts.
  10321. In theory, the minimum number of comparisons necessary to sort n items is the
  10322. ceiling of log2(n!). As n grows larger, 1og2(n!) approaches nlog2(n). The
  10323. reason why O(nlog2(n)) sorting algorithms, such as Quicksort, are so efficient
  10324. as n grows larger should be obvious. Sorting networks generated by the
  10325. Bose-Nelson algorithm (See Listing 1.) are O(n1.585), which diverges from
  10326. nlog2(n) rapidly. However, the Bose-Nelson network for 16 elements is 65
  10327. comparisons, which is pretty nearly nlog2(n), and sorting 1000 random
  10328. 16-element arrays using a Quicksort that pivoted on the last element in the
  10329. array requires 85.43 comparisons on average, which is just over n1.585. This
  10330. is probably the reverse of what you might have expected. Sorting is so often
  10331. discussed in terms of increasing n that it is easy to fall into the trap of
  10332. expecting "efficient" algorithms to behave nicely over the whole range of
  10333. their inputs. The "gotcha" in Quicksort is the extra comparisons needed to
  10334. properly partition the array. They are insignificant when sorting a large
  10335. array but constitute a significant part of the total when the array is small.
  10336. As n becomes very small, the behavior of sorting networks comes closer to the
  10337. ideal.
  10338. Bose-Nelson generates minimum comparison networks only for n Â£ 8. However,
  10339. minimum comparison networks are available for 9£ n£16 as well. Those generated
  10340. by the code in Listing 2 are based on illustrations in The Art of Computer
  10341. Programming, Vol. 3 (Knuth 1973). Please note that both listings generate
  10342. sorts according to element numbers--one greater than the corresponding array
  10343. index.
  10344. Sorting small arrays is a problem all its own, difficult to understand in the
  10345. usual terms. If you need to sort a small array in a time-critical section of
  10346. code, you can count on only two things holding true: your algorithmic sort may
  10347. perform much worse than expected, and no algorithmic sort can match the
  10348. minimum comparison networks on all combinations of elements. When that code is
  10349. part of something like an operating system, the networks have at least two
  10350. other attractive features: many comparisons can be done in parallel, if
  10351. appropriate; and they are inherently re-entrant, so that sorting may be
  10352. interleaved with other tasks.
  10353.  
  10354.  
  10355. Bibliography
  10356.  
  10357.  
  10358. Knuth, Donald E. 1973. The Art of Computer Programming, Vol. 3. Reading, MA:
  10359. Addison-Wesley. Pp. 220-229.
  10360. Bose, R. C. and Nelson, R. J. 1962. "A Sorting Problem". JACM, Vol. 9. Pp.
  10361. 282-296.
  10362. Davis, Wilbon. September, 1992. "Time Complexity". The C Users Journal, Vol.
  10363. 10, No. 9. Pp. 29-38.
  10364.  
  10365. Listing 1 Bose-Nelson algorithm for generating sorting networks
  10366. /* Calling bose(n) generates a network
  10367. * to sort n items. See R. C. Bose & R. J. Nelson,
  10368. * "A Sorting Problem", JACM Vol. 9, Pp. 282-296. */
  10369. bose(n)
  10370. int n;
  10371. {
  10372. Pstar(1, n); /* sort the sequence {X1,...,Xn} */
  10373. }
  10374.  
  10375. P(i, j)
  10376. int i, j;
  10377. {
  10378. printf("swap(%d, %d);\n", i, j);
  10379. }
  10380.  
  10381. Pstar(i, m)
  10382. int i; /* value of first element in sequence */
  10383. int m; /* length of sequence */
  10384. {
  10385. int a;
  10386.  
  10387. if(m > 1)
  10388. {
  10389. /* Partition into 2 shorter sequences,
  10390. * generate a sorting method for each,
  10391. * and merge the two sub-networks. */
  10392. a = m/2;
  10393. Pstar(i, a);
  10394.  
  10395. Pstar((i + a), (m - a));
  10396. Pbracket(i, a, (i + a), (m - a));
  10397. }
  10398. }
  10399.  
  10400. Pbracket(i, x, j, y)
  10401. int i; /* value of first element in sequence 1 */
  10402. int x; /* length of sequence 1 */
  10403. int j; /* value of first element in sequence 2 */
  10404. int y; /* length of sequence 2 */
  10405. {
  10406. int a, b;
  10407.  
  10408. if(x == 1 && y == 1)
  10409. P(i, j); /* 1 comparison sorts 2 items */
  10410. else if(x == 1 && y == 2)
  10411. {
  10412. /* 2 comparisons inserts an item into an
  10413. * already sorted sequence of length 2. */
  10414. P(i, (j + 1));
  10415. P(i, j);
  10416. }
  10417. else if(x == 2 && y == 1)
  10418. {
  10419. /* As above, but inserting j */
  10420. P(i, j);
  10421. P((i + 1), j);
  10422. }
  10423. else
  10424. {
  10425. /* Recurse on shorter sequences, attempting
  10426. * to make the length of one subsequence odd
  10427. * and the length of the other even. If we
  10428. * can do this, we eventually merge the two. */
  10429. a = x/2;
  10430. b = (x & 1) ? (y/2) : ((y + 1)/2);
  10431. Pbracket(i, a, j, b);
  10432. Pbracket((i + a), (x - a), (j + b), (y - b));
  10433. Pbracket((i + a), (x - a), j, b);
  10434. }
  10435. }
  10436. /* End of File */
  10437.  
  10438.  
  10439. Listing 2 Minimum comparison network pairs
  10440. /* 10 items - 29 comparisons; 9 - 25 */
  10441. int net10[29][2] = {
  10442. {2,9},{1,5},{6,10},{3,7},{4,8},{1,4},{7,10},{3,6},
  10443. {1,2},{4,7},{9,10},{5,8},{1,3},{5,9),{2,6),{8,10},
  10444. {2,3},{4,5},{6,7},{8,9),{2,4},{7,9},{3,5},{6,8},
  10445. {3,4},{7,8},{4,6},{5,7},{5,6}};
  10446.  
  10447. /* 12 items - 39 comparisons; 11 - 35 */
  10448. int net12[39][2] = {
  10449. {1,2},{3,4},{5,6},{7,8},{9,10},{11,12},{2,4},{6,8},
  10450. {10,12},{1,3},{5,7},{9,11},{2,3},{6,7},{10,11},
  10451. {2,6},{7,11},{6,10},{3,7},{2,6},{7,11},{1,5},{8,12},
  10452. {4,8},{5,9},{1,5},{8,12},{2,5},{8,11},{4,9},{3,4},
  10453. {9,10},{3,5},{8,10},{4,6},{7,9},{4,5},{6,7},{8,9}};
  10454.  
  10455.  
  10456. /* 16 items - 60 comparisons; 15 - 56; 14 - 51; 13 - 46 */
  10457. int net16[60][2] = {
  10458. {1,2},{3,4},{5,6},{7,8},{9,10},{11,12},{13,14},
  10459. {15,16},{1,3},{5,7},{9,11},{13,15},{2,4},{6,8},
  10460. {10,12},{14,16},{1,5},{9,13},{2,6},{10,14},{3,7},
  10461. {11,15},{4,8},{12,16},{1,9},{2,10},{3,11},{4,12},
  10462. {5,13},{6,14},{7,15},{8,16},{6,11},{7,10},{4,13},
  10463. {8,12},{14,15},{2,3},{5,9},{2,5},{8,14},{3,9},
  10464. {12,15},{3,5},{6,7},{10,11},{12,14},{4,9},{8,13},
  10465. {7,9},{4,6},{8,10},{11,13},{4,5},{6,7},{8,9},{10,11},
  10466. {12,13},{7,8},{9,10}};
  10467.  
  10468. /* Extracts a network for n items from an array of
  10469. * comparison pairs for m items when n <= m. Expects
  10470. * the 2nd member of each pair to be the larger. For
  10471. * example, to extract a minimum comparison network
  10472. * for 9 items call
  10473. * extract(9, sizeof(net10)/(2*sizeof(int)), net10); */
  10474. extract(n, m, network)
  10475. int n, m;
  10476. int network[][2];
  10477. {
  10478. int i;
  10479.  
  10480. for(i = 0; i < m; i += 1)
  10481. {
  10482. if(network[i][1] <= n)
  10483. printf("swap(%d, %d);\n",
  10484. network[i][0] , network[i][1]);
  10485. }
  10486. }
  10487. /* End of File */
  10488.  
  10489.  
  10490.  
  10491.  
  10492.  
  10493.  
  10494.  
  10495.  
  10496.  
  10497.  
  10498.  
  10499.  
  10500.  
  10501.  
  10502.  
  10503.  
  10504.  
  10505.  
  10506.  
  10507.  
  10508.  
  10509.  
  10510.  
  10511.  
  10512.  
  10513.  
  10514.  
  10515.  
  10516.  
  10517.  
  10518. An Efficient Method for Optimizing Binary Trees
  10519.  
  10520.  
  10521. David W. Schwartz
  10522.  
  10523.  
  10524. David W. Schwartz is an undergraduate student in Computer Science and
  10525. Economics at Oklahoma State University. He works in Systems Design for
  10526. Dimensional Concepts Inc., a company specializing in hospital Medical Records
  10527. software. He can be reached by mail at Oklahoma State University, c/o Dr. M.
  10528. Samadzadeh, 219 Math Sciences, Stillwater, OK 74078.
  10529.  
  10530.  
  10531. A binary tree is a data structure used to store dynamic, ordered data. The
  10532. distribution of data within a binary tree greatly influences the efficiency of
  10533. operations on the tree: the more unbalanced the tree, the less efficient the
  10534. data access. Through normal use (i.e., additions and/or deletions), a binary
  10535. tree will generally become unbalanced. Therefore, it is desirable to optimize
  10536. the binary tree periodically to maximize execution time efficiency.
  10537. Many techniques have been devised to prevent trees from becoming unbalanced,
  10538. including AVL trees, red-black trees, and 2-3 trees. These methods generally
  10539. complicate the simple insert, delete, and access routines available with
  10540. standard binary trees. They also generally incorporate the need for additional
  10541. memory in each node.
  10542. It is possible, however, to optimize a binary tree after construction using an
  10543. algorithm as short and simple as the ones used to create and access it. First,
  10544. the tree is formed into a linked list through a standard in-order traversal.
  10545. The tree can then be reformed into its optimally-balanced configuration with a
  10546. simple recursive function.
  10547. This solution allows all access routines to be concise and simple, uses no
  10548. extra data space, and saves time by not balancing a tree when speed is needed
  10549. most, i.e., when the tree is in use.
  10550.  
  10551.  
  10552. Origin
  10553.  
  10554.  
  10555. In his article "Optimizing Binary Trees," (Terry 1991) The author suggests
  10556. rebalancing a tree only periodically, by first flattening the tree into a
  10557. linked list (the least speed-efficient case), then "folding" the tree in half,
  10558. forming two subtrees, which can then also be folded recursively. This process
  10559. yields the optimum arrangement for the tree.
  10560. In Terry's algorithm, the only exception to the folding process appears when a
  10561. subtree contains exactly five nodes. Under the normal process of folding, the
  10562. subtree in Figure 1 would result. Note that if the values in the five nodes
  10563. are close together, it becomes impossible to add new nodes except at the
  10564. extreme edges of the tree, thus extending the tree to three levels. If the
  10565. tree is arranged as in Figure 2, any value less than 3 can be added without
  10566. extending the tree. To avoid such sparsely filled trees, Terry always
  10567. positions the short side of the subtree toward the outside of the parent tree.
  10568. His routines are also generic, i.e., they work on any standard binary tree,
  10569. without knowing what data it contains. His routines require only that each
  10570. node have the pointers to its branches at a predictable location in the node.
  10571. The easiest way to meet this requirement is to place the pointers at the
  10572. beginning of the node; any tree whose node structure begins as follows can be
  10573. optimized:
  10574. struct treenode {
  10575. struct treenode *left;
  10576. struct treenode *right;
  10577. ...
  10578. };
  10579.  
  10580.  
  10581. A Better Way
  10582.  
  10583.  
  10584. Although Terry's routine yields the optimal node arrangement for any given
  10585. tree, it includes many counterproductive movements. If a tree contains N
  10586. nodes, each time the routine folds the tree, it must alter 0(N) of the nodes.
  10587. The tree must be folded approximately log2(N) times, yielding a total of 0(N *
  10588. log2(N)) alterations to complete the process. It is possible to achieve the
  10589. same result with code that is smaller, faster, and makes only 2N or 0(N)
  10590. alterations in the tree. (For an empirical timing comparison of the two
  10591. methods, see the sidebar entitled "Timing Comparison".)
  10592. First, the tree must be formed into a linked list through a standard in-order
  10593. traversal. Then the tree is reformed into its optimally-balanced configuration
  10594. with a simple recursive routine (Listing 1). The tree passed to this routine
  10595. is assumed to have two pointers as the first elements in the node. Data in the
  10596. node is inconsequential to this discussion. The tree is also assumed to use
  10597. NULL pointers as terminators on the leaf nodes. Thus, a program can use these
  10598. routines by simply passing a void pointer to optree and replacing the old root
  10599. with the return value. A program need only execute the following line of code
  10600. to optimize any binary tree.
  10601. root = (struct treenode *) optree((void *)root);
  10602.  
  10603.  
  10604. The Code
  10605.  
  10606.  
  10607. Of the code necessary for the optimization (see Listing 1), the only routine
  10608. available to other source files is optree (from "optimize tree"). It serves as
  10609. the entry point for the module and takes only a single parameter, a pointer to
  10610. the root of the tree to be optimized. The optree function initializes the
  10611. pointers for the linked list and then calls the routine to form the tree into
  10612. a linked list, list_build.
  10613. The list_build function is a modified in-order traversal of the tree. It
  10614. traverses the tree attaching each node to the end of a list and counting how
  10615. many nodes are placed in the list. This count later determines how many nodes
  10616. are in the tree and how the tree should be subdivided optimally. When
  10617. list_build completes, optree begins the optimization process by resetting the
  10618. left variable, to indicate that the tree currently leans to the right, and
  10619. then calling the recursive optimization routine, form_tree.
  10620. The form_tree function works much like a recursive node-counting routine
  10621. turned upside down. A node-counting routine would call itself to find the
  10622. number of nodes in the left subtree, add one for the current node, and call
  10623. itself to find the number of nodes in the right subtree. Conversely, form_tree
  10624. receives as a parameter the number of nodes that should be included in the
  10625. newly-constructed tree. To construct the appropriate tree, form_tree
  10626. determines how many nodes belong in the left subtree, calls itself to remove
  10627. that number of nodes from the linked list, removes a node from the list for
  10628. itself, and then calls itself again to remove the rest of the nodes (the right
  10629. subtree) from the list. The recursion terminates when form_tree is asked to
  10630. construct a tree with fewer than one node (namely, a NULL tree).
  10631. The only complexity in the algorithm involves calculating how many nodes
  10632. belong in each subtree. If the number of nodes to be divided between the two
  10633. subtrees is odd, the extra node should be put in the subtree that is closer to
  10634. the center of the current node's parent. If the current node is a left child
  10635. of node X, the extra node should go into the right subtree of the current
  10636. node. This arrangement forces the shortest paths to the outside of the tree,
  10637. where data is most likely to be added.
  10638. The special five-node case (in which the subtree is unevenly divided, as in
  10639. Figure 2) is also considered in calculating the number of nodes that belong in
  10640. each subtree. Note that when folding a five-node tree, the desired effect can
  10641. be achieved by simply moving the fold one node too far from the center of the
  10642. parent tree. That is, instead of dividing the nodes so that there is one more
  10643. node in one subtree than in the other, simply divide it so that there are two
  10644. more nodes in one subtree than in the other. Thus handling the five-node case
  10645. requires only the following line of code.
  10646. if(num == 5) middle++;
  10647.  
  10648.  
  10649. External Variables
  10650.  
  10651.  
  10652. There are five global variables in Listing 1. The decision to make these
  10653. variables global was not reached arbitrarily. The globals are all static and
  10654. thus protected from other modules. The list_base variable is global so that
  10655. one node with a right pointer can be placed on the list before recursion
  10656. begins. This eliminates the need to check for a NULL header each time the
  10657. function is entered, thus increasing efficiency and decreasing code size. The
  10658. other four globals are used by multiple levels of recursion, although only one
  10659. copy of each is necessary. If these variables had not been global, some
  10660. routines would have required several extra parameters. Excess parameters
  10661. consume scarce stack space and add expensive pointer references. Consequently,
  10662. the drawbacks usually associated with the use of global variables are
  10663. overshadowed by the compactness, efficiency, and clarity they provide here.
  10664.  
  10665.  
  10666. Sorted Data
  10667.  
  10668.  
  10669. One disadvantage to binary trees is that when adding sorted data they
  10670. degenerate into linked lists and exhibit worst case behavior. The proposed
  10671. optimization algorithm addresses this limitation in two ways. First, the
  10672. optimization algorithm can conveniently "fix" any binary tree that has
  10673. suffered the effects of sorted data previously added. Second, with only a
  10674. small modification the algorithm can add sorted data to a tree as quickly and
  10675. conveniently as the data's sorted nature warrants! Since a large volume of
  10676. sorted data would normally only be added in batch processing, it could be done
  10677. easily as part of the optimization process.
  10678.  
  10679. During optimization, the tree is initially formed into a linked list. The
  10680. sorted data is already in some form of a list (or it cannot be considered
  10681. sorted). The two lists merge easily and quickly into a single list. This list
  10682. becomes a balanced tree when given to the optimization function. Thus, adding
  10683. a large volume of sorted data results in optimal performance, not worst-case
  10684. performance.
  10685.  
  10686.  
  10687. Possible Problems
  10688.  
  10689.  
  10690. Perhaps the most obvious problem with the routines presented here lies in the
  10691. tendency of list_build to cause a stack overflow when flattening a large,
  10692. badly degenerated tree. A non-recursive in-order traversal algorithm, which
  10693. can be found in many computer science textbooks, will solve this problem.
  10694. Listing 1 uses a recursive approach in the interest of simplicity and brevity.
  10695. The form_tree function should not need to be rewritten to avoid recursion.
  10696. Nearly all implementations of C allow for enough levels of recursion to
  10697. optimize millions of nodes. A well-optimized binary tree of height 32 can
  10698. contain over four billion nodes, many times the number of nodes normally
  10699. expected in a binary tree.
  10700.  
  10701.  
  10702. Conclusion
  10703.  
  10704.  
  10705. Any implementation of a binary tree can benefit from having the data in the
  10706. tree redistributed evenly. The routines presented here provide a means to
  10707. achieve the most speed-efficient distribution of the data, without the
  10708. complexities involved in balancing the tree as it is growing and shrinking.
  10709. The standard routines for manipulation of binary trees are renowned for being
  10710. small, easy to understand, and fairly efficient. Every attempt was made to
  10711. keep the optimization routines within that tradition.
  10712. The optimization routines presented here are a significant improvement over
  10713. the routines presented by Terry (1991) which inspired their creation. When
  10714. compared to the original routines, these routines:
  10715. are smaller--they compile to smaller executable code and take fewer pages to
  10716. list;
  10717. are easier to understand--only one function is necessary to rebuild a tree
  10718. from a list;
  10719. require less stack space--fewer parameters are used for the recursive
  10720. functions; and
  10721. execute faster.
  10722.  
  10723.  
  10724. Bibliography
  10725.  
  10726.  
  10727. Terry, Bruce. June 1991. "Optimizing Binary Trees." The C Users Journal.
  10728. 65-74.
  10729.  
  10730.  
  10731. Timing Comparison
  10732.  
  10733.  
  10734. In these tests, a small program, which adds the same random numbers to each of
  10735. two binary trees, optimizes each copy of the tree 30 times. (Since the tree is
  10736. initially flattened and then rebuilt, rebuilding a balanced tree should take
  10737. just as long as rebuilding a degenerate one.) Thirty repetitions was deemed to
  10738. be sufficient to cancel any round-off or timer errors. Table 1 gives the time
  10739. needed for each routine to complete its optimizations. Figure 3 is a graphical
  10740. representation of the data in the table.
  10741. Figure 1 Normal result of a five-node tree
  10742. Figure 2 Preferred result of a five-node tree
  10743. Figure 3 Execution time comparison
  10744. Table 1 Execution times for original and proposed methods
  10745.  
  10746. Listing 1 Code for optimizing a binary tree
  10747. #include <stdio.h>
  10748.  
  10749. /* TREE STRUCT: GENERIC BINARY TREE NODE */
  10750. typedef struct treenode {
  10751. struct treenode *left;
  10752. struct treenode *right;
  10753. /* Data is unimportant ...*/
  10754. } TREENODE;
  10755.  
  10756. /* STATIC VARIABLES */
  10757. static int list_num;
  10758. static TREENODE list_base;
  10759. static TREENODE *list_tail;
  10760. static TREENODE *root;
  10761. static int left;
  10762.  
  10763. /*list_num IS SIMPLY A COUNTER OF HOW MANY ITEMS HAVE
  10764. BEEN ADDED TO THE LIST.
  10765. list_base IS USED AS A DUMMY NODE AT THE HEAD OF THE
  10766. LIST TO PREVENT TESTING FOR A NULL ROOT
  10767. EVERY TIME A NODE IS ADDED TO THE LIST.
  10768. list_tail IS USED AS A POINTER TO THE LAST NODE IN
  10769.  
  10770. THE LINKED LIST.
  10771. root IS USED AS A POINTER TO THE NODE AT THE
  10772. HEAD OF THE LIST.
  10773. left IS A BOOLEAN TO KEEP TRACK OF WHICH WAY WE
  10774. ARE GOING IN THE TREE SO THAT WE DIVIDE
  10775. UNEVEN LISTS CORRECTLY. ALTHOUGH IT WOULD
  10776. BE MORE CLEAR TO USE A PARAMETER, THE
  10777. ROUTINE IS FINISHED WITH left BEFORE IT
  10778. RECURSES (IT IS NO LONGER USING IT) AND
  10779. MAKING IT STATIC REDUCES THE STACK SIZE
  10780. REQUIRED FOR THE RECURSION. */
  10781.  
  10782. /* Function prototypes */
  10783. void *optree(void *);
  10784. static void list_build(TREENODE *);
  10785. static TREENODE *form_tree (int);
  10786.  
  10787. /* OPTIMIZE A TREE */
  10788. void *optree(void *opt_root)
  10789. {
  10790. if (opt_root == NULL) return(NULL);
  10791.  
  10792. list_tail = &list_base;
  10793. list_num = 0;
  10794.  
  10795. list_build(opt_root);
  10796.  
  10797. root = list_base.right;
  10798.  
  10799. left = 0;
  10800. return((void *) form_tree(list_num));
  10801. }
  10802.  
  10803. /* FORM AN OPTIMIZED TREE FROM LIST */
  10804. TREENODE *form_tree(int num)
  10805. {
  10806. int middle;
  10807. TREENODE *ptr;
  10808.  
  10809. middle = (num >> 1); /* (num / 2) */
  10810.  
  10811. /* SPECIAL 5-NODE CASE */
  10812. if(num == 5) middle++;
  10813.  
  10814. /* LEAN BRANCH TO CENTER OF TREE */
  10815. if(left) middle = num - middle - 1;
  10816.  
  10817. /* REMOVE LEFT SUBTREE FROM LIST */
  10818. left = 1;
  10819. ptr = (middle > 0) ? form_tree(middle) : NULL;
  10820. root->left = ptr;
  10821.  
  10822. /* REMOVE THIS NODE FROM LIST */
  10823. ptr = root;
  10824. root = root->right;
  10825.  
  10826. /* REMOVE RIGHT SUBTREE FROM LIST */
  10827. left = 0;
  10828. middle =num - middle - 1;
  10829.  
  10830. ptr->right = (middle > 0) ? form_tree(middle) : NULL;
  10831. return ptr;
  10832. }
  10833.  
  10834. /* FLATTEN TREE INTO LINKED LIST */
  10835. void list_build(TREENODE *node)
  10836. {
  10837. if(node->left) list_build(node->left);
  10838.  
  10839. list_tail->right = node;
  10840. list_tail = node;
  10841. lis_num++;
  10842.  
  10843. if(node->right) list_build(node->right);
  10844. }
  10845. /* End of File */
  10846.  
  10847.  
  10848.  
  10849.  
  10850.  
  10851.  
  10852.  
  10853.  
  10854.  
  10855.  
  10856.  
  10857.  
  10858.  
  10859.  
  10860.  
  10861.  
  10862.  
  10863.  
  10864.  
  10865.  
  10866.  
  10867.  
  10868.  
  10869.  
  10870.  
  10871.  
  10872.  
  10873.  
  10874.  
  10875.  
  10876.  
  10877.  
  10878.  
  10879.  
  10880.  
  10881.  
  10882.  
  10883.  
  10884.  
  10885.  
  10886.  
  10887.  
  10888.  
  10889.  
  10890.  
  10891.  
  10892.  
  10893. A Library of Financial Functions
  10894.  
  10895.  
  10896. William Smith
  10897.  
  10898.  
  10899. William Smith is the engineering manager at Montana Software, a software
  10900. development company specializing in custom applications for MS-DOS and
  10901. Windows. You may contact him by mail at P.O. Box 663, Bozeman, MT 59771-0663.
  10902.  
  10903.  
  10904. The need in science and engineering for high-speed data processing was the
  10905. original driving force behind the invention and the later improvement of
  10906. computer hardware. It did not take long, however, before business became the
  10907. largest user of computers and the driving force behind the improvement and
  10908. diversification of computer software.
  10909. Presently, the largest volume categories of software applications are
  10910. business-oriented. Business uses of computers and software cover such diverse
  10911. applications as financial forecasting, economic modeling, accounting, tracking
  10912. inflation, record keeping, materials planning, inventory, depreciation,
  10913. document processing, taxes, and bookkeeping, to name just a few.
  10914. As a whole, business software is concerned with money. A central theme to
  10915. business-oriented calculations and modeling is the time value of money. Loans,
  10916. investments, rate-of-return calculations, depreciation, taxes, and
  10917. cost/benefit analysis all require calculations based upon interest rates over
  10918. periods of time. Nearly everyone has a loan, a bank account, or credit card.
  10919. Interest rates and the resulting time value of money directly affects us all.
  10920. Computations that involve interest rates are tedious by hand. They require
  10921. using interest rate tables to relieve some of the number pushing. Computer
  10922. automation can easily handle these types of calculations.
  10923. More than a couple of times I have had to incorporate the equivalent of
  10924. interest-rate tables into a software project. The relationships between the
  10925. three quantities present value, future values, and equivalent uniform series
  10926. form six basic formulas. These formulas are a function of interest rates and
  10927. time expressed as number of periods.
  10928. The theory of compounding, or how often you calculate the interest, further
  10929. complicates matters. Calculations with continuous compounding requires an
  10930. additional set of six formulas.
  10931. In total, there are twelve interest rate formulas that I have utilized when
  10932. writing business applications. These formulas eventually became a small
  10933. library of C functions and macros that I have used many times. They are
  10934. simple, small, and constitute a useful addition to your library and arsenal of
  10935. programming tools.
  10936. There are additional interest rate formulas for increasing series of values
  10937. known as gradients. I have never had to use these formulas, so they have not
  10938. appeared in my financial function library yet.
  10939.  
  10940.  
  10941. Numeric Money Types
  10942.  
  10943.  
  10944. When writing a business application in C, one of the first stumbling blocks
  10945. you will come up against is which numeric type works best to represent money.
  10946. Representing money values in C is a challenge. Floating-point and integer
  10947. types have some contrasting pros and cons. Using a floating-point value to
  10948. represent money has the problem of requiring a conversion to fixed point. This
  10949. requires rounding off and can eventually lead to round-off error. Using an
  10950. integer value has the disadvantage of not covering a large enough range of
  10951. values under some compiler/operating system combinations.
  10952. C-library vendors have addressed these problems by offering business-math
  10953. libraries. There are many business-math libraries available that each vendor
  10954. claims overcomes the problems of round-off error and range. I have never used
  10955. any so I cannot make any statements as to whether they overcome some of the
  10956. problems that developers typically encounter when working with money.
  10957. For the interest-rate functions, I choose to use the standard floating-point
  10958. type double. I choose this because the functions work with interest rates and
  10959. periods. Neither of these is a money type. Interest rate is a true
  10960. floating-point value. The number of periods is an integer value. The value
  10961. returned by the functions is also a floating-point value. You use this value
  10962. on or with money values, but it is not a money value. This gets the
  10963. interest-rate functions out of the dilemma of having to commit to a best way
  10964. to represent a money type. It is up to the user of the functions to determine
  10965. how best to handle rounding and representation of money values.
  10966.  
  10967.  
  10968. Interest-Rate Formulas
  10969.  
  10970.  
  10971. The basic requirements of interest calculations involve working with the
  10972. concepts of present values, future values, and equivalent uniform series of
  10973. values. The relationships between these three quantities depend upon interest
  10974. rates and time. For the purpose of the formulas, time is broken up into
  10975. periods. The interest rate values must apply to the same period.
  10976. There are six possible relationships between the three values of present value
  10977. (P), future value (F), and equivalent uniform series of values (A). Table 1
  10978. contains some symbols and definitions that the interest rate formulas use.
  10979. The relationships between A, F, and P are represented by ratios between the
  10980. values. Each of the six formulas calculate one of the possible six ratios. The
  10981. relationship or ratio depends only on the interest rate and number of periods.
  10982. Table 2 lists the six relationships for discrete compounding. The table
  10983. includes the formula as a function of interest rate (i) and number of periods
  10984. (n). The formulas and functions require the interest rate as a floating-point
  10985. number, not a percentage. The table also lists the symbolic representation of
  10986. the formula as a ratio between A, F, or P. The first column in the table is
  10987. the name of the C-function rendition of the formula.
  10988. The formulas in Table 2 assume discrete compounding. They expect the interest
  10989. rate to apply to the same period as the compounding frequency. As long as this
  10990. criterion holds, these formulas will work for any interest rate and number of
  10991. periods. If the compounding frequency is different from the interest period,
  10992. the number of periods and interest per period must be converted into values
  10993. adjusted for compounding periods and interest. This is done by dividing the
  10994. interest rate by the compounding frequency per period and multiplying the
  10995. number of periods by the compounding frequency.
  10996. For example, if the interest rate is 0.05 (5.0%) per period, the period length
  10997. is one year, the number of periods is 10, and the compounding is monthly, you
  10998. must adjust both the interest rate and the number of periods to use the
  10999. formulas in Table 2. The interest rate (i) becomes
  11000. 0.05 / 12 = 0.004167
  11001. and the number of periods (n) are
  11002. 10 * 12 = 120.
  11003. Continuous compounding requires entirely different formulas. You can think of
  11004. continuous compounding as increasing the compounding frequency to its absolute
  11005. limit. The formulas still use an interest rate and the number of periods.
  11006. Since the interest-rate period is no longer equal to the compounding period,
  11007. the interest rate is no longer the effective interest rate, but a nominal
  11008. interest rate (r). The nominal interest rate does not include the effect of
  11009. compounding.
  11010. Table 3 lists the six interest-rate formulas and functions for continuous
  11011. compounding. The symbolic representation of the formulas is the same as in
  11012. Table 2. The Symbols column is a ratio between A,F, or P. I have changed the C
  11013. function names by adding _c to the end of the name. This indicates that the
  11014. function is for continuous compounding. The formulas depend upon the nominal
  11015. interest rate per period and the number of periods. In addition, the formulas
  11016. use the constant e (natural log base).
  11017.  
  11018.  
  11019. Functions and Macros
  11020.  
  11021.  
  11022. Listing 1, FINANCES.C, contains the twelve functions listed in Table 2 and
  11023. Table 3. These functions are interdependent. They make calls to one another. I
  11024. have optimized these functions for size. The functions are as terse as
  11025. possible. They rely upon the similarities between one another to keep them
  11026. short and compact. This may take the least amount of overall code space, but
  11027. is not the fastest rendition possible. For those who need speed, Listing 2,
  11028. FINANCES.H contains macro equivalents to the functions in Listing 1. I have
  11029. optimized the macros for speed. The macros avoid calling any of the functions
  11030. in Listing 1. Listing 2 also contains prototypes for all the functions in
  11031. Listing 1.
  11032. If you want to create a hybrid of these two versions you can change the way
  11033. each function is coded. Just include a call to a macro inside a function
  11034. wrapper. For example you can rewrite the function a_to_f as
  11035. double a_to_f( double i, int n )
  11036. {
  11037. return ( A_TO_F( i, n ) );
  11038. }
  11039. This would speed up the functions by avoiding interdependency and calls to
  11040. other functions in the library.
  11041. Here is an example of using the functions to calculate the monthly payment
  11042. required to pay back a $100,000.00 loan in 30 years with a 8% annual interest
  11043. rate. This is a typical mortgage these days.
  11044. Payment = 100000.0 * p_to_a( 0.08 / 12.0, 30 * 12 );
  11045. (Monthly payment is $733.77)
  11046.  
  11047.  
  11048.  
  11049. Conclusions
  11050.  
  11051.  
  11052. Calculations involving interest rates are fundamental to business and
  11053. financial software. This library of functions presents a set of tools. They
  11054. are building blocks you can use to add time value of money features to your
  11055. applications. I have used them in applications ranging from a simple Real
  11056. Estate loan-calculation program to a sophisticated financial-modeling
  11057. application.
  11058. Do not underestimate their utility. The Real Estate loan calculation program
  11059. was extremely simple. It consisted of nothing more than a very easy to use
  11060. interface on top of these functions. I created this program as a vertical
  11061. application for some local realtors who were computer-illiterate and
  11062. computer-intimidated. For a very small amount of work it garnered a handsome
  11063. rate of return.
  11064. Table 1 Symbols and definitions used by the interest rate formulas
  11065. Symbol Description
  11066. -----------------------------------------
  11067. A uniform series amount or annuity
  11068. F future value or worth
  11069. P present value or worth
  11070. e natural logarithm base (2.718)
  11071. n number of periods
  11072. i effective interest rate per period
  11073. r nominal interest rate per period
  11074. Table 2 Interest rate functions and formulas for discrete compounding
  11075. Function Description Symbol Formula
  11076. Name
  11077. ----------------------------------------------------------
  11078. p_to_f present value to future value F/P (1 + i)n
  11079.  --------
  11080.  1
  11081.  
  11082. f_to_p future value to present value P/F 1
  11083.  --------
  11084.  (1 + i)n
  11085.  
  11086. f_to_a future value to annuity A/F i
  11087.  ----------
  11088.  (1 + i)n-1
  11089.  
  11090. p_to_a present value to annuity A/P i(1 + i)n
  11091.  ------------
  11092.  (1 + i)n - 1
  11093.  
  11094. a_to_f annuity to future value F/A (1 + i)n - 1
  11095.  ------------
  11096.  i
  11097.  
  11098. a_to_p annuity to present value P/A (1 + i)n - 1
  11099.  ------------
  11100.  i(1 + i)n
  11101. Table 3 Interest rate functions and formulas for continuous compounding
  11102. Function Description Symbol Formula
  11103. Name
  11104. ------------------------------------------------------------
  11105. p_to_f_c present value to future value F/P ern
  11106.  ---
  11107.  1
  11108.  
  11109. f_to_p_c future value to present value P/F 1
  11110.  ---
  11111.  ern
  11112.  
  11113. f_to_a_c future value to annuity A/F (er - 1)
  11114.  ---------
  11115.  (ern - 1)
  11116.  
  11117.  
  11118. p_to_a_c present value to annuity A/P (er - 1)ern
  11119.  -----------
  11120.  (ern - 1)
  11121.  
  11122. a_to_f_c annuity to future value F/A (ern - 1)
  11123.  ---------
  11124.  (er - 1)
  11125.  
  11126. a_to_p_c annuity to present value P/A (ern - 1)
  11127.  -----------
  11128.  (er - 1)ern
  11129.  
  11130. Listing 1 FINANCES.C -- functions for calculating interest rate formulas
  11131. /******************************************************
  11132. File Name: FINANCES.C
  11133. Description: Library of functions for
  11134. calculating interest rate
  11135. formulas
  11136. Global Function List: a_to_f
  11137. a_to_f_c
  11138. a_to_p
  11139. a_to_p_c
  11140. f_to_a
  11141. f_to_a_c
  11142. f_to_p
  11143. f_to_p_c
  11144. p_to_a
  11145. p_to_a_c
  11146. p_to_f
  11147. p_to_f_c
  11148. Portability: Standard C
  11149. ******************************************************/
  11150. /* Standard C */
  11151. #include <math.h>
  11152. /* Own */
  11153. #include <finances.h>
  11154.  
  11155. /*****************************************************
  11156. Name: a_to_f
  11157. Description: annuity to future value
  11158. Parameters: i - effective interest rate per period
  11159. n - number of periods
  11160. Return: F/A - annuity to future value factor
  11161. *****************************************************/
  11162. double a_to_f( double i, int n )
  11163. {
  11164. return ( ( p_to_f( i, n ) - 1.0 ) / i );
  11165. }
  11166.  
  11167. /*****************************************************
  11168. Name: a_to_f_c
  11169. Description: Annuity to future value
  11170. continuous compounding
  11171. Parameters: r - nominal interest rate per period
  11172. n - number of periods
  11173. Return: F/A - Annuity to future value factor
  11174. *****************************************************/
  11175. double a_to_f_c( double r, int n )
  11176. {
  11177.  
  11178. return ( ( p_to_f_c( r, n ) - 1.0 ) /
  11179. ( pow( LN BASE, r ) - 1.0 ) );
  11180. }
  11181.  
  11182. /*****************************************************
  11183. Name: a_to_p
  11184. Description: annuity to present value
  11185. Parameters: i - effective interest rate per period
  11186. n - number of periods
  11187. Return: P/A - annuity to present value factor
  11188. *****************************************************/
  11189. double a_to_p( double i, int n )
  11190. {
  11191. return ( a_to_f( i, n ) / p_to_f( i, n ) );
  11192. }
  11193.  
  11194. /*****************************************************
  11195. Name: a_to_p_c
  11196. Description: annuity to present value
  11197. continuous compounding
  11198. Parameters: r - nominal interest rate per period
  11199. n - number of periods
  11200. Return: P/A - annuity to present value factor
  11201. *****************************************************/
  11202. double a_to_p c( double r, int n )
  11203. {
  11204. return ( a_to_f_c( r, n ) / p_to_f_c( r, n ) );
  11205. }
  11206.  
  11207. /*****************************************************
  11208. Name: f_to_a
  11209. Description: future value to annuity
  11210. Parameters: i - effective interest rate per period
  11211. n - number of periods
  11212. Return: A/F - future value to annuity factor
  11213. *****************************************************/
  11214. double f_to_a( double i, int n )
  11215. {
  11216. return ( 1.0 / a_to_f( i, n ) );
  11217. }
  11218.  
  11219. /*****************************************************
  11220. Name: f_to_a c
  11221. Description: future value to annuity
  11222. continuous compounding
  11223. Parameters: r - nominal interest rate per period
  11224. n - number of periods
  11225. Return: A/F - future value to annuity factor
  11226. *****************************************************/
  11227. double f_to_a c( double r, int n )
  11228. {
  11229. return ( 1.0 / a_to_f_c( r, n ) );
  11230. }
  11231. /*****************************************************
  11232. Name: f_to_p
  11233. Description: future value to present value
  11234. Parameters: i - effective interest rate per period
  11235. n - number of periods
  11236. Return: P/F - future to present value factor
  11237.  
  11238. *****************************************************/
  11239. double f_to_p( double i, int n )
  11240. {
  11241. return ( 1.0 / p_to_f( i, n ) );
  11242. }
  11243. /*****************************************************
  11244. Name: f_to_p_c
  11245. Description: future value to present value
  11246. continuous compounding
  11247. Parameters: r - nominal interest rate per period
  11248. n - number of periods
  11249. Return: P/F - future to present value factor
  11250. *****************************************************/
  11251. double f_to_p_c( double r, int n )
  11252. {
  11253. return ( 1.0 / p_to_f_c( r, n ) );
  11254. }
  11255. /*****************************************************
  11256. Name: p_to_a
  11257. Description: present value to annuity
  11258. Parameters: i - effective interest rate per period
  11259. n - number of periods
  11260. Return: A/P - present value to annuity factor
  11261. *****************************************************/
  11262. double p_to_a( double i, int n )
  11263. {
  11264. return ( p_to_f( i, n ) / a_to_f( i, n ) );
  11265. }
  11266.  
  11267. /****************************************************
  11268. Name: p_to_a_c
  11269. Description: present value to annuity
  11270. continuous compounding
  11271. Parameters: r - nominal interest rate per period
  11272. n - number of periods
  11273. Return: A/P - present value to annuity factor
  11274. ****************************************************/
  11275. double p_to_a_c( double r, int n )
  11276. {
  11277. return ( p_to_f_c( r, n ) / a_to_f_c( r, n ) );
  11278. }
  11279.  
  11280. /****************************************************
  11281. Name: p_to_f
  11282. Description: present value to future value
  11283. Parameters: i - effective interest rate per period
  11284. n - number of periods
  11285. Return: F/P - present to future value factor
  11286. ****************************************************/
  11287. double p_to_f( double i, int n )
  11288. {
  11289. return ( pow( 1 + i, n ) );
  11290. }
  11291.  
  11292. /****************************************************
  11293. Name: p_to_f_c
  11294. Description: present value to future value
  11295. continuous compounding
  11296. Parameters: r - nominal interest rate per period
  11297.  
  11298. n - number of periods
  11299. Return: F/P - present to future value factor
  11300. ****************************************************/
  11301. double p_to_f_c( double r, int n )
  11302. {
  11303. return ( pow( LN_BASE, r * n ) );
  11304. }
  11305.  
  11306. /* End of File */
  11307.  
  11308.  
  11309. Listing 2 FINANCES.H
  11310. /****************************************************
  11311. File Name: FINANCES.H
  11312. Description: Include file for FINANCES.C
  11313. Portability: Standard C
  11314. ****************************************************/
  11315.  
  11316. #if !defined ( FINANCES_DEFINED )
  11317.  
  11318. /* Prototypes for function in FINANCES.C */
  11319. double a_to_f( double i, int n );
  11320. double a_to_f_c( double r, int n );
  11321. double a_to_p( double i, int n );
  11322. double a_to_p_c( double r, int n );
  11323. double f_to_a( double i, int n );
  11324. double f_to_a_c( double r, int n );
  11325. double f_to_p( double i, int n );
  11326. double f_to_p_c( double r, int n );
  11327. double p_to_a( double i, int n );
  11328. double p_to_a_c( double r, int n );
  11329. double p_to_f( double i, int n );
  11330. double p_to_f_c( double r, int n );
  11331.  
  11332. /* Natural log base */
  11333. #define LN_BASE 2.718281828459
  11334.  
  11335. /* Macro versions of functions in FINANCES.C */
  11336. #define A_TO_F( i, n ) \
  11337. ( ( pow( 1.0 + i, n ) - 1.0 ) / i )
  11338.  
  11339. #define A_TO_F_C( r, n ) \
  11340. ( ( pow( LN_BASE, r * n ) - 1.0 ) / \
  11341. ( pow( LN_BASE, r ) - 1.0 ) )
  11342.  
  11343. #define A_TO_P( i, n ) \
  11344. ( ( pow( 1.0 + i, n ) - 1.0 ) / \
  11345. ( i * pow( 1.0 + i, n) ) )
  11346.  
  11347. #define A_TO_P_C( r, n ) \
  11348. ( ( 1.0 - pow( LN_BASE, -( r * n ) ) ) / \
  11349. ( pow( LN_BASE, r ) - 1.0 ) )
  11350.  
  11351. #define F_TO_A( i, n ) \
  11352. ( i / ( pow( 1.0 + i, n ) - 1.0) )
  11353.  
  11354. #define F_TO_A_C( r, n ) \
  11355. ( ( pow( LN_BASE, r ) - 1.0 ) / \
  11356. ( pow( LN_BASE, r * n ) - 1.0 ) )
  11357.  
  11358.  
  11359. #define F_TO_P( i, n ) \
  11360. ( 1.0 / pow( 1.0 + i, n ) )
  11361.  
  11362. #define F_TO_P_C( r, n ) \
  11363. ( 1.0 / pow( LN_BASE, r * n ) )
  11364.  
  11365. #define P_TO_A( i, n ) \
  11366. ( i * pow ( 1.0 + i, n ) / \
  11367. ( pow( 1.0 + i, n ) - 1.0 ) )
  11368.  
  11369. #define P_TO_A_C( r, n ) \
  11370. ( ( pow( LN_BASE, r ) - 1.0 ) / \
  11371. ( 1.0 - pow( LN_BASE, -( r * n ) ) ) )
  11372.  
  11373. #define P_TO_F( i, n ) \
  11374. (pow( 1.0 + i, n) )
  11375.  
  11376. #define P_TO_F_C( r, n ) \
  11377. ( pow( LN_BASE, r * n ) )
  11378.  
  11379. #endif
  11380. /* End of File */
  11381.  
  11382.  
  11383.  
  11384.  
  11385.  
  11386.  
  11387.  
  11388.  
  11389.  
  11390.  
  11391.  
  11392.  
  11393.  
  11394.  
  11395.  
  11396.  
  11397.  
  11398.  
  11399.  
  11400.  
  11401.  
  11402.  
  11403.  
  11404.  
  11405.  
  11406.  
  11407.  
  11408.  
  11409.  
  11410.  
  11411.  
  11412.  
  11413.  
  11414.  
  11415.  
  11416.  
  11417.  
  11418.  
  11419.  
  11420.  
  11421. Making C++ Safe for Threads
  11422.  
  11423.  
  11424. Dave Plauger
  11425.  
  11426.  
  11427. Dave has over 20 years experience with the design and development of operating
  11428. systems. His main areas of expertise include real time and multi processing.
  11429. Dave currently serves as Technical Editor of POSIX 1003.14 (Multiprocessing
  11430. Profile) and has been a member of various POSIX working groups since the days
  11431. of /usr/group. Dave may be reached at Mercury Computer Systems, Lowell MA. The
  11432. phone number is (508) 458-3100; email djp@mc.com.
  11433.  
  11434.  
  11435.  
  11436.  
  11437. Introduction
  11438.  
  11439.  
  11440. Multithreading is a powerful technique that can speed up application programs
  11441. on both multiprocessors and uniprocessors. On multi processors there is a
  11442. potential speedup due to parallel computation. On uni processors there is a
  11443. potential speedup due to the overlap of I/O with computation.
  11444. The speedup is potential because, in the real world, there are always
  11445. trade-offs. In order to have computational parallelism you need
  11446. synchronization, which adds overhead. In order to overlap I/O with computation
  11447. you again need synchronization in the form of I/O completion events.
  11448. Multi threading helps by hiding (but not eliminating) the complexity and the
  11449. overhead associated with both types of parallelism. In the many cases where
  11450. parallelism does help, programming with threads is a convenient way to use it.
  11451.  
  11452.  
  11453. Issues of Thread-Safety
  11454.  
  11455.  
  11456. The main problem that arises when adding threads to programs is synchronizing
  11457. updates to shared variables. The problem arises because updates to data
  11458. structures often require several machine steps. These individual operations
  11459. may be interleaved at random among multiple processors. And that can cause
  11460. corrupted data structures, storage overwrites, and even more unpleasant
  11461. problems. Even on a uni processor threads may be time-shared, which means they
  11462. effectively run in parallel.
  11463. High-level languages generally let you ignore the underlying machine, but
  11464. parallel programming drags you back into worrying about what is really
  11465. happening. On the other hand, you do have enough information in the
  11466. declarations to help spot the potential problems. Variables that are declared
  11467. at file scope and static data within functions clearly need to be protected
  11468. from uncoordinated updates by multiple threads.
  11469. Less obviously, control data structures used by library packages need careful
  11470. attention as well. Examples of these are the FILE structures used in the
  11471. Standard C library and the dirent structures used in UNIX and POSIX. These
  11472. data structures hold state information between calls. Problems may arise if
  11473. they are used in more than one thread at the same time, or even alternately.
  11474. The same issues arise with member data in C++ classes. In the C library,
  11475. something has to be done for errno, stdin, stdout, and stderr (at least), to
  11476. make them thread-safe. In C++, the analogous structures are cin, cout, cerr,
  11477. and clog.
  11478.  
  11479.  
  11480. Two Solutions
  11481.  
  11482.  
  11483. There are two general approaches to dealing with a global shared variable in a
  11484. multi-threaded program:
  11485. provide locking in order to synchronize updates
  11486. eliminate the problem by making the variable thread private
  11487. There are lots of mechanisms to enforce locking. Some examples are semaphores,
  11488. monitors, and message queues. The POSIX threads package (IEEE P1003.4a, Draft
  11489. 6) provides the function pthread_mutex_lock and pthread_mutex_unlock.
  11490. Locks should be associated with the data, not the code. Locking code that
  11491. happens to access shared variables may overly limit parallelism. For example,
  11492. in an operating system one has to lock the global run queue or task data
  11493. during updates, but one should avoid having a scheduler lock. The latter
  11494. approach results in a system that may be thread-safe, but it will be slower
  11495. than the original, single-threaded version. The trade-off one makes is to
  11496. implement enough fine-grained locking to maximize parallelism without adding
  11497. too much overhead.
  11498. Besides adding to the overhead of even a single-threaded program, locking
  11499. introduces the problem of deadlock. Deadlock can be avoided by recognizing the
  11500. hierarchical structure of the data, and obtaining locks in a strict order.
  11501. However, this requires global knowledge of all data, which may be impossible
  11502. in a modular system, such as a third-party library.
  11503. Thread-private data may be supported explicitly by the compiler, if you're
  11504. lucky. But there are routines in each thread package that provide for
  11505. thread-private data, because it is indispensable. In POSIX threads, the
  11506. routines are pthread_keycreate, pthread_getspecific, and pthread_setspecific.
  11507. These routines implement an associative memory, in which a shared key is
  11508. associated with a thread-specific pointer.
  11509. The easiest form of thread-private storage to use is the stack. Each thread
  11510. has its own stack, therefore all automatic variables are thread-safe. You can
  11511. also place a data structure in thread-private storage by using malloc (in C)
  11512. or new (in C++) to allocate the storage, then storing the pointer in
  11513. thread-specific data. When a new thread is created, its specific data pointers
  11514. are set to null. Knowing this, the program can tell at runtime when to
  11515. allocate and initialize another instance of the data structure. Therefore, not
  11516. every thread needs a copy of every data structure. pthread_keycreate registers
  11517. a destructor function to be called for each key when the thread exits. This
  11518. function may free the storage and otherwise clean things up.
  11519. It's best to make variables thread-private whenever possible. The access cost
  11520. of thread-specific data is much less compared to lock/unlock overhead, and the
  11521. associated code can be written using the simpler, uni processor model.
  11522.  
  11523.  
  11524. Other Methods
  11525.  
  11526.  
  11527. One can dedicate a thread to particular functions, which makes data and code
  11528. thread-private by convention. For example, one could contrive the program so
  11529. that all calls to X-Window routines occur in a particular thread. The initial
  11530. thread--the one which runs main--is the safest one to dedicate to older
  11531. libraries. For backward compatibility, many threads packages associate the
  11532. initial thread with global resources, such as errno.
  11533. Sometimes global shared variables can be used in a multi-threaded program
  11534. without additional protection. For example, a Boolean variable used as a flag
  11535. can be set or cleared without synchronization. The code sequence:
  11536. lock;
  11537. flag=1;
  11538. unlock
  11539. is silly. But, if two or more variables need to change "simultaneously," then
  11540. locking may be needed to protect the program state, disallowing certain
  11541. intermediate states.
  11542. Statistics counters employ another kind of update which may go unprotected. An
  11543. expression like ++count is a multi-step update of a global variable, and is a
  11544. candidate for locking. But if all you ever care about are zero/non-zero values
  11545. of the count, no harm will result even if multiple processors collide on an
  11546. update. (The value will be at least 1.) This is worth mentioning because test
  11547. coverage tools often insert such counters. It is seldom worth the trouble to
  11548. make these tools thread-safe.
  11549.  
  11550.  
  11551. A Question of Semantics
  11552.  
  11553.  
  11554.  
  11555. Choosing whether to add locking or to make data thread-private is more than a
  11556. matter of mechanics. The bigger question concerns the thread model to be used.
  11557. Are threads to be independent or cooperating? The answer to this question
  11558. determines the implementation strategy.
  11559. If the threads are to be cooperating, meaning they produce or consume the same
  11560. stream of data, then the data must have internal locking and external
  11561. synchronization. In this model, the POSIX opendir/readdir facility, for
  11562. example, would operate with a shared data structure. An application would want
  11563. to have threads alternately read or write directory entries.
  11564. Each independent thread, on the other hand, would produce or consume its own
  11565. stream of data. In this case, the buffering would be thread-private, and there
  11566. would be no need for internal locking. Either approach is viable, it just
  11567. depends upon the application. But what is the library writer supposed to do?
  11568. The answer may be to supply both.
  11569.  
  11570.  
  11571. A Thread-Safe C++ Library
  11572.  
  11573.  
  11574. If the underlying C implementation is thread-safe, then C++ is very close to
  11575. it. However, there are a few special considerations. First, the I/O streams
  11576. cin, cout, cerr, and clog are static objects provided by the library. Nothing
  11577. prevents two threads from simultaneously writing to the same output stream,
  11578. and thus corrupting its internal state.
  11579. Second, C++ uses an internal list to keep track of sizes of arrays of objects.
  11580. This is due to the fact that C++ lets you say
  11581. delete [] array-ref
  11582. which implies that the compiler/library has to determine the number of
  11583. elements. (For various reasons, most implementations can't store the size in
  11584. the same chunk of memory as the array itself.) Since an array may be allocated
  11585. on one thread and freed on another, this internal list has to be global.
  11586. Therefore it has to be protected during updates.
  11587. An important issue is whether I/O streams be thread-private or shared. Given
  11588. the nature of the interface, which is to perform I/O by the character, word,
  11589. or line, it doesn't do much good to provide locking within the interface
  11590. functions. For example, one thread may print hello and another print world,
  11591. but the screen output will be jumbled anyway unless the threads coordinate
  11592. their outputs in some way outside of the I/O library. Adding locking within
  11593. each I/O primitive call (inside the interface) will make the I/O library
  11594. thread-safe. Indeed, this was the approach taken for POSIX threads. The
  11595. problem is that it introduces a lot of overhead in the interest of safety.
  11596. Alternatively, you could establish the rule that I/O streams are
  11597. thread-private. Cooperating threads will need to synchronize outside of the
  11598. interface. (Maybe they would have to anyway.) Independent threads would read
  11599. or write streams as efficiently as in the single-threaded case.
  11600. Given this rule, the library only has to concern itself with the streams it
  11601. provides initially, namely: cin, cout, cerr, and clog. In C++, it's fairly
  11602. easy to provide a "wrapper class" that can operate on a thread-private version
  11603. of the original class.
  11604.  
  11605.  
  11606. Wrapper-Class Implementation
  11607.  
  11608.  
  11609. The wrapper class defines a small object that holds the thread-specific data
  11610. key, initial file descriptor, and other stream-initialization parameters. The
  11611. declared object has a static initializer which takes care of allocating the
  11612. key. Each of the pre-defined streams is both declared and replaced with a
  11613. macro, as follows:
  11614. extern ostream_withassign cout;// stream declaration
  11615. extern t_ostream_withassign __tcout;// stream wrapper
  11616. #define cout (__tcout.ref())
  11617. Any textual reference to cout is replaced with a call to the wrapper class,
  11618. which returns a reference to the thread-private copy of the stream. The
  11619. thread-private stream is not allocated and initialized until the first call to
  11620. the ref member function is made. This introduces the limitation that &cout may
  11621. not be used in an initializer list--because the address is no longer a
  11622. constant value, but must be computed at runtime. The initial thread points at
  11623. the pre-defined streams. Old binaries will therefore access the initial
  11624. thread's I/O streams. This permits backward compatibility with single-threaded
  11625. code.
  11626. The wrapper is a very small class that defines a static object (such as
  11627. __tcout) which is shared by all threads. The static initializer does three
  11628. things:
  11629. It allocates a key to access thread-private storage.
  11630. It stores the stream-initialization parameters it will need later.
  11631. It points the wrapper at the corresponding global stream.
  11632. Static initializers are called from the initial thread before main executes.
  11633. A pointer to the wrapper class is stored in thread-private storage. This
  11634. pointer is later presented as an argument to the destructor function called by
  11635. thread-exit processing. Therefore, the thread-private wrapper object and its
  11636. stream object are deleted when the thread exits.
  11637. The ref member function simply uses the key stored in the static object and
  11638. tries to look up the thread-private pointer to the wrapper. The very first
  11639. time a new thread calls any I/O function, ref will find that its
  11640. thread-private pointer is null. When the pointer is null, ref allocates a new
  11641. wrapper object and a new stream, then stores the pointer to its wrapper object
  11642. in thread-private storage. Subsequently, ref returns a reference to the stream
  11643. object assocated with the wrapper object, both of which are private to the
  11644. thread.
  11645. With this implementation, only threads that use stream I/O get copies of
  11646. stream objects. Threads can be created at any time, since I/O stream
  11647. allocation occurs on demand. The main cost added to implement thread-private
  11648. streams is one lookup in thread-private storage for every I/O call. This is
  11649. much cheaper than adding lock/unlock to every I/O call.
  11650.  
  11651.  
  11652. A C++ Wish List
  11653.  
  11654.  
  11655. It would be nicer if you could have redefined cout as an object of the wrapper
  11656. class instead of having to use a macro. In other words, you would want to say
  11657. extern t_ostream_withassign cout
  11658. Then you could rely on the user-defined type conversion facility to yield a
  11659. reference to the desired class. That is, you would simply define
  11660. operator ostream_withassign&() {return ref();}
  11661. in the wrapper class.
  11662. I tried this and it mostly works, but the following limitations were found:
  11663. Invocations of member functions didn't work. For example, cout.flush() had to
  11664. be transformed to ((ostream_withassign&)(cout)).flush(). Here is a case where
  11665. it would be nice to be able to overload the dot operator. Then you could say:
  11666. ostream_withassign& operator.()
  11667. {return ref();}
  11668. The result is that the wrapper class could substitute a reference to the
  11669. desired class that defines the member functions.
  11670. The compiler complains when the expression in a return statement does not
  11671. match the function's declaration. For example:
  11672. ostream_withassign& f()
  11673. {return cout;}
  11674. would work only if the compiler applied the user-defined type conversion.
  11675. There is a similar warning when the type of an initializer does not match the
  11676. type of the variable being initialized. For example,
  11677. ostream_withassign &os = cout
  11678. would work only if the compiler applied the user-defined type conversion. If
  11679. the user-defined type conversion were applied uniformly and it were possible
  11680. to overload the dot operator, then it would be easier to implement a very
  11681. useful form of indirection for any object.
  11682.  
  11683.  
  11684. Arrays of Objects
  11685.  
  11686.  
  11687.  
  11688. As I stated above, the other change needed for the C++ library was to add
  11689. locking around an internal list. This change is not visible to the library's
  11690. user. The implementation of the locking mechanics used is perhaps worthy of
  11691. discussion.
  11692. The list is protected with simple, mutual exclusion, using pthread_mutex_lock
  11693. and pthread_mutex_unlock in the underlying implementation. The list-handling
  11694. file has a definition at file scope like
  11695. static_thread_lock_def list_lock;
  11696. where thread_lock_def defines a static initializer that allocates and
  11697. initializes a pthread_mutex_t.
  11698. The body of the code where mutual exclusion is needed has the syntax:
  11699. { thread_lock L(list_lock);
  11700. // do something to the list
  11701. } //end of critical region
  11702. The braces are used to control the scope of L, which is a reference to the
  11703. lock. The class thread_lock acquires the pthread_mutex in its initializer, and
  11704. releases it in its destructor. Thus, the scope of L determines the bounds of
  11705. the critical region.
  11706. This could be considered just another stupid C++ trick except that, should
  11707. there be a return or an exception within the region, the destructor for L will
  11708. be called, which will release the lock. Thus, locking is integrated with
  11709. returns and exceptions because of this method.
  11710. There is a quiet gotcha in this implementation. Should you forget to supply a
  11711. variable name (in this case it's L), and write thread_lock(list_lock), C++
  11712. will lock then immediately unlock list_lock, leaving the critical region
  11713. unprotected.
  11714. The reason there is no warning has something to do with the great debate over
  11715. the lifetime of temporaries. For example, in a simple expression like X + Y
  11716. the compiler may have to make temporary copies of intermediate results, which
  11717. one would expect to get deleted at the end of the expression, not at the end
  11718. of the current scope. See your local language lawyer for details.
  11719.  
  11720.  
  11721. Conclusion
  11722.  
  11723.  
  11724. I successfully converted the Hewlett-Packard C++ library to a thread-safe
  11725. form. Along the way, I observed:
  11726. C++ is very close to being thread-safe, except for some potential uses of I/O
  11727. streams and arrays of objects.
  11728. Making global data thread-private is more efficient than adding locking.
  11729. I/O streams should be thread-private, because the shared use of a stream
  11730. requires external coordination anyway.
  11731. Uniform application of user-defined type conversion coupled with the ability
  11732. to overload the dot operator would be useful extensions. This would aid in the
  11733. creation of wrapper classes for indirect access to any object.
  11734. Locking based upon C++ scoping rules integrates locking with exceptions.
  11735.  
  11736.  
  11737.  
  11738.  
  11739.  
  11740.  
  11741.  
  11742.  
  11743.  
  11744.  
  11745.  
  11746.  
  11747.  
  11748.  
  11749.  
  11750.  
  11751.  
  11752.  
  11753.  
  11754.  
  11755.  
  11756.  
  11757.  
  11758.  
  11759.  
  11760.  
  11761.  
  11762.  
  11763.  
  11764.  
  11765.  
  11766.  
  11767.  
  11768.  
  11769.  
  11770.  
  11771.  
  11772.  
  11773.  
  11774. Image Processing, Part 9: Histogram-Based Image Segmentation
  11775.  
  11776.  
  11777. Dwayne Phillips
  11778.  
  11779.  
  11780. The author works as a computer and electronics engineer with the U.S.
  11781. Department of Defense. He has a PhD in Electrical and Computer Engineering
  11782. from Louisiana State University. His interests include computer vision,
  11783. artificial intelligence, software engineering; and programming languages.
  11784.  
  11785.  
  11786.  
  11787.  
  11788. Introduction to the Series of Articles
  11789.  
  11790.  
  11791. This is the ninth in a series of articles on images and image processing.
  11792. Previous articles discussed reading, writing, displaying, and printing images
  11793. (TIFF format), histograms, edge detection, spatial frequency filtering, and
  11794. sundry image operations. This article will discuss simple image segmentation
  11795. based on histograms and image thresholding.
  11796. Image segmentation is the process of dividing an image into regions or
  11797. objects. It is the first step in the field of image analysis. Image processing
  11798. displays images and alters them to make them look "better," while image
  11799. analysis tries to discover what is in the image.
  11800. The basic idea of image segmentation is to group individual pixels (dots in
  11801. the image) together into regions if they are similar. Similar can mean they
  11802. are the same intensity (shade of gray), form a texture, line up in a row,
  11803. create a shape, etc. There are many techniques available for image
  11804. segmentation, and they vary in complexity, power, and area of application.
  11805.  
  11806.  
  11807. Histogram-Based Image Segmentation
  11808.  
  11809.  
  11810. Histogram-based image segmentation is one of the simplest and most often used
  11811. segmentation techniques. It uses the histogram to select the gray levels for
  11812. grouping pixels into regions. In a simple image there are two entities: the
  11813. background and the object. The background is generally one gray level and
  11814. occupies most of the image. Therefore, its gray level is a large peak in the
  11815. histogram. The object or subject of the image is another gray level, and its
  11816. gray level is another, smaller peak in the histogram.
  11817. Figure 1 shows an image example and Figure 2 shows its histogram. The tall
  11818. peak at gray level 2 indicates it is the primary gray level for the background
  11819. of the image. The secondary peak in the histogram at gray level 8 indicates it
  11820. is the primary gray level for the object in the image. Figure 3 shows the
  11821. image of Figure 1 with all the pixels except the 8s blanked out. The object is
  11822. a happy face.
  11823. This illustrates histogram-based image segmentation. The histogram will show
  11824. us the gray levels of the background and the object. The largest peak
  11825. represents the background and the next largest peak the object. We choose a
  11826. threshold point in the valley between the two peaks and threshold the image.
  11827. Thresholding takes any pixel whose value is on the object side of the point
  11828. and sets it to one; it sets all others to zero. The histogram peaks and the
  11829. valley between them are the keys.
  11830. The idea of histogram-based segmentation is simple, but there can be problems.
  11831. Where should you set the threshold point for the image of Figure 1? If you
  11832. choose the point mid-way between the two peaks (threshold point=5), you
  11833. produce the image of Figure 4. This is not the happy face object desired. If
  11834. you choose the valley floor values of 4 or 5 as the threshold point, you also
  11835. have a poor result. The best threshold point would be 7, but how could you
  11836. know that without using trial and error?
  11837. This example is difficult because there are only ten gray levels and the
  11838. object (happy face) is small. In practice, the techniques discussed below will
  11839. perform adequately, but there will be problems. Automatic techniques will
  11840. fail, and you may have to resort to manual methods.
  11841.  
  11842.  
  11843. Preprocessing: Histogram Equalization and Histogram Smoothing
  11844.  
  11845.  
  11846. Histogram-based segmentation depends on the histogram of the image. Therefore,
  11847. you must prepare the image and its histogram before analyzing it. The first
  11848. step is histogram equalization (Phillips, August 1991). Histogram equalization
  11849. attempts to alter an image so its histogram is flat and spreads out over the
  11850. entire range of gray levels. The result is an image with better contrast.
  11851. Photograph 1 shows an aerial image of several house trailers with its
  11852. histogram. The contrast is poor and it would be very difficult to find objects
  11853. based on its histogram. Photograph 2 shows the result of performing histogram
  11854. equalization. The contrast is much better and the histogram is spread out over
  11855. the entire range of gray levels. Photograph 3 shows the result of performing
  11856. high-pass filtering (Phillips, October 1992) on the image of photograph 2. It
  11857. is easy to see the house trailers, sidewalks, trees, bushes, gravel roads, and
  11858. parking lots.
  11859. The next preprocessing step is histogram smoothing. When examining a
  11860. histogram, you look at peaks and valleys. Too many tall, thin, peaks and deep
  11861. valleys will cause problems. Smoothing the histogram removes these spikes and
  11862. fills in empty canyons while retaining the same basic shape of the histogram.
  11863. Figure 5 shows the result of smoothing the histogram given in Figure 2. You
  11864. can still see the peaks corresponding to the object and background, but the
  11865. peaks are shorter and the valleys are filled. Photograph 4 shows another
  11866. example of histogram smoothing. The histogram on the left is the original with
  11867. several spikes and canyons. The histogram on the right has been smoothed. I
  11868. will analyze this in the segmentation.
  11869. Smoothing a histogram is an easy operation. It replaces each point with the
  11870. average of it and its two neighbors. Listing 1 shows the smooth_histogram
  11871. function that performs this operation.
  11872.  
  11873.  
  11874. Thresholding and Region Growing
  11875.  
  11876.  
  11877. There are two more topics common to all the methods of image segmentation:
  11878. image thresholding and region growing. Image thresholding sets the pixels in
  11879. the image to one or zero. Listing 2 shows the routine threshold_image_array
  11880. that accomplishes this task.
  11881. The difficult task is region growing. Figure 6 shows the result of
  11882. thresholding Figure 1 correctly. The "object" in Figure 6 is a happy face. It
  11883. comprises three different regions (two eyes and the smile). Region growing
  11884. takes this image, groups the pixels in each separate region, and gives them
  11885. unique labels. Figure 7 shows the result of region growing performed on Figure
  11886. 6. Region growing grouped and labeled one eye as region one, the other eye as
  11887. region two, and the smile as region three.
  11888. Figure 8 shows the algorithm for region growing. It begins with an image array
  11889. g comprising zeros and pixels set to a value. The algorithm loops through the
  11890. image array looking for a g(i,j) == value. When it finds such a pixel, it
  11891. calls the label_and_check_neighbor routine. label_and_check_neighbor sets the
  11892. pixel to g_label (the region label) and examines the pixel's eight neighbors.
  11893. If any of the neighbors equal value, they are pushed onto a stack. When
  11894. control returns to the main algorithm, each pixel on the stack is popped and
  11895. sent to label_and_check_neighbor. All the points on the stack equaled value,
  11896. so you set them and check their neighbors. After setting all the pixels in the
  11897. first region, you increment g_label and move on looking for the next region.
  11898. Listing 3 shows the functions that implement region growing with grow being
  11899. the primary routine. grow runs through the region-growing algorithm and calls
  11900. label_and_check_neighbor shown next in Listing 3.
  11901. The grow and label_and_check_neighbor functions follow the region-growing
  11902. algorithm step for step. The only unusual item is the stack. There are several
  11903. ways to implement a stack. I used a simple array and a file. I did this
  11904. because a region could be as large as ROWSxCOLS (10,000 in the C Image
  11905. Processing System), and the stack must be large enough to hold that many
  11906. points. I push points onto the stack array by putting them in the array and
  11907. moving the top of stack pointer. When the stack array becomes full, I write it
  11908. to the stack file. If the array fills again, I push the array onto the top of
  11909. the stack file. I pop points off the stack array by reading them and
  11910. decrementing the top of stack pointer. When the stack array is empty, I pop an
  11911. array off the stack file. The final four functions of Listing 3
  11912. (push_data_onto_stack_file, pop_data_off_of_stack_file, append_stack_files,
  11913. and copy_stack_files) implement the push and pop functions for the stack array
  11914. and file.
  11915.  
  11916.  
  11917. Histogram-Based Segmentation Techniques
  11918.  
  11919.  
  11920. There are four segmentation techniques: the manual technique, histogram peak
  11921. technique, histogram valley technique, and an adaptive technique.
  11922.  
  11923.  
  11924. The Manual Technique
  11925.  
  11926.  
  11927.  
  11928. In the manual technique the user inspects an image and its histogram manually.
  11929. Trial and error come into play and the result is as good as you want it to be.
  11930. I used Photograph 4 as the input for all the segmentation examples. Photograph
  11931. 5, Photograph 6, and Photograph 7 show the result of segmentation using three
  11932. different thresholds. The result in Photograph 5 used a high of 255 and a low
  11933. of 125. The segmentation included the white gravel roads as well as the house
  11934. trailers and sidewalks. The result in Photograph 6 used a high of 255 and a
  11935. low of 175. The gravel roads begin to disappear, but the house trailers and
  11936. sidewalks remain. Photograph 7 shows the result using a high of 255 and a low
  11937. of 225. This segmentation only finds the four dominant house trailers. Which
  11938. answer is correct? That depends on what you wanted to find.
  11939. Note, all image segmentations will appear rough. You can perform additional
  11940. processing to make the result more pleasing to the eye, but that is not the
  11941. purpose of segmentation. The purpose is to break the image into pieces so
  11942. later computer processing can interpret their meaning. The output is for
  11943. computer not human consumption. Also note how difficult it is for the
  11944. computer, even with manual aid, to find objects that are trivial for humans to
  11945. see. Anyone could trace over the input image and outline the objects better
  11946. than the segmentation process.
  11947. Listing 4 shows the code that implements manual segmentation. The function
  11948. manual_threshold_segmentation has the same form as the other application
  11949. functions in this series. It creates the output image file if it does not
  11950. exist, reads in the input data, thresholds it (Listing 2), grows regions if
  11951. requested (Listing 3), and writes the output.
  11952. manual_threshold_segmentation has the usual inputs (image file names, image
  11953. arrays, etc.) as well as the high and low threshold values, and the value and
  11954. segment parameters. The value parameter specifies the value at which to set a
  11955. pixel if it falls between the high and low thresholds. You usually set value
  11956. equal to 1 since those pixels outside the high-low range are set to zero. The
  11957. segment parameter specifies whether or not to grow regions after thresholding.
  11958. Sometimes you only want to threshold an image and not grow regions. The two
  11959. operations are identical except for the last step. If segment == 1, you call
  11960. the region-growing routines.
  11961. Manual segmentation is good for fine tuning and getting a feel for the
  11962. operation. Its trial-and-error nature, however, makes it time consuming and
  11963. impractical for many applications. You need techniques that examine the
  11964. histogram and select threshold values automatically.
  11965.  
  11966.  
  11967. Histogram Peak Technique
  11968.  
  11969.  
  11970. The first such technique uses the peaks of the histogram. This technique finds
  11971. the two peaks in the histogram corresponding to the background and object of
  11972. the image. It sets the threshold half way between the two peaks. Look back at
  11973. the smoothed histogram in Figure 5. The background peak is at 2 and the object
  11974. peak is at 7. The midpoint is 4, so the low threshold value is 4 and the high
  11975. is 9.
  11976. The peaks technique is straightforward except for two items. In the histogram
  11977. in Figure 5, you'll note the peak at 7 is the fourth highest peak. The peaks
  11978. at 1 and 3 are higher, but they are part of the background mountain of the
  11979. histogram and do not correspond to the object. When you search the histogram
  11980. for peaks, you must use a peak spacing to ensure the highest peaks are
  11981. separated. If you did not, then you would choose 2 as the background peak and
  11982. 1 as the object peak. Figure 9 shows the disastrous effect of this.
  11983. The second item to watch carefully is determining which peak corresponds to
  11984. the background and which corresponds to the object. Suppose an image had the
  11985. histogram shown in Figure 10. Which peak corresponds to the background? The
  11986. peak for gray level 8 is the highest, but it corresponds to the object not the
  11987. background. The reason is the mountain surrounding the peak at gray level 2
  11988. has a much greater area than that next to gray level 8. Therefore, gray levels
  11989. 0 through 6 occupy the vast majority of the image, and they are the
  11990. background.
  11991. Listing 5 shows the source code to implement the peaks technique.
  11992. peak_threshold_segmentation is the primary function. It checks for the
  11993. existence of the output image, reads the input image, and calculates and
  11994. smoothes the histogram. Next, it calls new functions to find the histogram
  11995. peaks and determine the high and low threshold values. Finally, it thresholds
  11996. the image, performs region growing if desired, and writes the result image to
  11997. the output file.
  11998. The functions find_peaks and insert_into_peaks in Listing 5 analyze the
  11999. histogram to find the peaks for the object and background. These functions
  12000. build a list of histogram peaks. There are several ways to do this. I used an
  12001. array of values. find_peaks loops through the histogram and calls
  12002. insert_into_peaks, which puts the histogram values in the proper place in the
  12003. array. find_peaks ends by looking at the spacing between the largest peaks to
  12004. ensure we do not have a disaster such as shown in Figure 9.
  12005. The function peaks_high_low takes the two peaks from find_peaks and calculates
  12006. the high- and low-threshold values for segmentation. peaks_high-low examines
  12007. the mountains next to the peaks as illustrated in Figure 10. It then finds the
  12008. mid-point between the peaks and sets the high and low threshold values.
  12009. Photograph 8 shows the result of applying the peaks technique to the image of
  12010. Photograph 4. The peaks technique found the two peaks at 255 and 77. The
  12011. mid-point is 166, so the high threshold is 255 and the low threshold is 166.
  12012. This is a reasonably good segmentation of Photograph 4.
  12013.  
  12014.  
  12015. Histogram Valley Technique
  12016.  
  12017.  
  12018. The second automatic technique uses the peaks of the histogram, but
  12019. concentrates on the valley between them. Instead of setting the mid-point
  12020. arbitrarily half way between the two peaks, the valley technique searches
  12021. between the two peaks to find the lowest valley.
  12022. Look back at the histogram of Figure 10. The peaks are at gray levels 2 and 8
  12023. and the peaks technique would set the midpoint at 5. In contrast, the valley
  12024. technique searches from 2 through 8 to find the lowest valley. In this case,
  12025. the "valleypoint" is at gray level 7.
  12026. Listing 6 shows the code that implements the valley technique. The primary
  12027. function is valley_threshold_segmentation. It checks for the output file,
  12028. reads the input image, calculates and smoothes the histogram, and finds the
  12029. peaks as peak_threshold_segmentation did. It finds the valley-point via the
  12030. functions valley_high_low, find_valley_point, and insert_into_deltas.
  12031. find_valley_point starts at one peak and goes to the next inserting the
  12032. histogram values into a deltas array via the insert_into_deltas function. This
  12033. uses an array to create a list of deltas in the same manner as
  12034. insert_into_peaks did in Listing 5. Once you have the valleypoint,
  12035. valley_high_low checks the mountains around the peaks to ensure you associate
  12036. the peaks with the background and object correctly.
  12037. Photograph 9 shows the result of applying the valley technique to the image in
  12038. Photograph 4. It found the peaks at 77 and 255 and went from 77 up to 255
  12039. looking for the lowest valley. It pinpointed the lowest valley at gray level
  12040. 241.
  12041.  
  12042.  
  12043. Adaptive Histogram Technique
  12044.  
  12045.  
  12046. The final technique uses the peaks of the histogram in a first pass and adapts
  12047. itself to the objects found in the image in a second pass (Castleman 1979). In
  12048. the first pass, the adaptive technique calculates the histogram for the entire
  12049. image. It smoothes the histogram and uses the peaks technique to find the high
  12050. and low threshold values.
  12051. In the second pass, the technique works on each ROWSxCOLS area of the image
  12052. individually. In each area, it segments using the high and low values found
  12053. during the first pass. Then, it calculates the mean value for all the pixels
  12054. segmented into background and object. It uses these means as new peaks and
  12055. calculates new high and low threshold values for that ROWSxCOLS area. Now, it
  12056. segments that area again using the new values.
  12057. Listing 7 shows the code that implements the adaptive technique with
  12058. adaptive_threshold_segmentation being the primary function. It is very similar
  12059. to the peak_threshold_segmentation function of Listing 5 in that it uses all
  12060. that code for its first pass. The second pass starts by calling
  12061. threshold_and_find_means. This function thresholds the image array into
  12062. background and object and calculates the mean pixel value for each. The second
  12063. pass continues by using peaks_high_low to find new threshold values based on
  12064. the background and object means. Finally, you threshold the image using these
  12065. new threshold values.
  12066. Photograph 10 shows the result of applying the adaptive technique to the image
  12067. of Photograph 4. The first pass found the high- and low-threshold values to be
  12068. 255 and 166. On the left side of the photograph, the second pass thresholded
  12069. the image array and found the background mean to be 94 and the object mean to
  12070. be 205. The new threshold values were 255 and 149. On the right side of the
  12071. photograph, the background and object means were 84 and 200 and the new
  12072. threshold values were 255 and 142.
  12073.  
  12074.  
  12075. Integrating Histogram-Based Segmentation into the C Image Processing System
  12076.  
  12077.  
  12078. Listing 8 shows the new code for the main routine of CIPS. I've added the
  12079. options of thresholding and segmentation using the four techniques discussed
  12080. above in cases 16 and 17. Listed next are the changes to the CIPS main menu
  12081. and the two functions that interact with the user to obtain the processing
  12082. options.
  12083. Listing 9 shows a stand-alone application program for thresholding and
  12084. segmenting entire image files. It is command- line driven and calls the
  12085. functions shown in the earlier listings.
  12086.  
  12087.  
  12088. Conclusions
  12089.  
  12090.  
  12091. This installment in the series introduced image segmentation. This is the
  12092. first step in locating and labeling the contents of an image. The techniques
  12093. discussed work on simple images with good contrast and gray level separation
  12094. between the object and background. You will need other techniques to attack
  12095. more complex images.
  12096. References
  12097. Castleman, Kenneth R. 1979. Digital Image Processing. Prentice-Hall.
  12098. Phillips, Dwayne. August 1991. "Image Processing, Part 4: Histograms and
  12099. Histogram Equalization," The C Users Journal.
  12100. Phillips, Dwayne. October 1992. "Image Processing, Part 7: Spatial Frequency
  12101. Filtering," The C Users Journal.
  12102. Figure 1 An image example
  12103. 22222232221222212222
  12104. 32222321250132123132
  12105. 22588897777788888232
  12106. 12988877707668882122
  12107.  
  12108. 22888892326669893213
  12109. 21278221222666665222
  12110. 22002222220226660225
  12111. 21221231223266622321
  12112. 32238852223266821222
  12113. 21288888342288882232
  12114. 22328888899888522121
  12115. 22123988888889223422
  12116. 23222278888882022122
  12117. 22232323883212123234
  12118. 25221212222222222222
  12119. 22122222320222202102
  12120. 20222322412212223221
  12121. 22221212222222342222
  12122. 21222222221222222142
  12123. Figure 2 A histogram of the image in Figure 1
  12124. Figure 3 The image in Figure 1 with all the pixels except the 8's blanked out
  12125. --------------------
  12126. --------------------
  12127. ---888------88888---
  12128. ---888-------888----
  12129. --8888--------8-----
  12130. ----8---------------
  12131. --------------------
  12132. --------------------
  12133. ----88--------8-----
  12134. ---88888----8888----
  12135. ----88888--888------
  12136. ------8888888-------
  12137. -------888888-------
  12138. --------88----------
  12139. --------------------
  12140. --------------------
  12141. --------------------
  12142. --------------------
  12143. --------------------
  12144. Figure 4 Figure 1 with athreshold point of 5
  12145. 00000000000000000000
  12146. 00000000000000000000
  12147. 00011111111111111000
  12148. 00111111101111110000
  12149. 00111110001111110000
  12150. 00011000000111110000
  12151. 00000000000001110000
  12152. 00000000000011100000
  12153. 00001100000011100000
  12154. 00011111000011110000
  12155. 00001111111111000000
  12156. 00000111111111000000
  12157. 00000011111110000000
  12158. 00000000110000000000
  12159. 00000000000000000000
  12160. 00000000000000000000
  12161. 00000000000000000000
  12162. 00000000000000000000
  12163. 00000000000000000000
  12164. Figure 5 The result of smoothing the histogram given in Figure 2
  12165. Figure 6 The result of correctly thresholding Figure 1
  12166. 00000000000000000000
  12167.  
  12168. 00000000000000000000
  12169. 00011100000011111000
  12170. 00011100000001110000
  12171. 00111100000000100000
  12172. 00001000000000000000
  12173. 00000000000000000000
  12174. 00000000000000000000
  12175. 00001100000000100000
  12176. 00011111000011110000
  12177. 00001111100111000000
  12178. 00000011111110000000
  12179. 00000001111110000000
  12180. 00000000110000000000
  12181. 00000000000000000000
  12182. 00000000000000000000
  12183. 00000000000000000000
  12184. 00000000000000000000
  12185. 00000000000000000000
  12186. Figure 7 The result of region growing performed on Figure 6
  12187. 00000000000000000000
  12188. 00000000000000000000
  12189. 00011100000022222000
  12190. 00011100000002220000
  12191. 00111100000000200000
  12192. 00001000000000000000
  12193. 00000000000000000000
  12194. 00000000000000000000
  12195. 00003300000000300000
  12196. 00033333000033330000
  12197. 00003333300333000000
  12198. 00000033333330000000
  12199. 00000003333330000000
  12200. 00000000330000000000
  12201. 00000000000000000000
  12202. 00000000000000000000
  12203. 00000000000000000000
  12204. 00000000000000000000
  12205. 00000000000000000000
  12206. Figure 8 Pseudocode for region growing
  12207. 1. Given an image g with m rows and n columns
  12208.  g(i,j) for i=1,m j=1,n
  12209.  g(i,j) = value for object
  12210.  = 0 for background
  12211. 2. set g_label=2 this is the label value
  12212. 3. for (i=0; i<m; i++)
  12213.  scan ith row
  12214.  for (j=0; j<n; j++)
  12215.  check jth element
  12216.  stack_empty = true
  12217.  if g(i,j) == value
  12218.  label_and_check_neighbor(g(i,j),g_label)
  12219.  while stack_empty = false do
  12220.  pop element (i,j) off the stack
  12221.  label_and_check_neighbor(g(i,j),g_label)
  12222.  end while
  12223.  g_label = g_label + 1
  12224.  end of checking jth element
  12225.  end of scanning ith row
  12226. 4. The End
  12227.  
  12228.  ---------------------------------------
  12229. procedure label_and_check_neighbor(g(r,e), g_label)
  12230.  g(r,e) = g_label
  12231.  for (R=r-1; r<=r+1; R++)
  12232.  for (E=e-1; e<=e+1; e++)
  12233.  if g(R,E) == value then
  12234.  push (R,E) onto the stack
  12235.  stack_empty = false
  12236.  end if
  12237.  end loop over E
  12238.  end loop over R
  12239. end procedure label_and_check_neighbor
  12240. Figure 9 Result of incorrectpeak separation when applying the histogram peak
  12241. technique to Figure 1
  12242. ----------*----*----
  12243. -------*--**--*--*--
  12244. --------------------
  12245. *--------*-------*--
  12246. ------------------*-
  12247. -*-----*------------
  12248. --**------*-----*---
  12249. -*--*--*-----------*
  12250. ----------------*---
  12251. -*------------------
  12252. -----------------*-*
  12253. --*-----------------
  12254. --------------*--*--
  12255. ------------*-*-----
  12256. ----*-*-------------
  12257. --*-------*----*-**-
  12258. -*-------*--*------*
  12259. ----*-*-------------
  12260. -*--------*------*--
  12261. Figure 10 A histogram in which the highest peak does not correspond to the
  12262. background
  12263. Photograph 1 Aerial image with poor contrast and histogram
  12264. Photograph 2 Result of histogram on Photograph 1
  12265. Photograph 3 Result of high-pass filtering on Photograph 2
  12266. Photograph 4 Image portion with histogram and smoothed histogram
  12267. Photograph 5 Threshold of Photograph 4 withhigh=225 and low=125
  12268. Photograph 6 Threshold of Photograph 4 withhigh=255 and low=175
  12269. Photograph 7 Threshold of Photograph 4 withhigh=255 and low=225
  12270. Photograph 8 Threshold of Photograph 4 using peaks technique (high=255,
  12271. low=166)
  12272. Photograph 9 Threshold of Photograph 4 using valleytechnique (high=255,
  12273. low=241)
  12274. Photograph 10 Threshold of Photograph 4 using adaptive technique (high=255,
  12275. low=149 for left side; high=255, low=142 for right side)
  12276.  
  12277. Listing 1 Function for smoothing a histogram
  12278. /*********************************************
  12279. *
  12280. * smooth_histogram(...
  12281. *
  12282. * This function smoothes the input histogram
  12283. * and returns it. It uses a simple averaging
  12284. * scheme where each point in the histogram
  12285. * is replaced by the average of itself and
  12286. * the two points on either side of it.
  12287. *
  12288. *********************************************/
  12289.  
  12290. smooth_histogram(histogram)
  12291. unsigned long histogram[];
  12292.  
  12293. {
  12294. int i;
  12295. unsigned long new_hist[GRAY_LEVELS+1];
  12296.  
  12297. zero_histogram(new_hist);
  12298.  
  12299. new_hist[0] = (histogram[0] + histogram[1])/2;
  12300. new_hist[GRAY_LEVELS] =
  12301. (histogram[GRAY_LEVELS] +
  12302. histogram[GRAY_LEVELS-1])/2;
  12303.  
  12304. for(i=1; i<GRAY_LEVELS; i++){
  12305. new_hist[i](histogram[i-1] +
  12306. histogram[i] +
  12307. histogram[i+1])/3;
  12308. }
  12309.  
  12310. for(i=0; i<=GRAY_LEVELS; i++)
  12311. histogram[i] = new_hist[i];
  12312.  
  12313. } /* ends smooth_histogram */
  12314. /* End of File */
  12315.  
  12316.  
  12317. Listing 2 Function for image thresholding
  12318. /*********************************************
  12319. *
  12320. * threshold_image_array(...
  12321. *
  12322. * This function thresholds an input image array
  12323. * and produces a binary output image array.
  12324. * If the pixel in the input array is between
  12325. * the hi and low values, then it is set to value.
  12326. * Otherwise, it is set to 0.
  12327. *
  12328. *********************************************/
  12329.  
  12330. threshold_image_array(in_image, out_image, hi, low, value)
  12331. short hi, low, in image[ROWS][COLS],
  12332. out_image[ROWS][COLS], value;
  12333. {
  12334. int counter = 0, i, j;
  12335. for(i=0; i<ROWS; i++){
  12336. for(j=0; j<COLS; j++){
  12337. if(in_image[i][j] >= low &&
  12338. in_image[i][j] <= hi){
  12339. out_image[i][j] = value;
  12340. counter++;
  12341. }
  12342. else
  12343. out_image[i][j] = 0;
  12344. } /* ends loop over j */
  12345. ) /* ends loop over i */
  12346. printf("\n\tTIA> set %d points", counter);
  12347. } /* ends threshold_image_array */
  12348. /* End of File */
  12349.  
  12350.  
  12351. Listing 3 Functions that implement region growing
  12352.  
  12353. /*********************************************
  12354. * grow(...
  12355. * This function is an object detector.
  12356. * Its input is an binary image array
  12357. * containing 0's and value's.
  12358. * It searches through the image and connects
  12359. * the adjacent values.
  12360. **********************************************/
  12361.  
  12362. grow(binary, value)
  12363. short binary[ROWS][COLS],
  12364. value;
  12365. {
  12366. char name[80];
  12367.  
  12368. int first_call,
  12369. i,
  12370. j,
  12371. object_found,
  12372. pointer,
  12373. pop_i,
  12374. pop_j,
  12375. stack_empty,
  12376. stack_file_in_use;
  12377.  
  12378. short g_label, stack[STACK_SIZE][2];
  12379.  
  12380. /***********************************
  12381. * Now begin the process of growing
  12382. * regions.
  12383. *************************************/
  12384.  
  12385. g_label = 2;
  12386. object_found = 0;
  12387. first_call = 1;
  12388.  
  12389. for(i=0; i<ROWS; i++){
  12390. for(j=0; j<COLS; j++){
  12391. stack file in use = 0;
  12392. stack_empty = 1;
  12393. pointer = -1;
  12394.  
  12395. /*********************************
  12396. * Search for the first pixel of
  12397. * a region.
  12398. **********************************/
  12399.  
  12400. if(binary[i][j] == value){
  12401. label_and_check_neighbor(binary, stack, g_label,
  12402. &stack_empty, &pointer, i, j,
  12403. value, &stack_file_in_use,
  12404. &first_call);
  12405. object_found = 1;
  12406. } /* ends if binary[i]j] == value */
  12407.  
  12408. /******************************
  12409. * If the stack is not empty,
  12410. * pop the coordinates of
  12411. * the pixel off the stack
  12412.  
  12413. * and check its 8 neighbors.
  12414. *******************************/
  12415.  
  12416. while(stack_empty == 0){
  12417. pop_i = stack[pointer][0]; /* POP */
  12418. pop_j = stack[pointer][1]; /* OPERATION */
  12419. --pointer;
  12420. if(pointer <= 0){
  12421. if(stack_file_in_use){
  12422. pop_data_off_of_stack_file(
  12423. stack,
  12424. &pointer,
  12425. &stack_file_in_use);
  12426. } /* ends if stack_file_in_use */
  12427. else{
  12428. pointer = 0;
  12429. stack_empty = 1;
  12430. } /* ends else stack file is
  12431. not in use */
  12432. } /* ends if point <= 0 */
  12433.  
  12434. label_and_check_neighbor(binary,
  12435. stack, g_label,
  12436. &stack_empty,
  12437. &pointer, pop_i,
  12438. pop_j, value,
  12439. &stack_file_in_use,
  12440. &first_call);
  12441. } /* ends while stack_empty == 0 */
  12442.  
  12443. if(object_found == 1){
  12444. object_found = 0;
  12445. ++g_label;
  12446. } /* ends if object_found == 1 */
  12447.  
  12448. } /* ends loop over j */
  12449. } /* ends loop over i */
  12450.  
  12451. printf("\nGROW> found %d objects", g_label);
  12452.  
  12453. } /* ends grow */
  12454.  
  12455. /********************************************
  12456. * label_and_check_neighbors(...
  12457. * This function labels a pixel with an object
  12458. * label and then checks the pixel's 8
  12459. * neighbors. If any of the neigbors are
  12460. * set, then they are also labeled.
  12461. **********************************************/
  12462.  
  12463. label_and_check_neighbor(binary_image, stack,
  12464. g_label, stack_empty,
  12465. pointer, r, e, value,
  12466. stack_file_in_use,
  12467. first_call)
  12468. int e,
  12469. *first_call,
  12470. *pointer,
  12471. r,
  12472.  
  12473. *stack_empty,
  12474. *stack_file_in_use;
  12475.  
  12476. short binary_image [ROWS] [COLS],
  12477. g_label,
  12478. stack[STACK_SIZE][2],
  12479. value;
  12480. {
  12481. int already_labeled = 0,
  12482. i, j;
  12483.  
  12484. if (binary_image[r][e] == g_label)
  12485. already_labeled = 1;
  12486.  
  12487. binary_image[r][e] = g_label;
  12488.  
  12489. /*************************************
  12490. * Look at the 8 neighors of the
  12491. * point r,e.
  12492. * Ensure the points you are checking
  12493. * are in the image, i.e. not less
  12494. * than zero and not greater than
  12495. * ROWS-1 or COLS-1.
  12496. **************************************/
  12497.  
  12498. for(i=(r-1); i<=(r+1); i++){
  12499. for(j=(e-1); j<=(e+1); j++){
  12500.  
  12501. if((i>=0) && (i<=ROWS-1) && (j>=0) && (j<=COLS-1)) {
  12502.  
  12503. if(binary_image[i][j] == value){
  12504. *pointer = *pointer + 1;
  12505. stack[*pointer][0] = i; /* PUSH */
  12506. stack[*pointer][1] = j; /* OPERATION */
  12507. *stack_empty = 0;
  12508.  
  12509. if(*pointer >= (STACK_SIZE -
  12510. STACK_FILE_LENGTH)){
  12511. push_data_onto_stack_file(stack,
  12512. pointer, first_call);
  12513. *stack_file_in_use = 1;
  12514. } /* ends if *pointer >=
  12515. STACK_SIZE - STACK_FILE_LENGTH*/
  12516.  
  12517. } /* end of if binary_image == value */
  12518. } /* end if i and j are on the image */
  12519. } /* ends loop over i rows */
  12520. } /* ends loop over j columns */
  12521. } /* ends label_and_check_neighbors */
  12522.  
  12523. /***************************************
  12524. * push_data_onto_stack_file(...
  12525. * This function takes the stack array
  12526. * and pushes it onto the stack file.
  12527. *****************************************/
  12528.  
  12529. push_data_onto_stack_file(stack, pointer, first_call)
  12530. int *first_call, *pointer;
  12531. short stack[STACK_SIZE][2];
  12532.  
  12533. {
  12534. char backup_file_name[MAX_NAME_LENGTH];
  12535. FILE *backup_file_pointer, *stack_file_pointer;
  12536. int diff, i;
  12537. short holder[STACK_FILE_LENGTH][2];
  12538.  
  12539. printf("\nSFO> Start of push_data_onto_stack ");
  12540.  
  12541. diff = STACK_SIZE - STACK_FILE_LENGTH;
  12542.  
  12543. /*****************************************
  12544. * Copy the elements to be stored to the
  12545. * stack file into holder
  12546. ******************************************/
  12547.  
  12548. for(i=0; i<STACK_FILE_LENGTH; i++){
  12549. holder[i][0] = stack[i][0];
  12550. holder[i][1] = stack[i][1];
  12551. }
  12552. /*****************************************
  12553. * Move the elements of the stack down
  12554. *****************************************/
  12555.  
  12556. for(i=0; i<diff; i++){
  12557. stack[i][0] = stack[i + STACK_FILE_LENGTH][0];
  12558. stack[i][1] = stack[i + STACK_FILE_LENGTH][1];
  12559. }
  12560.  
  12561. /*****************************************
  12562. * Fill the top of the stack with zeros
  12563. *****************************************/
  12564.  
  12565. for(i=diff; i<STACK_SIZE; i++){
  12566. stack[i][0] = 0;
  12567. stack[i][1] = 0;
  12568. }
  12569.  
  12570. *pointer = *pointer - STACK_FILE_LENGTH;
  12571.  
  12572. /*************************************************
  12573. * Store the holder array into the stack file.
  12574. * Open the stack file for writing in binary
  12575. * mode. If the file does not exist it will be
  12576. * created. If the file does exist it will be
  12577. * over written.
  12578. * PUSH - IF first_time == 1 then write to stack
  12579. * ELSE write to stack.bak
  12580. * append stack onto stack.bak
  12581. * copy stack.bak to stack
  12582. * this has the effect of writing
  12583. * to the beginning of the stack.
  12584. ************************************************/
  12585.  
  12586. if(*first_call == 1){
  12587.  
  12588. *first_call = *first_call + 1;
  12589. if((stack_file_pointer = fopen(STACK_FILE,"wb"))
  12590. == NULL)
  12591. printf("\nSFO> Could not open stack file");
  12592.  
  12593. else{
  12594. /*printf("\n\nSFO> Writing to stack file");*/
  12595. fwrite(holder, sizeof(holder), 1, stack_file_pointer);
  12596. fclose(stack_file_pointer);
  12597. } /* ends else could not open stack_file */
  12598.  
  12599. } /* ends if *first_call == 1 */
  12600. else{ /* else stack file has been used already */
  12601. strcpy(backup_file_name, STACK_FILE);
  12602. append_string(".bak\0", backup_file_name);
  12603. if((backup_file_pointer =
  12604. fopen(backup_file_name, "wb")) == NULL)
  12605. printf("\nSFO> Could not open backup file");
  12606. else{
  12607. /*printf("\n\nSFO> Writing to backup file");*/
  12608. fwrite(holder, sizeof(holder), 1, backup_file_pointer);
  12609. fclose(backup_file_pointer);
  12610. } /* ends else could not open backup_file */
  12611.  
  12612. append_stack_files(backup_file_name, STACK_FILE, holder);
  12613. copy_stack_files(backup_file_name, STACK_FILE, holder);
  12614.  
  12615. } /* ends else first_call != 1 */
  12616.  
  12617. printf("--- End of push_data_onto_stack");
  12618.  
  12619. } /* ends push_data_onto_stack_file */
  12620.  
  12621. /***************************************
  12622. * pop_data_off_of_stack_file(...
  12623. * This function pops the stack array
  12624. * off of the stack file.
  12625. ****************************************/
  12626.  
  12627. pop_data_off_of_stack_file(stack, pointer, stack_file_in_use)
  12628. int *pointer, *stack_file_in_use;
  12629. short stack[STACK_SIZE][2];
  12630. {
  12631. char backup_file_name[MAX_NAME_LENGTH];
  12632. FILE *backup_file_pointer, *stack_file_pointer;
  12633. int i;
  12634. long write_counter;
  12635. short holder[STACK_FILE_LENGTH][2],
  12636. holder2[STACK_FILE_LENGTH][2];
  12637.  
  12638. /********************************************
  12639. * POP - Read 1 time from stack
  12640. * Copy the remainder of stack to
  12641. * stack.bak
  12642. * Copy stack.bak to stack
  12643. * This has the effect of popping off
  12644. * of the stack.
  12645. * Read the holder array from the stack file.
  12646. * Open the stack file for reading in binary
  12647. * mode.
  12648. * If it requires more than one write to
  12649. * copy the remainder of stack to
  12650. * stack.bak then there is still data in the
  12651. * stack file so set stack_file_in_use = 1.
  12652.  
  12653. * Else set it to 0.
  12654. ***********************************************/
  12655.  
  12656. printf("\nSFO> Start of pop_data_off_of_stack ");
  12657. write_counter = 0;
  12658.  
  12659. strcpy(backup_file_name, STACK_FILE);
  12660. append_string(".bak\0", backup_file_name);
  12661.  
  12662. if( (stack_file_pointer = fopen(STACK_FILE, "rb")) == NULL)
  12663. printf("\nSFO> Could not open stack file");
  12664. else{
  12665. /*printf("\n\nSFO> Reading from stack file");*/
  12666. fread(holder, sizeof(holder),
  12667. 1, stack_file_pointer);
  12668. backup_file pointer =
  12669. fopen(backup_file_name, "wb");
  12670. while( fread(holder2, sizeof(holder2),
  12671. 1, stack_file pointer) ){
  12672. fwrite(holder2, sizeof(holder2),
  12673. 1, backup_file_pointer);
  12674. ++write_counter;
  12675. } /* ends while reading */
  12676. if(write_counter > 0)
  12677. *stack_file_in_use = 1;
  12678. else
  12679. *stack_file_in_use = 0;
  12680.  
  12681. fclose(backup_file_pointer);
  12682. fclose(stack_file_pointer);
  12683. } /* ends else could not open stack file */
  12684.  
  12685. copy_stack_files(backup_file_name,
  12686. STACK_FILE, holder2);
  12687.  
  12688. for(i=0; i<STACK_FILE_LENGTH; i++){
  12689. stack[i][0] = holder[i][0];
  12690. stack[i][1] = holder[i][1];
  12691. }
  12692. *pointer = *pointer + STACK_FILE_LENGTH - 1;
  12693.  
  12694. printf("--- End of pop_data_off_of_stack");
  12695. } /* ends pop_data_off_of_stack_file */
  12696.  
  12697. /*********************************************
  12698. * append_stack_files(...
  12699. * Append the second file onto the end
  12700. * of the first.
  12701. ***********************************************/
  12702.  
  12703. append_stack_files(first_file, second_file, holder)
  12704. char first_file[], second file[];
  12705. short holder[STACK_FILE_LENGTH][2];
  12706. {
  12707. FILE *first, *second;
  12708. int i;
  12709.  
  12710. if((first = fopen(first_file, "r+b")) == NULL)
  12711. printf("\n\nSFO> Cannot open file %s",
  12712.  
  12713. first_file);
  12714.  
  12715. if((second = fopen(second_file, "rb")) == NULL)
  12716. printf("\n\nSFO> Cannot open file %s", second_file);
  12717.  
  12718. /***************************************
  12719. * Seek to the end of the first file and
  12720. * to the beginning of the second file.
  12721. *****************************************/
  12722.  
  12723. fseek(first, OL, 2);
  12724. fseek(second, OL, 0);
  12725.  
  12726. while(fread(holder, sizeof(holder), 1, second) ){
  12727. fwrite(holder, sizeof(holder), 1, first);
  12728. } /* ends while reading */
  12729.  
  12730. fclose(first);
  12731. fclose(second);
  12732.  
  12733. } /* ends append_stack_files */
  12734.  
  12735. /******************************************
  12736. * copy_stack_files(...
  12737. * Copy the first file to the second.
  12738. ********************************************/
  12739.  
  12740. copy_stack_files(first_file, second_file, holder)
  12741. char first_file[], second_file[];
  12742. short holder[STACK_FILE_LENGTH][2];
  12743. {
  12744. FILE *first, *second;
  12745. int i;
  12746.  
  12747. if( (first = fopen[first_file, "rb")) == NULL)
  12748. printf("\n\nSFO> Cannot open file %s", first_file);
  12749.  
  12750. if( (second = fopen(second_file, "wb")) == NULL)
  12751. printf("\n\nSFO> Cannot open file %s", second_file);
  12752.  
  12753. /****************************************
  12754. * Seek to the beginning of the first file.
  12755. *****************************************/
  12756.  
  12757. fseek(first, 0L, 0);
  12758.  
  12759. while( fread(holder, sizeof(holder), 1, first) ){
  12760. fwrite(holder, sizeof(holder), 1, second);
  12761. } /* ends while reading */
  12762.  
  12763. fclose(first);
  12764. fclose(second);
  12765.  
  12766. } /* ends copy_stack_files */
  12767.  
  12768. /* End of File */
  12769.  
  12770.  
  12771. Listing 4 Code implementing manual segmentation
  12772.  
  12773. /**************************************************
  12774. * manual_threshold_segmentation(...
  12775. * This function segments an image using thresholding
  12776. * given the hi and low values of the threshold
  12777. * by the calling routine. It reads in an image
  12778. * and writes the result to the output image.
  12779. * If the segment parameter is 0, you only
  12780. * threshold the array - you do not segment.
  12781. ***************************************************/
  12782.  
  12783. manual_threshold_segmentation(in_name, out_name,
  12784. the_image, out_image,
  12785. il, ie, ll, le,
  12786. hi, low, value, segment)
  12787. char in_name[], out_name[];
  12788. int il, ie, ll, le, segment;
  12789. short hi, low, the_image[ROWS][COLS],
  12790. out_image[ROWS][COLS], value;
  12791. {
  12792. int length, width;
  12793. struct tiff_header_struct image_header;
  12794.  
  12795. if(does_not_exist(out_name)){
  12796. printf("\n\nMTS> output file does not exist %s", out_name);
  12797. read_tiff_header(in_name, &image_header);
  12798. round_off_image_size(&image_header, &length, &width);
  12799. image_header.image_length = length*ROWS;
  12800. image_header.image_width = width*COLS;
  12801. create_allocate_tiff_file(out_name, &image_header, out_image);
  12802. } /* ends if does_not_exist */
  12803.  
  12804. read_tiff_image(in_name, the_image, il, ie, ll, le);
  12805. threshold_image_array(the_image, out_image, hi, low, value);
  12806. if(segment == 1)
  12807. grow(out_image, value);
  12808. write_array_into_tiff_image(out_name, out_image, il, ie, ll, le);
  12809. } /* ends manual_threshold_segmentation */
  12810.  
  12811. /* End of File */
  12812.  
  12813.  
  12814. Listing 5 Code implementing the histogram peaks technique
  12815. /*********************************************
  12816. * peak_threshold_segmentation(...
  12817. * This function segments an image using
  12818. * thresholding. It uses the histogram peaks
  12819. * to find the hi and low values of the
  12820. * threshold.
  12821. * If the segment parameter is 0, you only
  12822. * threshold the array - you do not segment.
  12823. ***********************************************/
  12824.  
  12825. peak_threshold_segmentation(in_name, out_name
  12826. the_image, out_image,
  12827. il, ie, ll, le,
  12828. value, segment)
  12829. char in name[], out_name[];
  12830. int il, ie, ll, le, segment;
  12831. short the_image[ROWS][COLS],
  12832.  
  12833. out_image[ROWS] [COLS], value;
  12834. {
  12835. int length, peak1, peak2, width;
  12836. short hi, low;
  12837. struct tiff_header_struct image_header;
  12838. unsigned long histogram[GRAY_LEVELS+1];
  12839.  
  12840. if(does_not_exist(out_name)){
  12841. printf("\n\nPTS> output file does not exist %s", out_name);
  12842. read_tiff_header(in_name, &image_header);
  12843. round_off_image_size(&image_header, &length, &width);
  12844. image_header.image_length = length*ROWS;
  12845. image_header.image_width = width*COLS;
  12846. create_allocate_tiff_file(out_name, &image_header, out_image);
  12847. } /* ends if does not exist */
  12848.  
  12849. read_tiff_image(in_name, the_image, il, ie, ll, le);
  12850. zero_histogram(histogram);
  12851. calculate_histogram(the_image, histogram);
  12852. smooth_histogram(histogram);
  12853. find_peaks(histogram, &peak1, &peak2);
  12854. peaks_high_low(histogram, peak1, peak2, &hi, &low);
  12855. threshold_image_array(the_image, out_image, hi, low, value);
  12856. if(segment == 1)
  12857. grow(out_image, value);
  12858. write_array_into_tiff_image(out_name, out_image, il, ie, ll, le);
  12859.  
  12860. } /* ends peak_threshold_segmentation */
  12861.  
  12862. /*******************************************
  12863. * find_peaks(...
  12864. * This function looks through the histogram
  12865. * array and finds the two highest peaks.
  12866. * The peaks must be separated, cannot be
  12867. * next to each other, by a spacing defined
  12868. * in cips.h.
  12869. * The peaks array holds the peak value
  12870. * in the first place and its location in
  12871. * the second place.
  12872. *******************************************/
  12873.  
  12874. find_peaks(histogram, peak1, peak2)
  12875. unsigned long histogram[];
  12876. int *peak1, *peak2;
  12877. {
  12878. int distance[PEAKS], peaks[PEAKS][2];
  12879. int i, j=0, max:0, max_place:0;
  12880.  
  12881. for(i=0; i<PEAKS; i++){
  12882. distance[i] = 0;
  12883. peaks[i][0] = -1;
  12884. peaks[i] [1] = -1;
  12885. }
  12886.  
  12887. for(i=0; i<=GRAY_LEVELS; i++){
  12888. max = histogram[i];
  12889. max_place = i;
  12890. insert_into_peaks(peaks, max, max_place);
  12891. } /* ends loop over i */
  12892.  
  12893.  
  12894. for(i=1; i<PEAKS; i++){
  12895. distance[i] = peaks[0][1] - peaks[i][1];
  12896. if(distance[i] < 0)
  12897. distance[i] = distance[i]*(-1);
  12898. }
  12899.  
  12900. *peak1 = peaks[0] [1];
  12901. for(i=PEAKS-1; i>0; 1--)
  12902. if(distance[i] > PEAK_SPACE) *peak2 = peaks[i][1];
  12903.  
  12904. } /* ends find_peaks */
  12905.  
  12906. /*******************************************
  12907. * insert_into_peaks(...
  12908. * This function takes a value and its
  12909. * place in the histogram and inserts them
  12910. * into a peaks array. This helps us rank
  12911. * the the peaks in the histogram.
  12912. * The objective is to build a list of
  12913. * histogram peaks and thier locations.
  12914. * The peaks array holds the peak value
  12915. * in the first place and its location in
  12916. * the second place.
  12917. *******************************************/
  12918.  
  12919. insert_into_peaks(peaks, max, max_place)
  12920. int max, max_place, peaks[PEAKS][2];
  12921. }
  12922. int i, j;
  12923.  
  12924. /* first case */
  12925. if(max > peaks[0][0]){
  12926. for(i=PEAKS-1; i>0; i--){
  12927. peaks[i][0] = peaks[i-1][0];
  12928. peaks[il[1] = peaks[i-1[1];
  12929. }
  12930. peaks[0] [0] = max;
  12931. peaks[0][1] = max_place;
  12932. } /* ends if */
  12933.  
  12934. /* middle cases */
  12935. for(j=0; j<PEAKS-3; j++){
  12936. if(max < peaks[j][0] && max > peaks[j+1][0]){
  12937. for(i:PEAKS-1; i>j+1; i--){
  12938. peaks[i][0] = peaks[i-1][0];
  12939. peaks[i][1] = peaks[i-1][1];
  12940. }
  12941. peaks[j+1][0] = max;
  12942. peaks[j+1][1] = max_place;
  12943. } /* ends if */
  12944. } /* ends loop over j */
  12945. /* last case */
  12946. if(max < peaks[PEAKS-2][0] && max > peaks[PEAKS-1] [0]){
  12947. peaks[PEAKS-1][0] = max;
  12948. peaks[PEAKS-1][0] = max_place;
  12949. } /* ends if */
  12950.  
  12951. } /* ends insert into_peaks */
  12952.  
  12953.  
  12954. /***********************************************
  12955. * peaks_high_low(...
  12956. * This function uses the histogram array
  12957. * and the peaks to find the best high and
  12958. * low threshold values for the threshold
  12959. * function. You want the hi and low values
  12960. * so that you will threshold the image around
  12961. * the smaller of the two "humps" in the
  12962. * histogram. This is because the smaller
  12963. * hump represents the objects while the
  12964. * larger hump represents the background.
  12965. ***********************************************/
  12966.  
  12967. peaks_high_low(histogram, peak1, peak2, hi, low)
  12968. int peak1, peak2;
  12969. short *hi, *low;
  12970. unsigned long histogram[];
  12971. {
  12972. int i, mid_point;
  12973. unsigned long sum1 = 0, sum2 = 0;
  12974.  
  12975. if(peak1 > peak2)
  12976. mid_point = ((peak1 - peak2)/2) + peak2;
  12977. if(peak1 < peak2)
  12978. mid_point = ((peak2 - peak1)/2) + peak1;
  12979.  
  12980. for(i=0; i<mid_point; i++)
  12981. sum1 = sum1 + histogram[i];
  12982.  
  12983. for(i=mid_point; i<=GRAY_LEVELS; i++)
  12984. sum2 = sum2 + histogram[i];
  12985. if(sum1 >= sum2){
  12986. *low = midpoint;
  12987. *hi = GRAY_LEVELS;
  12988. }
  12989. else{
  12990. *low = 0;
  12991. *hi = mid_point;
  12992. }
  12993.  
  12994. } /* ends peaks_high_low */
  12995. /* End of File */
  12996.  
  12997.  
  12998. Listing 6 Code implementing the histogram valley technique
  12999. /*********************************************
  13000. * valley_threshold_segmentation(...
  13001. * This function segments an image using
  13002. * thresholdlng. It uses the histogram valleys
  13003. * to find the hi and low values of the
  13004. * threshold.
  13005. * If the segment parameter is 0, you only
  13006. * threshold the array - you do not segment.
  13007. ***********************************************/
  13008.  
  13009. valley_threshold_segmentation(in_name, out_name,
  13010. the_image, out_image,
  13011. il, ie, ll, le,
  13012.  
  13013. value, segment)
  13014. char in_name[], out_name[];
  13015. int il, ie, ll, ie, segment;
  13016. short the_image[ROWS] [COLS],
  13017. out_image[ROWS][COLS], value;
  13018. {
  13019. int length, peak1, peak2, width;
  13020. short hi, low;
  13021. struct tiff_header_struct image_header;
  13022. unsigned long-histogram[GRAY_LEVELS+1];
  13023.  
  13024. if(does_not_exist(out_name)){
  13025. printf{"\n\nVTS> output file does not exist %s", out_name);
  13026. read_tiff_header(in_name, &image_header);
  13027. round_off_image_size(&image_header, &length, &width);
  13028. image_header.image_length = length*ROWS;
  13029. image_header.image_width = width*COLS;
  13030. create_allocate tiff_file(out_name, &image_header, out_image);
  13031. } /* ends if does_not_exist */
  13032.  
  13033. read_tiff_image(in_name, the_image, il, ie, ll, ie);
  13034. zero_histogram(histogram);
  13035. calculate_histogram(the_image, histogram);
  13036. smooth_histogram(histogram);
  13037. find_peaks(histogram, &peak1, &peak2);
  13038. valley_high_low(histogram, peak1, peak2, &hi, &low);
  13039. threshold_image_array(the_image, out_image, hi, low, value);
  13040. if(segment == 1)
  13041. grow(out_image, value);
  13042. write_array_into_tiff_image(out_name, out_image, il, ie, ll, le);
  13043.  
  13044. } /* ends valley_threshold_segmentation */
  13045.  
  13046. /******************************************
  13047. * valley_high_low(...
  13048. * This function uses the histogram array
  13049. * and the valleys to find the best high and
  13050. * low threshold values for the threshold
  13051. * function. You want the hi and low values
  13052. * so that you will threshold the image around
  13053. * the smaller of the two "humps" in the
  13054. * histogram. This is because the smaller
  13055. * hump represents the objects while the
  13056. * larger hump represents the background.
  13057. *******************************************/
  13058.  
  13059. valley_high_low(histogram, peak1, peak2, hi, low)
  13060. int peak1, peak2;
  13061. short *hi, *low;
  13062. unsigned long histogram[];
  13063. {
  13064. int i, valley_point;
  13065. unsigned long sum1 = 0, sum2 = 0;
  13066.  
  13067. find_valley_point(histogram, peak1, peak2, &valley_point);
  13068. /*printf("\nVHL> valley point is %d", valley_point);*/
  13069.  
  13070. for(i=0; i<valley_point; i++)
  13071. sum1 = sum1 + histogram[i];
  13072.  
  13073. for(i=valley_point; i<=GRAY LEVELS; i++)
  13074. sum2 = sum2 + histogram"[i];
  13075.  
  13076. if(sum1 >= sum2){
  13077. *low = valley point;
  13078. *hi = GRAY_LEVELS;
  13079. }
  13080. else{
  13081. *low = 0;
  13082. *hi = valley_point;
  13083. }
  13084.  
  13085. } /* ends valley_high low */
  13086.  
  13087. /******************************************
  13088. * find_valley_point(...
  13089. * This function finds the low point of
  13090. * the valley between two peaks in a
  13091. * histogram. It starts at the lowest
  13092. * peak and works its way up to the
  13093. * highest peak. Along the way, it looks
  13094. * at each point in the histogram and inserts
  13095. * them into a list of points. When done,
  13096. * it has the location of the smallest histogram
  13097. * point - that is the valley point.
  13098. * The deltas array holds the delta value
  13099. * in the first place and its location in
  13100. * the second place.
  13101. *******************************************/
  13102.  
  13103. find_valley_point(histogram, peak1, peak2, valley_point)
  13104. int peak1, peak2, *valley_point;
  13105. unsigned long histogram[];
  13106. {
  13107. int deltas[PEAKS][2], delta_hist, i;
  13108. for(i=0; i<PEAKS; i++){
  13109. deltas[i][0] = 10000;
  13110. deltas[i][1] = -1;
  13111.  
  13112. if(peak1 < peak2){
  13113. for(i=peak1+1; i<peak2; i++){
  13114. delta_hist = (int)(histogram[i]);
  13115. insert_into_deltas(deltas, delta_hist, i);
  13116. } /* ends loop over i */
  13117. } /* ends if peak1 < peak2 */
  13118.  
  13119. if(peak2 < peak1){
  13120. for(i=peak2+1; i<peak1; i++){
  13121. delta_hist = (int)(histogram[i]);
  13122. insert_into_deltas(deltas, delta_hist, i);
  13123. } /* ends loop over i */
  13124. } /* ends if peak2 < peak1 */
  13125.  
  13126. *valley_point = deltas[0][1];
  13127.  
  13128. } /* ends find valley_point */
  13129.  
  13130. /****************************************
  13131. * insert_into_deltas(...
  13132.  
  13133. * This function inserts histogram deltas
  13134. * into a deltas array. The smallest delta
  13135. * will be at the top of the array.
  13136. * The objective is to build a list of
  13137. * histogram area deltas and thier locations.
  13138. * The deltas array holds the delta value
  13139. * in the first place and its location in
  13140. * the second place.
  13141. *******************************************/
  13142. insert_into_deltas(deltas, value, place)
  13143. int value, place, deltas[PEAKS] [2];
  13144. {
  13145. int i, j;
  13146. /* first case */
  13147. if(value < deltas[0] [0]){
  13148. for(i=PEAKS-1; i>0; i--){
  13149. deltas[i][0] = deltas[i-1][[0];
  13150. deltas[i] [1] = deltas[i-1] [1];
  13151. }
  13152. deltas[0] [0] = value;
  13153. deltas[0][1] = place;
  13154. } /* ends if */
  13155.  
  13156. /* middle cases */
  13157. for(j=0; j<PEAKS-3; j++){
  13158. if(value > deltas[j][0] &&
  13159. value < deltas [j+1] [0] ) {
  13160. for(i=PEAKS-1; i>j+1; i--){
  13161. deltas[i][0] = deltas[i-1][0];
  13162. deltas[i][1] = deltas[i-1][1];
  13163. }
  13164. deltas[j+1][0] = value;
  13165. deltas[j+1][1] = place;
  13166. } /* ends if */
  13167. } /* ends loop over j */
  13168.  
  13169. /* last case */
  13170. if(value > deltas[PEAKS-2][0] &&
  13171. value < deltas[PEAKS-i][o]){
  13172. deltas[PEAKS-1][0] = value;
  13173. deltas[PEAKS-1][1] = place;
  13174. } /* ends if */
  13175.  
  13176. } /* ends insert_into_deltas */
  13177. /* End of File */
  13178.  
  13179.  
  13180. Listing 7 Code implementing the adaptive technique
  13181. /************************************************
  13182. *
  13183. * adaptive_threshold_segmentation(...
  13184. *
  13185. * This function segments an image using
  13186. * thresholding. It uses two passes
  13187. * to find the hi and low values of the
  13188. * threshold. The first pass uses the peaks
  13189. * of the histogram to find the hi and low
  13190. * threshold values. It thresholds the image
  13191. * using these hi lows and calculates the means
  13192.  
  13193. * of the object and background. Then we use
  13194. * these means as new peaks to calculate new
  13195. * hi and low values. Finally, we threshold
  13196. * the image again using these second hi low
  13197. * hi low values.
  13198. *
  13199. * If the segment parameter is 0, you only
  13200. * threshold the array - you do not segment.
  13201. *
  13202. **************************************************/
  13203.  
  13204. adaptive_threshold_segmentation(in_name, out_name,
  13205. the_image, out_image,
  13206. il, ie, ll, le,
  13207. value, segment)
  13208. char in_name[], out_name[];
  13209. int il, ie, ll, le, segment;
  13210. short the_image[ROWS] [COLS], out_image[ROWS] [COLS], value;
  13211. {
  13212. int length, peak1, peak2, width;
  13213. short background, hi, low, object;
  13214. struct tiff_header_struct image_header;
  13215. unsigned long-histogram[GRAY_LEVELS+1];
  13216.  
  13217. if(does_not exist(out_name)){
  13218. printf("\n\nATS> output file does not exist %s", out_name);
  13219. read_tiff_header(in_name, &image_header);
  13220. round_off_image_size(&image_header, &length, &width);
  13221. image_header.image_length = length*ROWS;
  13222. image_header.image_width = width*COLS;
  13223. create_allocate_tiff_file(out_name, &image_header, out_image);
  13224. } /* ends if does not exist */
  13225.  
  13226. read_tiff_image(in_name, the_image, il, ie, ll, le);
  13227. zero_histogram(histogram);
  13228. calculate_histogram(the_image, histogram);
  13229. smooth_histogram(histogram);
  13230. find_peaks(histogram, &peak1, &peak2);
  13231. peaks_high_low(histogram, peak1, peak2, &hi, &low);
  13232. threshold_and_find_means(the_image, out_image,
  13233. hi, low, value,
  13234. &object, &background);
  13235. peaks_high_low(histogram, object, background,
  13236. &hi, &low);
  13237. threshold_image_array(the_image, out_image, hi, low, value);
  13238. if(segment == 1)
  13239. grow(out_image, value);
  13240. write_array_into_tiff_image(out_name, out_image,
  13241. il, ie, ll, le);
  13242.  
  13243. } /* ends adaptive_threshold_segmentation */
  13244.  
  13245. /************************************************
  13246. *
  13247. * threshold_and_find_means(...
  13248. *
  13249. * This function thresholds an input image array
  13250. * and produces a binary output image array.
  13251. * If the pixel in the input array is between
  13252.  
  13253. * the hi and low values, then it is set to value.
  13254. * Otherwise, it is set to 0.
  13255. *
  13256. ************************************************/
  13257.  
  13258. threshold_and_find_means(in_image, out_image, hi,
  13259. low, value, object_mean,
  13260. background_mean)
  13261. short *background_mean, hi, low,
  13262. in_image[ROWS][COLS], *object_mean,
  13263. out_image[ROWS][COLS], value;
  13264. {
  13265. int counter = 0,
  13266. i,
  13267. j;
  13268. unsigned long object = 0,
  13269. background = 0;
  13270.  
  13271. for(i=0; i<ROWS; i++){
  13272. for(j=0; j<COLS; j++){
  13273. if(in_image[i][j] >= low && in_image[i][j] <= hi){
  13274. out_image[i][j] = value;
  13275. counter++;
  13276. object = object + in_image[i][j];
  13277. }
  13278. else{
  13279. out_image[i][j] : 0;
  13280. background = background + in_image[i][j];
  13281. }
  13282. } /* ends loop over j */
  13283. } /* ends loop over i */
  13284. object = object/counter;
  13285. background = background/((ROWS*COLS)-counter);
  13286. *object_mean = (short)(object);
  13287. *background mean = (short)(background);
  13288. printf("\n\tTAFM> set %d points", counter);
  13289. printf("\n\tTAFM> object=%d background=%d",
  13290. *object_mean, *background_mean);
  13291. } /* ends threshold_and_find_means */
  13292.  
  13293. /* End of File */
  13294.  
  13295.  
  13296. Listing 8 main routine for the C Image Processing System revised to include
  13297. histogram image segmentation
  13298. case 16: /* image thresholding */
  13299. printf("\nCIPS> Enter input image name\n");
  13300. get_image_name(name);
  13301. printf("\nCIPS> Enter output image name\n");
  13302. get_image_name(name2);
  13303. get_parameters(&il, &ie, &ll, &le);
  13304. get_threshold_options(ts_method, &hi, &low, &value);
  13305. if (ts_method[0] == 'm' ts_method[0] == 'M')
  13306. manual_threshold_segmentation(name,
  13307. name2, the_image, out_image,
  13308. il, ie, ll, le,
  13309. hi, low, value, 0);
  13310. if(ts_method[0] == 'p' ts_method[0] == 'P')
  13311. peak_threshold_segmentation(name,
  13312. name2, the_image, out_image,
  13313.  
  13314. il, ie, ll, le, value, 0);
  13315. if(ts_method[0] == 'v' ts_method[0] == 'V')
  13316. valley_threshold_segmentation(name,
  13317. name2, the_image, out_image,
  13318. il, ie, ll, le, value, 0);
  13319. if(ts_method[0] == 'a' ts_method[0] == 'a')
  13320. adaptive_threshold_segmentation(name,
  13321. name2, the_image, out_image,
  13322. il, ie, ll, le, value, 0);
  13323. break;
  13324.  
  13325. case 17: /* image segmentation */
  13326. printf("\nCIPS> Enter input image name\n");
  13327. get_image_name(name);
  13328. printf("\nCIPS> Enter output image name\n");
  13329. get_image_name(name2);
  13330. get_parameters(&il, &ie, &ll, &le);
  13331. get_segmentation_options(ts_method, &hi,
  13332. &low, &value);
  13333. if(ts_method[0] == 'm' (ts_method[0] == 'M')
  13334. manual_threshold_segmentation(name,
  13335. name2, the_image, out_image,
  13336. il, ie, ll, le,
  13337. hi, low, value, 1);
  13338. if(ts_method[0] == 'p' ts_method[0] == 'P')
  13339. peak_threshold_segmentation(name,
  13340. name2, the_image, out_image,
  13341. il, ie, ll, le, value, 1);
  13342. if(ts_method[0] == 'v' ts_method[0] == 'V')
  13343. valley_threshold_segmentation(name,
  13344. name2, the_image, out_image,
  13345. il, ie, ll, le, value, 1);
  13346. if(ts_method[0] == 'a' ts_method[0] == 'a')
  13347. adaptive_threshold_segmentation(name,
  13348. name2, the_image. out_image,
  13349. il, ie, ll, le, value, 1);
  13350. break;
  13351.  
  13352. .
  13353. .
  13354. .
  13355.  
  13356. /****************************************************
  13357. * show_menu(..
  13358. * This function displays the CIPS main menu.
  13359. *****************************************************/
  13360. show_menu()
  13361. {
  13362.  
  13363. printf("\n\nWelcome to CIPS");
  13364. printf("\nThe C Image Processing System");
  13365. printf("\nThese are you choices:");
  13366. printf("\n\t1. Display image header");
  13367. printf("\n\t2. Show image numbers");
  13368. printf("\n\t3. Print image numbers");
  13369. printf("\n\t4. Display image (VGA & EGA only)");
  13370. printf("\n\t5. Display or print image using halftoning");
  13371. printf("\n\t6. Print graphics image using dithering");
  13372. printf("\n\t7. Print or display histogram numbers");
  13373.  
  13374. printf("\n\t8. Perform edge detection");
  13375. printf("\n\t9. Perform edge enhancement");
  13376. printf("\n\t10. Perform image filtering");
  13377. printf("\n\t11. Perform image addition and subtraction");
  13378. printf("\n\t12. Perform image cutting and pasting");
  13379. printf("\n\t13. Perform image rotation and flipping");
  13380. printf("\n\t14. Perform image scaling");
  13381. printf("\n\t15. Create a blank image");
  13382. printf("\n\t16. Perform image thresholding");
  13383. printf("\n\t17. Perform image segmentation");
  13384. printf("\n\t20. Exit system");
  13385. printf("\n\nEnter choice _\b");
  13386.  
  13387. } /* ends show_menu */
  13388. .
  13389. .
  13390. .
  13391.  
  13392. /******************************************
  13393. * get_segmentation_options(...
  13394. * This function interacts with the user
  13395. * to obtain the options for image
  13396. * segmentation.
  13397. *******************************************/
  13398.  
  13399. get_segmentation_options(method, hi, low, value)
  13400. char method[];
  13401. short *hi, *low, *value;
  13402. {
  13403. int i, not_finished = 1, response;
  13404.  
  13405. while(not_finished){
  13406. printf("\n\nThe image segmentation options are:\n");
  13407. printf("\n\t1. Method is %s", method);
  13408. printf("\n\t (options are manual peaks");
  13409. printf( " valleys adapative)");
  13410. printf("\n\t2. Value is %d", *value);
  13411. printf("\n\t3. Hi is %d", *hi);
  13412. printf("\n\t4. Low is %d", *low);
  13413. printf("\n\t Hi and Low needed only for");
  13414. printf( "manual method");
  13415. printf("\n\nEnter choice (0 = no change):_\b");
  13416.  
  13417. get_integer(&response);
  13418.  
  13419. if(response == 0)
  13420. not_finished == 0;
  13421.  
  13422. if(response == 1){
  13423. printf("\nEnter method (options are:");
  13424. printf(" manual peaks valleys adaptive)\n\t");
  13425. read_string(method);
  13426. }
  13427.  
  13428. if(response == 2){
  13429. printf("\nEnter value: __\b\b\b");
  13430. get_short(value);
  13431. }
  13432.  
  13433.  
  13434. if(response == 3){
  13435. printf("\nEnter hi: __\b\b\b");
  13436. get_short(hi);
  13437. }
  13438. if(response == 4){
  13439. printf("\nEnter low: __\b\b\b");
  13440. get_short(low);
  13441. }
  13442.  
  13443. } /* ends while not_finished */
  13444. } /* ends get_segmentation_options */
  13445.  
  13446. /*********************************************
  13447. * get_threshold_options{...
  13448. * This function interacts with the user
  13449. * to obtain the options for image
  13450. * threshold.
  13451. **********************************************/
  13452.  
  13453. get_threshold_options(method, hi, low, value)
  13454. char method[];
  13455. short *hi, *low, *value;
  13456. {
  13457. int i, not_finished = 1, response;
  13458. while(not_finished){
  13459. printf("\n\nThe image threshold options are:\n");
  13460. printf("\n\tl. Method is %s", method);
  13461. printf("\n\t (options are manual peaks");
  13462. printf( "valleys adapative)");
  13463. printf("\n\t2. Value is %d", *value);
  13464. printf("\n\t3. Hi is %d", *hi);
  13465. printf("\n\t4. Low is %d", *low);
  13466. printf("\n\t Hi and Low needed only for");
  13467. printf( " manual method");
  13468. printf("\n\nEnter choice (0 = no change):_\b");
  13469.  
  13470. get_integer(&response);
  13471.  
  13472. if(response == 0)
  13473. not_finished = 0;
  13474.  
  13475. if(response == 1){
  13476. printf("\nEnter method (options are:");
  13477. printf(" manual peaks valleys adaptive)\n\t");
  13478. read_string(method);
  13479. }
  13480.  
  13481. if(response == 2){
  13482. printf("\nEnter value: __\b\b\b");
  13483. get_short(value);
  13484. }
  13485.  
  13486. if(response == 3){
  13487. printf("\nEnter hi: __\b\b\b");
  13488. get_short(hi);
  13489. }
  13490. if(response == 4){
  13491. printf("\nEnter low: __\b\b\b");
  13492. get_short(low);
  13493.  
  13494. }
  13495.  
  13496. } /* ends while not_finished */
  13497. } /* ends get_threshold_options */
  13498. /* End of File */
  13499.  
  13500.  
  13501. Listing 9 Application for thresholding and segmenting an image file
  13502. /***************************************
  13503. *
  13504. * file d:\cips\mainseg.c
  13505. *
  13506. * Functions: This file contains
  13507. * main
  13508. *
  13509. * Purpose:
  13510. * This file contains the main calling
  13511. * routine in an edge detection program.
  13512. *
  13513. * External Calls:
  13514. * gin.c - get_image_name
  13515. * numcvrt.c - get_integer
  13516. * int_convert
  13517. * tiff.c - read_tiff_header
  13518. * enhance_edges
  13519. * hist.c - zero_histogram
  13520. * smooth_histogram
  13521. * show_histogram
  13522. * calculate_histogram
  13523. * segment.c - threshold_image_array
  13524. * grow
  13525. * find_peaks
  13526. * peaks_high_low
  13527. * valley_high_low
  13528. * threshold_and_find_means
  13529. *
  13530. * Modifications:
  13531. * 27 September 1992 - created
  13532. *
  13533. ****************************************/
  13534.  
  13535. #include "cips.h"
  13536.  
  13537. short the_image[ROWS][COLS];
  13538. short out_image[ROWS][COLS];
  13539. unsigned long histogram[GRAY_LEVELS+1];
  13540.  
  13541. main(argc, argv)
  13542. int argc;
  13543. char *argv[];
  13544. {
  13545. char name[80], name2[80], response[80];
  13546. int count, i, ie, i1, j, k, le, length, ll,
  13547. peakl, peak2, size,
  13548. t, type, v, width;
  13549. short background, hi, low, object, value;
  13550. struct tiff_header_struct image_header;
  13551.  
  13552. _setvideomode(_TEXTC80); /* MSC 6.0 statements */
  13553.  
  13554. _setbkcolor(1);
  13555. _settextcolor(7);
  13556. _clearscreen(_GCLEARSCREEN);
  13557.  
  13558. if(argc < 7){
  13559. printf("\n\nmainseg in-file out-file hi low value operation");
  13560. printf("\n\t\toperation = threshold grow peaks valleys adaptive");
  13561. printf("\n");
  13562. exit(0);
  13563. }
  13564.  
  13565. strcpy(name, argv[1]);
  13566. strcpy(name2, argv[2]);
  13567. hi = atoi(argv[3]);
  13568. low = atoi(argv[4]);
  13569. value = atoi(argv[5]);
  13570.  
  13571. il = 1;
  13572. ie = 1;
  13573. ll = ROWS+1;
  13574. le = COLS+1;
  13575.  
  13576. read_tiff_header(name, &image_header);
  13577.  
  13578. length = (90 + image_header.image_length)/ROWS;
  13579. width = (90 +image_header.image_width)/COLS;
  13580. count = 1;
  13581. printf("\nlength=%d width=%d", length, width);
  13582.  
  13583. if(does_not_exist(name2)){
  13584. read_tiff_header(name, &image_header);
  13585. round_off_image_size(&image_header, &length, &width);
  13586. image_header.image_length = length*ROWS;
  13587. image_header;image_width = width*COLS;
  13588. create_allocate_tiff_file(name2, &image_header,
  13589. out_image);
  13590. } /* ends if does_not_exist */
  13591. zero_histogram(histogram);
  13592.  
  13593. /**********************************
  13594. *
  13595. * Manual Threshold operation
  13596. *
  13597. ***********************************/
  13598.  
  13599. if(argv[6][0] == 't'){
  13600. for(i=0; i<length; i++){
  13601. for(j=0; j<width; j++){
  13602. printf("\nrunning %d of %d", count, length*width);
  13603. count++;
  13604. read_tiff_image(name, the_image,
  13605. il+i*ROWS, ie+j*COLS,
  13606. ll+i*ROWS, le+j*COLS);
  13607. printf("\nMS> Calling threshold");
  13608. threshold_image_array(the_image, out_image,
  13609. hi, low, value);
  13610. write_array_into_tiff_image(name2, out_image,
  13611. il +i*ROWS,
  13612. ie+j*COLS,
  13613.  
  13614. 11+i*ROWS,
  13615. le+j*COLS);
  13616. } /* ends loop over i */
  13617. } /* ends loop over j */
  13618. } /* ends if t */
  13619.  
  13620. /**********************************
  13621. *
  13622. * Grow region operation
  13623. *
  13624. **********************************/
  13625.  
  13626. if(argv[6][0] == 'g'){
  13627. for(i=0; i<length; i++){
  13628. for(j=0; j<width; j++){
  13629. printf("\nrunning %d of %d", count, length*width);
  13630. count++;
  13631. read_tiff_image(name, the_image,
  13632. il+i*ROWS, ie+j*COLS,
  13633. 11+i*ROWS, le+j*COLS);
  13634. printf("\nMS> Calling grow");
  13635. grow(the_image, value);
  13636. write_array_into_tiff_image(name2, the_image,
  13637. il+i*ROWS,
  13638. ie+j*COLS,
  13639. 11+i*ROWS,
  13640. le+j*COLS);
  13641. } /* ends loop over i */
  13642. } /* ends loop over j */
  13643. } /* ends if g */
  13644.  
  13645. /**********************************
  13646. *
  13647. * Peak threshold operation
  13648. *
  13649. ***********************************/
  13650.  
  13651. if(argv[6][0] == 'P'){
  13652.  
  13653. /* calculate histogram for the
  13654. entire image file */
  13655.  
  13656. zero_histogram(histogram);
  13657. for(i=0; i<length; i++){
  13658. for(j=0; j<width; j++){
  13659. printf("\nrunning %d of %d", count, length*width);
  13660. count++;
  13661. read_tiff_image(name, the_image,
  13662. il+i*ROWS, ie+j*COLS,
  13663. ll+i*ROWS, le+j*COLS);
  13664. printf("\nMS> Calling hist functions");
  13665. calculate_histogram(the_image, histogram);
  13666. } /* ends loop over i */
  13667. } /* ends loop over j */
  13668.  
  13669. smooth_histogram(histogram);
  13670. show_histogram(histogram);
  13671. find_peaks(histogram, &peak1, &peak2);
  13672. printf("\npeakl=%d peak2=%d", peak1, peak2);
  13673.  
  13674. peaks_high_low(histogram, peak1, peak2,
  13675. &hi, &low);
  13676. printf("\nhi=%d low=%d", hi, low);
  13677.  
  13678. /* now read the image file again
  13679. and threshold and grow objects. */
  13680. count = 1;
  13681. for(i=0; i<length; i++){
  13682. for(j=0; j<width; j++){
  13683. printf("\nrunning %d of %d", count, length*width);
  13684. count++;
  13685. read_tiff_image(name, the_image,
  13686. il+i*ROWS, ie+j*COLS,
  13687. ll+i*ROWS, le+j*COLS);
  13688. threshold_image_array(the_image, out_image,
  13689. hi, low, value);
  13690. write_array_into_tiff_image(name2, out_image,
  13691. il+i*ROWS,
  13692. ie+j*COLS,
  13693. ll+i*ROWS,
  13694. le+j*COLS);
  13695. } /* ends loop over i */
  13696. } /* ends loop over j */
  13697. } /* ends if p */
  13698.  
  13699. /********************************
  13700. *
  13701. * Valley threshold operation
  13702. *
  13703. ********************************/
  13704.  
  13705. if(argv[6][0] = = 'v'){
  13706.  
  13707. /* calculate histogram for the
  13708. entire image file */
  13709.  
  13710. zero_histogram(histogram);
  13711. for(i=0; i<length; i++){
  13712. for(j=0; j<width; j++){
  13713. printf("\nrunning %d of %d", count, length*width);
  13714. count++;
  13715. read_tiff_image(name, the_image,
  13716. il+i*ROWS, ie+j*COLS,
  13717. ll+i*ROWS, le+j*COLS);
  13718. printf("\nMS> Calling hist functions");
  13719. calculate_histogram(the_image, histogram);
  13720. } /* ends loop over i */
  13721. } /* ends loop over j */
  13722.  
  13723. smooth_histogram(histogram);
  13724. show_histogram(histogram);
  13725. find_peaks(histogram, &peak1, &peak2);
  13726. printf("\npeakl=%d peak2=%d", peak1, peak2);
  13727. valley_high_low(histogram, peak1, peak2,
  13728. &hi, &low);
  13729. printf("\nhi=%d low=%d", hi, low);
  13730.  
  13731. /* now read the image file again
  13732. and threshold and grow objects. */
  13733.  
  13734. count = 1;
  13735. for(i=0; i<length; i++){
  13736. for(j=0; j<width; j++){
  13737. printf("\nrunning %d of %d", count, length*width);
  13738. count++;
  13739. read_tiff_image(name, the_image,
  13740. il+i*ROWS, ie+j*COLS,
  13741. ll+i*ROWS, le+j*COLS);
  13742. threshold_image_array(the_image, out_image,
  13743. hi, low, value);
  13744. write_array_into_tiff_image(name2, out_image,
  13745. il+i*ROWS,
  13746. ie+j*COLS,
  13747. ll+i*ROWS,
  13748. le+j*COLS);
  13749. } /* ends loop over i */
  13750. } /* ends loop over j */
  13751. } /* ends if v */
  13752.  
  13753. /********************************
  13754. *
  13755. * Adaptive threshold operation
  13756. *
  13757. ********************************/
  13758.  
  13759. if(argv[6][0] == 'a'){
  13760.  
  13761. /* calculate histogram for the
  13762. entire image file */
  13763.  
  13764. zero_histogram(histogram);
  13765. for(i=0; i<length; i++){
  13766. for(j=0; j<width; j++){
  13767. printf("\nrunning %d of %d", count, length*width);
  13768. count++;
  13769. read_tiff_image(name, the_image,
  13770. il+i*ROWS, ie+j*COLS,
  13771. ll+i*ROWS, le+j*COLS);
  13772. printf("\nMS> Calling hist functions");
  13773. calculate_histogram(the_image, histogram);
  13774. } /* ends loop over i */
  13775. } /* ends loop over j */
  13776.  
  13777. /* find the peaks for the entire
  13778. image file. */
  13779.  
  13780. smooth_histogram(histogram);
  13781. show_histogram(histogram);
  13782. find_peaks(histogram, &peakl, &peak2);
  13783. printf("\npeakl=%d peak2=%d", peak1, peak2);
  13784. peaks_high_low(histogram, peak1, peak2,
  13785. &hi, &low);
  13786. printf("\nhi=%d low=%d", hi, low);
  13787.  
  13788. /* Second Pass */
  13789.  
  13790. count = 1;
  13791. for(i=0; i<length; i++){
  13792. for(j=0; j<width; j++){
  13793.  
  13794. printf("\nrunning %d of %d", count, length*width);
  13795. count++;
  13796. read_tiff_image(name, the_image,
  13797. il+i*ROWS, ie+j*COLS,
  13798. ll+i*ROWS, le+j*COLS);
  13799. threshold_and find_means(the_image,
  13800. out_image, hi, low,
  13801. value, &object,
  13802. &background);
  13803. peaks_high_low(histogram, object, background,
  13804. &hi, &low);
  13805. printf("\nafter means calculated - hi=%d low=%d", hi,
  13806. low);
  13807. threshold_image_array(the_image, out_image,
  13808. hi, low, value);
  13809. write_array_into_tiff_image(name2, out_image,
  13810. il+i*ROWS,
  13811. ie+j*COLS,
  13812. ll+i*ROWS,
  13813. le+j*COLS);
  13814. } /* ends loop over i */
  13815. } /* ends loop over j */
  13816. } /* ends if a */
  13817.  
  13818. } /* ends main */
  13819.  
  13820. /* End of File */
  13821.  
  13822.  
  13823.  
  13824.  
  13825.  
  13826.  
  13827.  
  13828.  
  13829.  
  13830.  
  13831.  
  13832.  
  13833.  
  13834.  
  13835.  
  13836.  
  13837.  
  13838.  
  13839.  
  13840.  
  13841.  
  13842.  
  13843.  
  13844.  
  13845.  
  13846.  
  13847.  
  13848.  
  13849.  
  13850.  
  13851.  
  13852.  
  13853.  
  13854.  
  13855.  
  13856.  
  13857. Pointer Power in C and C++, Part 1
  13858.  
  13859.  
  13860. Christopher Skelly
  13861.  
  13862.  
  13863. Christopher Skelly has been a teacher of C and C++ for the past ten years,
  13864. first for Plum Hall Inc., and then for his own company, Insight Resource Inc.
  13865. Insight Resource also developed the bestselling help utility, KO-PILOT for
  13866. WordPerfect, which Brit Hume called "the best add-in ever written." Chris has
  13867. served on both the C and C++ ANSI committees, and was the Technical Chairman
  13868. for this year's "C Plus C++" and "C++ in Action" conferences, presented by
  13869. Boston University. He writes regularly for the C User's Journal and the C++
  13870. Journal, and can be reached at Insight Resource Inc., 914-631-5032, or at
  13871. 71005.771@compuserve.com.
  13872.  
  13873.  
  13874. Pointers have always been the trickiest part of C to fully master. The syntax
  13875. of declarations and expressions using pointers is decidedly different. Arrays
  13876. have an often fuzzily understood relationship to pointers in C. Perhaps a
  13877. minority of C programmers really understand that [] has no intrinsic
  13878. connection with "arrayness," just as * has no fixed connection with
  13879. "pointerness." Pointer arithmetic is tricky, especially with
  13880. multiply-dimensioned arrays, or higher-level pointers like char **p. Pointers
  13881. to functions provide a sublime power in C programs, but one easily misused, as
  13882. any number of physically-damaged devices might testify to. Early BASIC and
  13883. Pascal made it hard to shoot yourself in the foot. Early C made it easy, and
  13884. later C and C++ can only help you if you let them. The situation I encountered
  13885. teaching C in the early 1980s was well represented by one student who
  13886. observed, "I was doing fine with C, until we got to pointers."
  13887. This two-part article sets out to help you master C's pointer power. Pointers
  13888. turn out to be a marvelous aspect of the C family of languages. They let you
  13889. solve many complex problems efficiently and elegantly, often using runtime
  13890. analysis and decision-making stratagems. Despite their complex aspects,
  13891. pointers in C turn out to be governed by relatively simple underlying ideas,
  13892. which can be used to resolve even the most complex pointer problems. This
  13893. article is based on the somewhat unconventional idea that pointers in C and
  13894. C++, for all their power, are really quite simple, once one understands a
  13895. small set of central principles.
  13896. This set of principles is designed to be extremely practical and simple to
  13897. use. Virtually everything in this article applies to both C and C++.
  13898. Understanding this model of pointer behavior is critical. You can memorize
  13899. rules or the meaning of certain particular expressions. You can even learn to
  13900. work with simple pointers by rote. But unless you understand the underlying
  13901. principles, you will get stuck every time by whatever the next level of
  13902. complexity happens to be. C and C++ are wonderful at always having a next
  13903. level of complexity available, should you have need of it!
  13904. The program in Listing 1, inspired by Alan Feuer's great C Puzzle Book,
  13905. illustrates the kind of complex pointer expression you will want to be able to
  13906. handle, if not with great ease, at least with the certainty that you are
  13907. approaching the problem correctly. At the end of this two-part series, I will
  13908. solve this puzzle, using the techniques described here.
  13909. Expressions like ++*--*++pppp[0] +5 arise with great infrequency in the real
  13910. world! Nevertheless, the ability to decipher such an expression is a
  13911. fundamental part of fluency in C. The most important advantage of fluency is
  13912. the freedom to think more about the real problem at hand, and less about the
  13913. programming language.
  13914. The following analysis starts simply enough, but it quickly jumps to more
  13915. complicated levels based on the fundamental premise. I call the fundamental
  13916. premise, the Campleat Pointer, perhaps because the great Angling classic
  13917. really does represent the point I am trying to make, or perhaps, as my wife
  13918. suggests, because I would rather be trout fishing than practically anything
  13919. else.
  13920.  
  13921.  
  13922. The Compleat Pointer
  13923.  
  13924.  
  13925. First, you must start off with a fundamental question. What exactly is a
  13926. pointer?
  13927. In English, a "pointer" is an indicator. It directs you toward something. An
  13928. arrow indicating a location, or a hot tip about a certain opportunity, a
  13929. pointer always directs your attention to something besides itself. The same is
  13930. true in C and C++. Calling something a pointer is a poetic way of saying that
  13931. you can use this thing to find something else. The something else is the thing
  13932. "pointed at." Imagine a line pointing from the first thing to the second. In
  13933. reality, of course, there is no line, dotted or otherwise, connecting the two
  13934. objects. The lines drawn in diagrams are quite imaginary.
  13935.  
  13936.  
  13937. Key Fact #1 -- A pointer is a variable containing an address.
  13938.  
  13939.  
  13940. The real connection a pointer makes is that the content of the pointer is the
  13941. address of the object pointed at. All the power and subtlety of pointers comes
  13942. out of this fundamental connection. Pointers are variables, which store the
  13943. addresses of other programming objects. Remember that an object in Standard C
  13944. is simply a region of storage. I'll use the term class object to refer to
  13945. instances of classes in C++ or other object-oriented systems.
  13946.  
  13947.  
  13948. Key Fact #2 -- A pointer always "knows" the type of thing it addresses. It can
  13949. be properly used only to access something of the correct type.
  13950.  
  13951.  
  13952. Computer memory is organized into one or more ranges of addresses. Almost
  13953. every object in a program has a unique memory address. The address tells where
  13954. in the computer's memory the object is located. Since a pointer holds the
  13955. address of an object, the pointer can be used as a tool for accessing the
  13956. object pointed at. This is the heart of the pointer concept.
  13957. Yet all addresses are not the same, at least not according to a pointer. Each
  13958. pointer has a built-in sense of the type of object stored at the address which
  13959. the pointer contains. This is the second crucial observation.
  13960. Put more formally, a pointer always points to an object of some particular
  13961. type. The type may be one of the built-in types, such as char, short, int,
  13962. long, float, or double, or any one of the possible derived types, including
  13963. arrays, functions, structs, unions, and even other pointers. C++ adds classes
  13964. to Standard C, allowing C++ pointers to point at class objects, or even at
  13965. class-object members, though the latter are implemented rather differently
  13966. than typical C pointers. Even the special case pointer to void points at a
  13967. specific type, and such a pointer has its own set of resources and
  13968. limitations.
  13969. Every pointer value is thus really a package, a collection of two specific
  13970. pieces of information: an address and a type pointed at. One might think of
  13971. this combination of information about where something is located, as well as
  13972. what is located there, as an "access cookie." The word "cookie" means a
  13973. package or collection of ingredients mixed and cooked together properly. Since
  13974. pointers are almost always used to access objects, the term access cookie
  13975. reminds you that it always takes both ingredients, the address and the type,
  13976. to properly access an object.
  13977.  
  13978.  
  13979. Key Fact #3 -- Pointer values are address/type pairs, just like pointer
  13980. variables. However, pointer values are not storable Ivalues.
  13981.  
  13982.  
  13983. Next, you should understand the important distinction between pointer
  13984. variables and pointer values. Pointer-like values often exist which are not
  13985. contained in any specific variable. Simple pointer expressions like p + 1
  13986. evaluate to these pointer-like values, without ever being stored in any
  13987. specific variable.
  13988. In other words, if p is the name of a pointer variable, then p + 1 is a
  13989. pointer value, but not a pointer variable. Why? Because the expression p + 1
  13990. gives us an address, and it has an associated type, exactly as if it were a
  13991. pointer variable, yet there is no variable that is actually storing the value
  13992. p + 1. Pointer variables always store pointer values, but pointer values are
  13993. not always stored in pointer variables. Both C and C++ call objects with
  13994. storable addresses, lvalues, though they disagree in some surprising ways as
  13995. to exactly what an lvalue is.
  13996. In most cases, the same rules apply to pointer variables and pointer values.
  13997. Both variables and values can have the indirection operator (*) applied to
  13998. them. This is called dereferencing the pointer. But some operators, like the
  13999. address-of operator (&) can only be applied to pointer variables, not to
  14000. pointer values. Strictly speaking, I ought to reserve the word pointer for
  14001. pointer variables, and always refer to pointer values explicitly as
  14002. address/type values or with some other term. For practical purposes however, I
  14003. will sometimes use the word pointer for both variables and values. Whenever
  14004. the distinction is critical, however, I'll use the specifically correct term.
  14005. To review, the absolutely essential pointer principles are:
  14006. A pointer is a package containing an address and a type to be found at that
  14007. address.
  14008. Simple but specific rules govern the behavior of both parts of the pointer
  14009. package. Some things affect the address part, some things the type part. You
  14010. have to keep track of what both parts mean when you work with pointers.
  14011. Pointer variables are variables which store pointer values. Pointer values can
  14012. also be generated by expressions.
  14013.  
  14014.  
  14015. Essential Programming Concepts
  14016.  
  14017.  
  14018. There are two essential programming concepts that will be of real benefit to
  14019. those studying pointers in C. The first I call The Three Attributes, and the
  14020. second The Ladder of Indirection. If you understand the Three Attributes you
  14021. can understand the Ladder of Indirection, and mastering the Ladder is the
  14022. heart of playing Pointer Dominos. Knowing how to play Pointer Dominos is the
  14023. key to mastering pointers in C.
  14024.  
  14025.  
  14026.  
  14027. Key Fact #4 -- Every pointer has three fundamental attributes. These
  14028. attributes are the location, the contents, and the indirect value of the
  14029. pointer.
  14030.  
  14031.  
  14032. You know that a pointer is an address storer, that is, it contains an address.
  14033. You also know the difference between pointer variables and pointer values. The
  14034. Three Attributes are the attributes of a pointer variable.
  14035. The contents of a pointer is the first of three critical values which can be
  14036. derived from that pointer. These three values are so critical to the proper
  14037. understanding of pointers that I've named them the Three Attributes.
  14038. Technically, each of these attributes is the value of an expression using the
  14039. pointer. I choose to focus on three of these particular expressions, since
  14040. these three yield the most critical information involved with the pointer.
  14041. The expression which will always return the contents of a pointer is simply
  14042. the name of the pointer. If you have a pointer p, that address which is the
  14043. current contents of the pointer is represented by the symbol p in your
  14044. program.
  14045. A pointer variable has a second attribute, a second value intimately
  14046. associated with that pointer. This second attribute is the location of the
  14047. pointer. The location of the pointer is the place in memory where the pointer
  14048. itself is stored. This location, like the contents, is an address. But the
  14049. pointer's location is generally a very different address from the address
  14050. stored in the pointer as the pointer's contents.
  14051. The expression which gives us the location of a pointer is composed of the
  14052. pointer's name, preceded by the & or address-of operator, as in
  14053. &p /* &p is the LOCATION of the pointer */
  14054. The first two attributes, location and contents, are attributes of every
  14055. variable. A simple int variable, x, has a location given by &x and a contents
  14056. or current value given by x. But a pointer has a third attribute, an indirect
  14057. value, the critical value that makes a pointer special to begin with.
  14058. The indirect value may also be the trickiest of the three attributes to work
  14059. with and to fully understand. To find the indirect value of a pointer, you
  14060. take the contents of the pointer as an address, from which you retrieve a
  14061. value. The indirect value is thus the value at the contents of the pointer.
  14062. The expression for the indirect value also has a type, and the type is always
  14063. the same as the type part of the pointer itself. If p is a pointer to char,
  14064. then the indirect value of p is a char. If p points at a double, then the
  14065. indirect value of p is a double.
  14066. In a program, the expression which evaluates to the third attribute, or
  14067. indirect value, is *p, as in
  14068. *p /* *p is the INDIRECT VALUE of p */
  14069. There are several good ways to think about the meaning of the term indirect
  14070. value. In a sense, the contents of the pointer is the pointer's direct value.
  14071. When you access a variable directly, you expect to receive the value of that
  14072. variable's contents. But with a pointer, you can use this contents as an
  14073. address to go look for something else. In effect, you get to the thing you are
  14074. looking for indirectly, using the pointer as an intermediate stepping stone.
  14075. This is where the term indirect value comes from.
  14076. These three attributes can be organized into a small but powerful set of
  14077. values concerning a pointer. If you keep these three values clearly
  14078. distinguished one from another in working with a pointer, you will be most
  14079. likely to use the pointer correctly in your programs.
  14080. To review, the three attributes are:
  14081. location -- where the pointer is itself stored (&p).
  14082. contents -- what is stored in the pointer (p).
  14083. indirect value -- what is stored AT the contents of pointer (*p).
  14084.  
  14085.  
  14086. Key Fact #5 -- The three attributes of a pointer represent three distinct
  14087. address levels. These address levels can also be called levels of indirection.
  14088.  
  14089.  
  14090. You may have noticed that although I discussed the contents of the pointer as
  14091. the very first attribute, I am now showing the location as the top or first
  14092. attribute. The reason why will become clear in just a moment.
  14093. Let's think about these three attributes of every pointer. What do they
  14094. reveal? First of all, they show that there are levels of addressing, at least
  14095. three levels represented by the three attributes. Each attribute is at a
  14096. different level in the addressing scheme.
  14097. Start with p itself. p is a variable containing an address. I call p a level 1
  14098. expression. Level 1 means that p holds the address of something else. Look at
  14099. what happens when you tack the ampersand onto p in front. Now you get the
  14100. address of p, &p. &p is the address of something whose contents is also an
  14101. address, that is, an address of an address. This I call a level 2 expression.
  14102. Starting with the value &p, you can do the process of going to an address and
  14103. finding a value twice.
  14104. *p is also at a different level than p. To get to *p from p you go to an
  14105. address. You use up one level of addressing and go "down" to the next lower
  14106. level. So if p is level 1, *p is level 0. *p is just like other variables
  14107. which don't hold addresses, such as the int variable i.
  14108. To review, we have three different levels of indirection represented by the
  14109. three attributes:
  14110. level 2 -- location of p (&p)
  14111. level 1 -- contents of p (p)
  14112. level 0 -- indirect value of p (*p)
  14113. It's not hard to imagine the levels connected together as steps on a ladder,
  14114. and that's precisely the second essential programming concept about pointers,
  14115. the Ladder of Indirection.
  14116.  
  14117.  
  14118. The Ladder of Indirection
  14119.  
  14120.  
  14121. The Ladder of Indirection is really a model of how expressions change level in
  14122. pointer space. In the model, pointer space is a series of discrete planes,
  14123. starting at ground level 0, and connected by a "ladder," or means of ascending
  14124. and descending.
  14125.  
  14126.  
  14127. Key Fact #6 -- Pointer space is organized into a series of planes or levels. 
  14128.  
  14129.  
  14130. Every pointer expression can be assigned to one of these planes. The plane of
  14131. a pointer expression is a measure of how much potential for indirection there
  14132. is in that pointer expression.
  14133. All pointers and pointer expressions can be seen as existing on particular
  14134. planes in this model of pointer space. The ladder is visualized as connecting
  14135. the planes from level 0 upwards to infinity. Each rung up is the next plane on
  14136. the Ladder of Indirection. Each rung down is the next plane down.
  14137. Moving up the ladder of indirection involves the process known as referencing,
  14138. or taking the address of something. When you take the address of something you
  14139. create a reference to that thing. References to objects are exactly what gets
  14140. stored in pointers. Every pointer must have at least one level of indirection
  14141. associated with it, or it couldn't be called a pointer. Some pointer
  14142. expressions have two or more levels of indirection associated with them. By
  14143. the way, don't be fooled by C++ references. References in C++ are a distinct
  14144. set of types, so-called precisely because they do indeed store an address
  14145. rather than a complete object. Referencing and dereferencing in C are general
  14146. terms, synonymous with taking an address of something and with going to
  14147. something by means of its address.
  14148. Every time you take something's address you go up a level on the ladder. Every
  14149. time you go to an address, you go down a level on the ladder. Different
  14150. operators take you up and down the ladder in different ways.
  14151. Imagine the dizzying whole of pointer space, with its Ladder connecting planes
  14152. ascending upward forever. Well, not really forever. Standard C says an
  14153. identifier may be declared with up to 12 modifying declarators, so level 12 is
  14154. the top, though some heavy duty compilers might support more. Objects and
  14155. expressions seem to move around or change values on a given plane, but
  14156. sometimes they leap up and jump to the next plane above. Something's address
  14157. has just been taken. At other times, references snake down from one plane to
  14158. the one below. An address on the higher plane has been used to descend to a
  14159. particular location on the lower. Except for ground level 0, this whole
  14160. organization of planes is highly symmetrical. Each level is equal to every
  14161. other level. Each one has its own precise level of indirection. The critical
  14162. point is that the level of indirection is an intrinsic part of the type of a
  14163. pointer. A level 2 pointer, in general, should not be used where a level one
  14164. pointer is required. Using pointers properly means always keeping track of the
  14165. level of indirection associated with each pointer.
  14166. Level 0 is different for one particular reason. You cannot go beneath it.
  14167. There is no level -1. So indirection has to stop when an expression reaches
  14168. level 0. Only with pointer expressions can you ever go down a level, and you
  14169. always have to stop at the bottom.
  14170.  
  14171.  
  14172. Master Pointers to Get Arrays
  14173.  
  14174.  
  14175. The heading for this section, with its fully intended pun, is designed to
  14176. introduce a very powerful, but also subtle, relationship that exists in C
  14177. between pointers and arrays.
  14178. While it is very true that understanding pointers fully might well lead to a
  14179. raise in pay, at least for a professional programmer, the real issue here is
  14180. that arrays in C are much more closely related to pointers than might be
  14181. apparent at first glance. One of the deeper elegances of C concerns this
  14182. special relationship between pointers and arrays. Incidentally, some have
  14183. considered this elegance a weakness in certain contexts.
  14184.  
  14185.  
  14186.  
  14187. Key Fact #7 -- The name of an array usually behaves as if the array name were
  14188. a pointer value.
  14189.  
  14190.  
  14191. Why does an array name in a C expression often behave like a pointer value?
  14192. The answer is simple, a matter of formal definition, built right into the
  14193. fundamentals of C. An array name used in a program is really an expression in
  14194. its own right. When the translator comes upon an array name, the translator
  14195. will evaluate the array name expression according to the standard rules for
  14196. expression evaluation. In almost every context, the array-name expression will
  14197. evaluate to an address. What address? The address of the data actually stored
  14198. in the first element of the array! Since arrays in C are indexed starting with
  14199. 0, I call this address the address of the "zeroth" element of the array.
  14200.  
  14201.  
  14202. Key Fact #8 -- The name of an array, in almost every context, evaluates to the
  14203. address of the array's own "zeroth" element.
  14204.  
  14205.  
  14206. The reason for making such a hedged statement is the desire to avoid a common
  14207. misunderstanding, usually stated something like "an array name is a pointer."
  14208. This apparently reasonable statement is in fact quite false. An array never
  14209. becomes a pointer and a pointer is not the same type as an array. What is true
  14210. is that array names act like pointer values is nearly every context. But not
  14211. always. An array name as the operand of the sizeof operator evaluates to the
  14212. size of the entire array, not the size of a pointer, just one example of an
  14213. array name not behaving like a pointer.
  14214. The [] operator, usually thought of as being related to arrays, is also a
  14215. dereferencing operator. p[n] lives on the plane below p. To see that this is
  14216. the case consider the simple array declaration
  14217. int arr[10];
  14218. What level does the expression arr have in most contexts? arr is a level-one
  14219. expression, evaluating to the address of the zeroth element of arr, in
  14220. virtually every context. arr behaves in this regard like a pointer to int,
  14221. though you must be careful not to say that an array name is a pointer.
  14222. Pointers are modifiable lvalues, array names are non-modifiable array-name
  14223. lvalues, not quite the same thing!
  14224. In any event, arr will typically behave like a level-one value. What about
  14225. arr[0]? arr[0] is clearly a level 0 value. arr[0] represents the actual data
  14226. in the first element of the arr array. So the subscript brings you down one
  14227. level of indirection, just as the * did in a dereference.
  14228.  
  14229.  
  14230. Summary
  14231.  
  14232.  
  14233. In this installment, I have defined the Three Attributes and the Ladder of
  14234. Indirection, and discussed the role of arrays. In the next installment, I will
  14235. teach you the game of Pointer Dominos. This is a game which I made up in the
  14236. process of teaching C classes in the early 1980's. The essential notion here
  14237. is that working with pointers is as simple as playing dominos. There are only
  14238. a small number of moves, and the moves are always played in a particular
  14239. order.
  14240. The rules of pointer dominos, and the solution to the puzzle in Listing 1 will
  14241. be described in Part 2 of "Pointer Power in C and C++," appearing in next
  14242. month's C Users Journal.
  14243.  
  14244. Listing 1 What does this program print?
  14245. #include <stdio.h>
  14246.  
  14247. char *ap[] = {
  14248. "INTEGER",
  14249. "PROPORTION",
  14250. "DEBUGGER",
  14251. "PORTABLE",
  14252. "TOWER!"
  14253. };
  14254.  
  14255. char **app[] = { ap + 4, ap + 3, ap + 2, ap + 1, ap };
  14256. char ***ppp = app;
  14257. char ****pppp = &ppp;
  14258.  
  14259. void main()
  14260. {
  14261. printf("%.*s", 2, *--**pppp);
  14262. printf("%.*s", 3, *(++*pppp[0] - 4));
  14263. printf("%s " , ++*--*++pppp[0] + 5);
  14264. printf("%.*s", 2, *++pppp[0] [3] + 3);
  14265. printf("%s\n", (*pppp + 2)[-2][2] + 2);
  14266. }
  14267.  
  14268. /* End of File */
  14269.  
  14270.  
  14271.  
  14272.  
  14273.  
  14274.  
  14275.  
  14276.  
  14277.  
  14278.  
  14279.  
  14280.  
  14281.  
  14282.  
  14283.  
  14284.  
  14285.  
  14286.  
  14287.  
  14288.  
  14289.  
  14290.  
  14291.  
  14292.  
  14293.  
  14294.  
  14295.  
  14296.  
  14297.  
  14298.  
  14299.  
  14300.  
  14301.  
  14302.  
  14303.  
  14304.  
  14305.  
  14306.  
  14307.  
  14308.  
  14309.  
  14310.  
  14311.  
  14312.  
  14313.  
  14314.  
  14315.  
  14316.  
  14317.  
  14318.  
  14319.  
  14320.  
  14321.  
  14322.  
  14323.  
  14324.  
  14325.  
  14326.  
  14327.  
  14328.  
  14329.  
  14330.  
  14331.  
  14332.  
  14333.  
  14334.  
  14335.  
  14336.  
  14337.  
  14338.  
  14339.  
  14340.  
  14341.  
  14342.  
  14343.  
  14344. Solving Linear Equations Using C
  14345.  
  14346.  
  14347. Matt Weisfeld
  14348.  
  14349.  
  14350. Matt Weisfeld is currently employed by the Allen-Bradley Company in Highland
  14351. Heights, Ohio. He is responsible for the design and development of test
  14352. software on VAX/VMS, UNIX, DOS and other platforms. Matt is currently working
  14353. on a book entitled Building and Testing Portable Libraries in C to be
  14354. published by QED later this year. He can be reached on Compuserve at
  14355. [71620,2171].
  14356.  
  14357.  
  14358. Resource allocation, a fundamental issue in any discipline, involves
  14359. maximizing value and minimizing costs. As early as Junior High School,
  14360. students solve simultaneous equations to find where these two variables break
  14361. even. However, solving simultaneous equations by hand works only when the
  14362. number of unknowns is small. When the problem involves dozens of equations and
  14363. variables, pencil and paper just aren't enough. Linear Programming (LP) is
  14364. used to solve these large resource-allocation problems. This article presents
  14365. a program for solving linear equations using C.
  14366.  
  14367.  
  14368. Sample Application
  14369.  
  14370.  
  14371. To illustrate how Linear Programming works in a real life application,
  14372. consider the following example. The Acme soft drink company markets two
  14373. different lines of cola: diet and regular. Operating at maximum daily
  14374. capacity, Acme can produce four tons of diet and six tons of regular. Most
  14375. ingredients (such as water and sweetener) are obtained upon demand, so they do
  14376. not affect potential output. However, the secret ingredient that makes Acme
  14377. cola taste so special is limited to 24 pounds a day. Each ton of diet needs
  14378. three pounds of the secret ingredient while each ton of regular needs four
  14379. pounds. Finally, each ton of diet sells for $200,000, while each ton of
  14380. regular sells for $600,000. The Acme company must maximize income by producing
  14381. the optimal amount of diet and regular cola.
  14382. The standard LP problem consists of variables, an objective, and constraints.
  14383. The variables in this example represent the two types of cola, diet (x1) and
  14384. regular (x2). The goal is to allocate these variables in a fashion that
  14385. maximizes the company's income (z). The objective function can be written
  14386. z = 2x1 + 6x2 (in units of $100,000)
  14387. To complete the LP model three constraints must be factored in:
  14388. x1 Â£ 4 (max diet in tons)
  14389. x2 Â£ 6 (max regular in tons)
  14390. 3x1 + 4x2 Â£ 24 (in pounds)
  14391. The final problem is:
  14392. Maximize
  14393. z = 2x1 + 6x2 (objective function)
  14394. such that
  14395. x1 Â£ 4 (constraint 1)
  14396. x2 Â£ 6 (constraint 2)
  14397. 3x1 + 4x2 Â£ 24 (constraint 3)
  14398. x1, x2 >=0 (it is impossible to have a negative amount of cola)
  14399.  
  14400.  
  14401. The Graphical Solution
  14402.  
  14403.  
  14404. Since this problem has only two variables, you can depict the solution
  14405. graphically. Understanding the problem in terms of a graph makes the
  14406. transition to the algorithmic solution much easier. However, once a third
  14407. variable (and thus a third dimension) enters the picture, graphing becomes
  14408. quite a challenge.
  14409. Figure 1 shows the graph of the Acme problem with only the constraints
  14410. plotted. Notice the area contained within the constraint lines and the x1 and
  14411. x2 axis. This area is called the Feasible Solution Space. Any point within
  14412. this area will yield a combination of diet and regular cola that can be
  14413. produced and yield some income. However, the goal is to find the best
  14414. solution, not simply a feasible one. Notice also that there are five edges
  14415. that surround the Feasible Solution Space. An edge is where two lines meet to
  14416. form a single point. The optimal solution will be found at one of these edges.
  14417. To find the optimal solution, you need the objective function. Figure 2 shows
  14418. two possible solutions based on a different value for z. Since the ratio of x1
  14419. to x2 is known, the slope of the line can be calculated. By varying the
  14420. position of the line (the slope is always the same so the lines must move in a
  14421. parallel manner), different solutions to this problem can be explored. The
  14422. values of x1 and x2 are obtained by examining the points where the objective
  14423. function intersects the lines bounding the feasible solution space. Line 1
  14424. intersects the feasible solution space at edge b (point x1=0, x2=6: z=36).
  14425. Notice that as the line moves away from the origin, the value of z increases.
  14426. Also remember that the objective line must intersect the feasible solution
  14427. space for a valid solution to exist. Looking at the graph, it is apparent that
  14428. the largest value of z that intersects the Feasible Solution Space is at edge
  14429. c (point x1=1.5, x2=6, z=39). An optimal solution not falling on an edge
  14430. represents a situation where the objective function is parallel to one of the
  14431. constraints and more than one (actually infinite) optimal solution exists.
  14432.  
  14433.  
  14434. The Standard Form of the LP Model
  14435.  
  14436.  
  14437. In order to have a mathematical algorithm that can solve general LP models as
  14438. well as lend itself to a computer implementation, all LP problems must follow
  14439. the Standard Form of the LP model. The Standard Form includes these features:
  14440. All constraints are equations with a non-negative Right Hand Side (RHS). If a
  14441. RHS is negative, simply multiple both sides by --1.
  14442. All variables are non-negative. In this case there is no choice. There is no
  14443. way to produce a negative amount of diet cola.
  14444. The objective function can be maximized or minimized. In the sample problem
  14445. you are maximizing income. However, the problem could be designed to minimize
  14446. costs (this will be discussed later).
  14447. To satisfy the first rule the constraints must be converted to equations. This
  14448. may seem confusing, but in their present form they are not now equations. The
  14449. operator used in all the constraints is Â£. To make the constraints conform to
  14450. the Standard Form, equal signs must separate the left-hand-side from the
  14451. right-hand-side. However, simply replacing the Â£ with an = is inappropriate
  14452. since it actually alters the constraint (eg: i£1 is obviously not the same as
  14453. i=1). To properly convert the constraint to an equation, a slack variable must
  14454. be added. The slack variable accounts for the fact that the Â£ represents a
  14455. wide range of values and permits the use of the equality.
  14456. Even though the slack variables are necessary in the conversion of the
  14457. constraints, they do not contribute to the value of the objective function.
  14458. Only x1 and x2 affect the income. The slack variables are created to assist in
  14459. the computations, they do not represent real unknowns. To illustrate this
  14460. fact, the objective function multiplier for each slack variable is 0. Thus the
  14461. Acme Cola problem, represented in the Standard Form, is:
  14462. Maximize
  14463. z = 2x1 + 6x2 + 0s1 + 0s2 + 0s3 (objective function)
  14464. such that
  14465. x1 + s1 = 4 (constraint 1)
  14466. x2 +s2 = 6 (constraint 2)
  14467. 3x1 + 4x2 s3 = 24 (constraint 3)
  14468. x1, x2 >= 0 (it is impossible to have a negative amount of cola)
  14469. In this case, the slack variables are all positive because all the constraints
  14470. are of the Â£ type. If the constraints were >=, the slack variables would be
  14471. negative. Figure 3 shows how the slack variables are represented graphically.
  14472. There is a direct relationship between the number of graph sides (5) and the
  14473. number of variables (x1, x2, s1, s2, s3).
  14474.  
  14475.  
  14476.  
  14477. The Starting Solution
  14478.  
  14479.  
  14480. To begin solving the Acme problem you must identify an initial, feasible
  14481. starting solution. Since the optimal solution must be an edge, this problem
  14482. has five possible candidates (as can be seen on the graph). Normally, you
  14483. would begin at the origin when a problem is bounded by the x1 and x2 axis
  14484. (where x1, x2 > 0), since this is both a feasible solution and an edge. The
  14485. concept of the algorithm (presented later) is to move from the current edge to
  14486. the next adjacent edge on a path to the optimal solution. The direction to
  14487. move is determined by the algorithm. Thus, from the origin (edge a in Figure
  14488. 2), the algorithm moves to edge b and then stops at edge c, the optimal
  14489. solution. The algorithm must follow adjacent edges (that is, the solution
  14490. cannot move directly from edge a to edge c).
  14491.  
  14492.  
  14493. The Initial Table
  14494.  
  14495.  
  14496. A table format is used to group all the information needed to solve the
  14497. problem. The C data structures mimic this table. Table 1 contains the initial
  14498. table. The top row of the table is simply a header. The second row represents
  14499. the objective function. The remaining number of rows are variable and depend
  14500. on the number of constraints.
  14501. The first column identifies the variables that are currently in the basic
  14502. solution. As mentioned before, the starting edge is at the origin (x1 and x2
  14503. are zero). This leaves the slack variables (s1, s2, s3) as the variables in
  14504. the starting solution, which is called the basis. Notice that the rows
  14505. representing the slack variables form an identity matrix (ones down the
  14506. horizontal). Note, if some of the constraints had been >=, the corresponding
  14507. slack variables would be negative and thus not a candidate for entry into the
  14508. basis. In this instance, the algorithm presented would need to be modified to
  14509. construct a proper basis. Finally, the column at the far right represents the
  14510. current values for the variables in the basis.
  14511.  
  14512.  
  14513. The C Data Structures
  14514.  
  14515.  
  14516. Since the user interface is not a topic of this discussion, all data
  14517. structures are defined internally. However, in any practical application the
  14518. input data should not be hard-coded. Listing 1 contains all definitions. The C
  14519. program includes three arrays that hold all the data necessary to represent a
  14520. table. The arrays basis and objective hold the names (labels) that represent
  14521. the variables. The actual computations are performed on the array called
  14522. table. The dimensions of the table are always rows by columns. In the example
  14523. table, the dimensions are four rows by seven columns. The only other
  14524. information needed are the number of variables and constraints, which in this
  14525. example are two and three respectively.
  14526.  
  14527.  
  14528. The Simplex Method
  14529.  
  14530.  
  14531. The algorithm used to solve this problem is called the simplex method. With
  14532. this method you determine if there are any non-basic variables that, if in the
  14533. basic solution, would enhance the final result. Based on the initial table,
  14534. the basis variables are s1, s2, and s3. This is a feasible solution, however,
  14535. since the variables x1 and x2 are not in the basis, the result is to produce
  14536. nothing (z=0). To identify any variables that should enter the basis, inspect
  14537. the objective function. The objective function is
  14538. z x1 x2 s1 s2 s3 sol
  14539. ---------------------------
  14540. 1 -2 -6 0 0 0 0
  14541. Any variable that is negative (in a maximization problem) will increase the
  14542. value of z, and so should enter the basis. If there is more than one negative
  14543. value, the most negative one is chosen. Thus, there are two candidates for the
  14544. entering value (--2 and --6). In this case, x2 should enter the basis.
  14545. It is obvious that if one variable enters the basis, another currently in the
  14546. basis must leave. Since s1, s2, and s3 are in the basis, one must leave if x2
  14547. is to enter. Determining the leaving variable is done by inspecting the
  14548. numbers in the column under entering variable, x2. They are
  14549.  x2
  14550.  -----
  14551. s1 0.0
  14552. s2 1.0
  14553. s3 3.0
  14554. All numbers that are not positive are not considered, thus s1 cannot leave the
  14555. basis at this time. To determine which candidate actually leaves the basis,
  14556. the ratios between the values in the entering column and the values in the
  14557. solution are taken. For example, since there are two possible candidates to
  14558. leave the basis, the ratios considered are as follows:
  14559.  sol x2 ratio
  14560.  ----- ----- -----
  14561. s2 : 6 / 1 = 6
  14562. s3 : 24 / 3 = 8
  14563. The lowest value is chosen to leave the basis, thus s2 receives the honor.
  14564. This makes the value 1.00, at the intersection of column x1 and row s2, the
  14565. pivot element.
  14566. Now that the entering and leaving variables have been identified, the next
  14567. iteration of the table is calculated by using the Gauss-Jordon method. Looking
  14568. at the table, it is evident that for x2 to become part of the basis, it must
  14569. replace s2 as part of the identity matrix. To do this the pivot element must
  14570. be a 1. Thus, all values in the leaving equation are divided by the pivot
  14571. element. In this instance, the value of the pivot element is already a 1 so
  14572. the division does not change anything. The row that was s2 and is now replaced
  14573. by x2 is called the pivot equation.
  14574. As mentioned above, placing x2 in the basis means that it must replace s2 as
  14575. part of the identity matrix. Since the pivot element is now a 1, all the other
  14576. values in the x2 column must be 0. To do this the transformation
  14577. new equation = old equation - (entering column coefficient 
  14578. new pivot equation)
  14579. is applied to all elements of all the rows (including the objective function)
  14580. other than the pivot equation.
  14581. Thus the objective function transformation is
  14582. old :1 -2 -6 0 0 0 0
  14583.  
  14584.  -(-6)0 -(6)(0) -(6)(1) -(6-(0) -(6)(1) -(6)(0) -(6)(6)
  14585. -------------------------------------------------------------------
  14586.  
  14587. new :1 -2 0 0 6 0 36
  14588. The s1 equation is unchanged due to the fact that the entering column
  14589. coefficient is already zero. The s3 equation transformation is as follows:
  14590. old :0 4 3 0 0 1 24
  14591.  
  14592.  -(3)0 -(3)(0) -(3)(1) -(3)(0) -(3)(1) -(3)(0) -(3)(6)
  14593. ------------------------------------------------------------------
  14594.  
  14595.  
  14596. new :0 4 0 0 -3 1 6
  14597. The first iteration is complete and the second table is presented in Table 2.
  14598. The important information that can be gathered from inspecting this table is
  14599. in the solution column. The 36 in the solution column of the objective
  14600. function indicates that with the current solution the ACME Co. can make $36.00
  14601. (in units of 100,000). To determine the product mix, examine the variables in
  14602. the basis. In this case only x2 is in the basis. Its solution is 6 (if six
  14603. units of x2 are produced, ACME will make $36.00). However, since the objective
  14604. function still has a negative value, the optimum solution has not yet been
  14605. reached.
  14606. The variable x1, which has a value of -2, is the next to enter the basis. The
  14607. ratios are:
  14608. s1: 4/1 = 4
  14609. s3: 6/4 = 1.5
  14610. Thus s3 is the leaving variable. In this case the pivot element is not one, so
  14611. all the elements of the pivot equation are divided by the pivot element, which
  14612. is four. The new pivot equation is as follows:
  14613. 0 1 0 0 -0.75 0.25 1.5
  14614. Applying the Gauss-Jordon technique yields the table in Table 3. The first
  14615. thing to notice is that there are no negative values in the objective
  14616. function. This means that the optimal solution has been found (z=39). Also
  14617. notice that both variables, x1 and x2, are in the basis. Thus, to maximize
  14618. profits, ACME should produce 1.5 units of diet and 6 units of regular.
  14619.  
  14620.  
  14621. The C Routines
  14622.  
  14623.  
  14624. The amount of C code necessary to perform the table operations is surprisingly
  14625. small. Listing 2 contains the code for the mainline which loops until there
  14626. are no more entering variables, printing each iteration of the table. The
  14627. entering variable (enter_pos) and leaving variable (leave_pos) are determined
  14628. by the code in Listing 3. If enter_pos is 0 then the optimal solution has
  14629. already been obtained and the loop terminates. The location of the pivot
  14630. element is placed in the variable pivot_element.
  14631. The routines new_pivot and new_equation are presented in Listing 4. new_pivot
  14632. calculates the next pivot equation by dividing all elements in the pivot row
  14633. by the pivot element, while new_equation performs the Gauss-Jordon algorithm
  14634. on the entire table and completes the current iteration.
  14635. To help illustrate the program's performance, and to aid in debugging, two
  14636. additional routines are included. The first, build_basis, labels all the rows
  14637. and columns for better readability when the table is printed. The second,
  14638. print_table, simply prints the contents of the table so that the information
  14639. it contains can be inspected. These routines are presented in Listing 5. All
  14640. of the listing files can be combined into one program file for compilation and
  14641. execution.
  14642.  
  14643.  
  14644. Conclusion
  14645.  
  14646.  
  14647. Linear Programming is a powerful tool when attempting to allocate resources.
  14648. The example used in this article presents a solution for one specific case, a
  14649. maximization problem with all constraints of the Â£ variety. As stated before,
  14650. there are a number of variations to the standard LP model. In practice, there
  14651. can be a mixture of constraints (>=, Â£, or = ) and the final goal may be to
  14652. minimize the result (as in costs). When the constraints are mixed, extensions
  14653. to the simplex algorithm are required. Furthermore, the results obtained from
  14654. the final table are anything but final. All the numbers in the table represent
  14655. useful information. (The act of gathering this information is called
  14656. sensitivity analysis.)
  14657. References
  14658. Hamdy A. Taha. 1982. Operations Research, An Introduction, 3rd ed. New York,
  14659. NY: MacMillan Publishing Company, Inc.
  14660. Hillier, Frederick S. and Gerald J. Lieberman. 1980. Introduction to
  14661. Operations Research, 3rd ed. San Francisco, CA: Holden-Day Inc.
  14662. Figure 1 Feasible solution space for ACME problem
  14663. Figure 2 Two possible solutions based on a different value for z
  14664. Figure 3 Graphical representation of the slack variables
  14665. Table 1 Original table
  14666. basis z x1 x2 s1 s2 s3 sol
  14667. --------------------------------------------------
  14668.  z 1.00 -2.00 -6.00 0.00 0.00 0.00 0.00
  14669.  s1 0.00 1.00 0.00 1.00 0.00 0.00 4.00
  14670.  s2 0.00 0.00 1.00 0.00 1.00 0.00 6.00
  14671.  s3 0.00 4.00 3.00 0.00 0.00 1.00 24.00
  14672. Table 2 Iteration 1
  14673. basis z x1 x2 s1 s2 s3 sol
  14674. --------------------------------------------------
  14675.  z 1.00 -2.00 0.00 0.00 6.00 0.00 36.00
  14676.  s1 0.00 1.00 0.00 1.00 0.00 0.00 4.00
  14677.  x2 0.00 0.00 1.00 0.00 1.00 0.00 6.00
  14678.  s3 0.00 4.00 0.00 0.00 -3.00 1.00 6.00
  14679. Table 3 Iteration 2
  14680. basis z x1 x2 s1 s2 s3 sol
  14681. --------------------------------------------------
  14682.  z 1.00 0.00 0.00 0.00 4.50 0.50 39.00
  14683.  s1 0.00 0.00 0.00 1.00 0.75 -0.25 2.50
  14684.  x2 0.00 0.00 1.00 0.00 1.00 0.00 6.00
  14685.  x1 0.00 1.00 0.00 0.00 -0.75 0.25 1.50
  14686.  
  14687. Listing 1 Function definitions
  14688. #include <stdio.h>
  14689.  
  14690. #define LABELSIZE 10
  14691.  
  14692. /* define matrix parameters */
  14693. #define ROWS 4
  14694. #define COLUMNS 7
  14695. #define VARIABLES 2
  14696.  
  14697. #define EQUATIONS 3
  14698.  
  14699. /* initialize matrix */
  14700. float table[ROWS][COLUMNS] = { 1,-2,-6,0,0,0,0,
  14701. 0, 1, 0,1,0,0,4,
  14702. 0, 0, 1,0,1,0,6,
  14703. 0, 4, 3,0,0,1,24, };
  14704.  
  14705. /* char strings to hold labels */
  14706. char basis[LABELSIZE][LABELSIZE];
  14707. char objective[LABELSIZE][LABELSIZE];
  14708.  
  14709. /* used to build labels */
  14710. char var[LABELSIZE];
  14711. char num[LABELSIZE];
  14712.  
  14713. /* save info for leaving & entering var */
  14714. int leave_pos;
  14715. float leave_holder;
  14716.  
  14717. int enter_pos;
  14718. float enter_holder;
  14719.  
  14720. /* save info for pivot element */
  14721. float pivot_element;
  14722.  
  14723. /* count # of iterations */
  14724. int pass;
  14725.  
  14726. /* prototypes */
  14727. int select_entering(void);
  14728. int select_leaving(void);
  14729. void new_pivot(void);
  14730. void new_equation(void);
  14731. void build_basis(void);
  14732. void print_table(void);
  14733. /* End of File */
  14734.  
  14735.  
  14736. Listing 2 Main loop of the LP program
  14737. main(int argc, char **argv)
  14738. {
  14739.  
  14740. int i,j; /* loop control */
  14741. char temp[10]; /* workspace */
  14742.  
  14743. /* build the initial tableau */
  14744. build_basis();
  14745. printf ("**** ORIGINAL TABLE\n");
  14746. print_table();
  14747. pass = 1;
  14748.  
  14749. /* select_entering will return a 0 when
  14750. there are no more entering VARIABLES,
  14751. otherwise, the location of the entering
  14752. variable is returned */
  14753. while (enter_pos = select_entering()) {
  14754.  
  14755. /* return pos for leaving variable */
  14756.  
  14757. leave_pos = select_leaving();
  14758.  
  14759. /* calculate the pivot element */
  14760. pivot_element = table[leave_pos][enter_pos];
  14761.  
  14762. /* calculate the new pivot equation */
  14763. new_pivot();
  14764.  
  14765. /* calculate all the non-pivot EQUATIONS */
  14766. new_equation();
  14767.  
  14768. /* label the new basis */
  14769. strcpy (basis[leave_pos],
  14770. objective[enter_pos]);
  14771.  
  14772. printf ("\n**** ITERATION %d\n\n", pass);
  14773. pass++;
  14774.  
  14775. print_table();
  14776.  
  14777. }
  14778.  
  14779. }
  14780. /* End of File */
  14781.  
  14782.  
  14783. Listing 3 Code to determine entering and leaving variables
  14784. int select_entering(void)
  14785. {
  14786.  
  14787. int i,j;
  14788.  
  14789. enter_pos = 0;
  14790. enter_holder = 0.0;
  14791.  
  14792. /* determine the most neg value, if any */
  14793. for (j=1; j<COLUMNS-1; j++) {
  14794. if (table[0][j]<0) {
  14795. if (table[0][j] < enter holder) {
  14796. enter_holder = table[0][j];
  14797. enter_pos = j;
  14798. }
  14799. }
  14800. }
  14801.  
  14802. /* if j has been changed from 0, then we have
  14803. an entering equation */
  14804.  
  14805. return(enter_pos);
  14806.  
  14807. }
  14808.  
  14809. int select_leaving (void)
  14810. {
  14811.  
  14812. int i,j;
  14813.  
  14814. float ratio[50];
  14815.  
  14816.  
  14817. leave_pos = 0;
  14818. leave_holder = 999.0;
  14819.  
  14820. /* determine the lowest ratio of the
  14821. positive elements */
  14822.  
  14823. for (i=1; i<ROWS;i++) {
  14824.  
  14825. if (table[i][enter_pos] > 0) {
  14826.  
  14827. ratio[i] =
  14828. table[i][COLUMNS-1]/table[i][enter_pos];
  14829.  
  14830. if (ratio[i] < leave_holder) {
  14831.  
  14832. leave_holder = ratio[i];
  14833. leave_pos = i;
  14834.  
  14835. }
  14836.  
  14837. }
  14838.  
  14839. }
  14840.  
  14841. return (leave_pos);
  14842.  
  14843. }
  14844. /* End of File */
  14845.  
  14846.  
  14847. Listing 4 Code for finding pivot row and completing current iteration
  14848. void new_pivot(void)
  14849. {
  14850.  
  14851. int i,j;
  14852.  
  14853. /* calculate the new pivot equation */
  14854. for (j=0; j<COLUMNS; j++) {
  14855. table[leave_pos][j] =
  14856. table[leave_pos][j]/pivot_element;
  14857. }
  14858. }
  14859.  
  14860. void new_equation(void)
  14861. {
  14862.  
  14863. int i,j;
  14864.  
  14865. float enter_coef;
  14866. float new_pivot;
  14867. float new_pivot_equ;
  14868.  
  14869. /* calculate all the non-pivot EQUATIONS */
  14870. for (i=0;i<=ROWS;i++) {
  14871. enter_coef = -table[i][enter_pos];
  14872.  
  14873. /* if the pivot coefficient is zero,
  14874. or if this is the leaving equation,
  14875. skip */
  14876.  
  14877. if ( (i == leave_pos) (enter_coef == 0) )
  14878. continue;
  14879. for (j=0; j<COLUMNS; j++) {
  14880. new_pivot = table[leave_pos][j];
  14881. new_pivot_equ = new_pivot*enter_coef;
  14882. table[i][j] =
  14883. table[i][j] + new_pivot_equ;
  14884. }
  14885. }
  14886. }
  14887. /* End of File */
  14888.  
  14889.  
  14890. Listing 5 Code for labeling and printing table
  14891. void build_basis(void)
  14892. {
  14893. int i,j;
  14894. strcpy (objective[0], "z");
  14895. i = 1;
  14896.  
  14897. /* create the labels for the orig tableau */
  14898. for (j=1; j<VARIABLES+1; j++) {
  14899. strcpy (var, "X");
  14900. itoa(i, num);
  14901. strcat (var,num);
  14902. strcpy (objective[j], var);
  14903. i++;
  14904. }
  14905. i = 1;
  14906. for (j=VARIABLES+1; j<EQUATIONS+VARIABLES+1;
  14907. j++) {
  14908. strcpy (var, "s");
  14909. itoa(i, num);
  14910. strcat (var,num);
  14911. strcpy (objective[j], var);
  14912. i++;
  14913. }
  14914. strcpy (objective[j], "sol");
  14915. strcpy (var, "z");
  14916. strcpy (basis[0], var);
  14917. for (i=1;i<=EQUATIONS;i++) {
  14918. strcpy (var, "s");
  14919. itoa(i, num);
  14920. strcat (var,num);
  14921. strcpy (basis[i], var);
  14922. }
  14923. }
  14924.  
  14925. void print_table(void)
  14926. {
  14927. int i,j;
  14928. printf ("\n");
  14929. printf ("%6s"," basis ");
  14930. for (j=0; j<=COLUMNS; j++) {
  14931. if ((j==1) (j==COLUMNS-1))
  14932. printf (" ");
  14933. printf (" %6s", objective[j]);
  14934. }
  14935. printf ("\n");
  14936.  
  14937. for (j=0; j<COLUMNS+2; j++) {
  14938. printf ("-------");
  14939. }
  14940. printf ("\n");
  14941. for (i=0; i<ROWS;i++) {
  14942. printf (" %6s", basis[i]);
  14943. for (j=0; j<COLUMNS; j++) {
  14944. if ((j==0) (j==1) 
  14945. (j==COLUMNS-1))
  14946. printf (" ");
  14947. printf (" %6.2f",table[i][j]);
  14948. }
  14949. if (i==0) {
  14950. printf ("\n");
  14951. for (j=0; j<COLUMNS+2; j++) {
  14952. printf ("-------");
  14953. }
  14954. }
  14955. printf ("\n");
  14956. }
  14957. printf ("\n");
  14958. }
  14959. /* End of File */
  14960.  
  14961.  
  14962.  
  14963.  
  14964.  
  14965.  
  14966.  
  14967.  
  14968.  
  14969.  
  14970.  
  14971.  
  14972.  
  14973.  
  14974.  
  14975.  
  14976.  
  14977.  
  14978.  
  14979.  
  14980.  
  14981.  
  14982.  
  14983.  
  14984.  
  14985.  
  14986.  
  14987.  
  14988.  
  14989.  
  14990.  
  14991.  
  14992.  
  14993.  
  14994.  
  14995.  
  14996.  
  14997.  
  14998.  
  14999.  
  15000. Standard C
  15001.  
  15002.  
  15003. Time Conversion Functions
  15004.  
  15005.  
  15006.  
  15007.  
  15008. P. J. Plauger
  15009.  
  15010.  
  15011. P.J. Plauger is senior editor of The C Users Journal. He is convenor of the
  15012. ISO C standards committee, WG14, and active on the C++ committee, WG21. His
  15013. latest books are The Standard C Library, published by Prentice-Hall, and ANSI
  15014. and ISO Standard C (with Jim Brodie), published by Microsoft Press. You can
  15015. reach him at pjp@plauger.com.
  15016.  
  15017.  
  15018.  
  15019.  
  15020. Introduction
  15021.  
  15022.  
  15023. Last month, I discussed the basic functions for obtaining scalar estimates of
  15024. times. These include both elapsed execution time (type clock_t) and calendar
  15025. time (type time_t). (See "The Header <time.h>," CUJ January 1993.) With just
  15026. those facilities, you can obtain time stamps and measure small and large time
  15027. intervals.
  15028. But that's not the end of it. The Standard C library also lets you represent
  15029. times in terms of their more familiar clock and calendar components (type
  15030. struct tm). Moreover, the component representation can be in terms of either
  15031. local time or UTC (formerly GMT). It can even keep track of whether Daylight
  15032. Savings Time is in effect.
  15033. My goal this month is to lead you through the code that converts between
  15034. scalar and structured times. If you've ever worked with calendar computations,
  15035. you know that this is hard code to write and debug. That makes it rather hard
  15036. to understand as well. Be prepared to take an occasional deep breath.
  15037.  
  15038.  
  15039. What the C Standard Says
  15040.  
  15041.  
  15042.  
  15043.  
  15044. 7.12.3 Time conversion functions
  15045.  
  15046.  
  15047. Except for the strftime function, these functions return values in one of two
  15048. static objects: a broken-down time structure and an array of char. Execution
  15049. of any of the functions may overwrite the information returned in either of
  15050. these objects by any of the other functions. The implementation shall behave
  15051. as if no other library functions call these functions.
  15052.  
  15053.  
  15054. 7.12.2.3 The mktime function
  15055.  
  15056.  
  15057.  
  15058.  
  15059. Synopsis
  15060.  
  15061.  
  15062. #include <time.h>
  15063. time_t mktime
  15064. (struct tm *timeptr);
  15065.  
  15066.  
  15067. Description
  15068.  
  15069.  
  15070. The mktime function converts the broken-down time, expressed as local time, in
  15071. the structure pointed to by timeptr into a calendar time value with the same
  15072. encoding as that of the values returned by the time function. The original
  15073. values of the tm_wday and tm_yday components of the structure are ignored, and
  15074. the original values of the other components are not restricted to the ranges
  15075. indicated above.139 On successful completion, the values of the tm_wday and
  15076. tm_yday components of the structure are set appropriately, and the other
  15077. components are set to represent the specified calendar time, but with their
  15078. values forced to the ranges indicated above; the final value of tm_mday is not
  15079. set until tm_mon and tm_year are determined.
  15080.  
  15081.  
  15082. Returns
  15083.  
  15084.  
  15085.  
  15086. The mktime function returns the specified calendar time encoded as a value of
  15087. type time_t. If the calendar time cannot be represented, the function returns
  15088. the value (time_t)-1.
  15089.  
  15090.  
  15091. Example
  15092.  
  15093.  
  15094. What day of the week is July 4, 2001?
  15095. #include <stdio.h>
  15096. #include <time.h>
  15097.  
  15098. static const cha[ *const wday[] = {
  15099. "Sunday", "Monday", "Tuesday", "Wednesday",
  15100. "Thursday", "Friday", "Saturday", "-unknown-"
  15101. };
  15102. struct tm time_str;
  15103. /*...*/
  15104.  
  15105. time_str.tm_year = 2001 - 1900;
  15106. time_str.tm_mon = 7 - 1;
  15107. time_str.tm_mday = 4;
  15108. time_str.tm_hour = 0;
  15109. time_str.tm_min = 0;
  15110. time_str.tm_sec = 1;
  15111. time_str.tm_isdst = -1;
  15112. if (mktime(&time_str) == -1)
  15113. time_str. tm_wday = 7;
  15114. printf("%s\n",
  15115. wday [time_str.tm_wday]);
  15116.  
  15117. ....
  15118.  
  15119.  
  15120. 7.12.3.3 The gmtime function
  15121.  
  15122.  
  15123.  
  15124.  
  15125. Synopsis
  15126.  
  15127.  
  15128. #include <time.h>
  15129. struct tm *gmtime
  15130. (const time_t *timer);
  15131.  
  15132.  
  15133. Description
  15134.  
  15135.  
  15136. The gmtime function converts the calendar time pointed to by timer into a
  15137. broken-down time, expressed as Coordinated Universal Time (UTC).
  15138.  
  15139.  
  15140. Returns
  15141.  
  15142.  
  15143. The gmtime function returns a pointer to that object, or a null pointer if UTC
  15144. is not available.
  15145.  
  15146.  
  15147. 7.12.3.4 The local time function
  15148.  
  15149.  
  15150.  
  15151.  
  15152.  
  15153. Synopsis
  15154.  
  15155.  
  15156. #include <time.h>
  15157. struct tm *localtime
  15158. (const time_t *timer);
  15159.  
  15160.  
  15161. Description
  15162.  
  15163.  
  15164. The local time function converts the calendar time pointed to by timer into a
  15165. broken-down time, expressed as local time.
  15166.  
  15167.  
  15168. Returns
  15169.  
  15170.  
  15171. The localtime function returns a pointer to that object.
  15172. Footnotes
  15173. 139. Thus, a positive or zero value for tm_isdst causes the mktime function to
  15174. presume initially that Daylight Saving Time, respectively, is or is not in
  15175. effect for the specified time. A negative value causes it to attempt to
  15176. determine whether Daylight Saving Time is in effect for the specified time.
  15177.  
  15178.  
  15179. Using the Conversion Functions
  15180.  
  15181.  
  15182. Note that the two functions that return a value of type pointer to struct tm
  15183. return a pointer to a static data object of this type. Thus, a call to one of
  15184. these functions can alter the value stored on behalf of an earlier call to
  15185. another (or the same) function. Be careful to copy the value stored in this
  15186. shared data object if you need the value beyond a conflicting function call.
  15187. gmtime -- (The gm comes from GMT, which is now a slight misnomer.) Use this
  15188. function to convert a calendar time to a broken-down UTC time. The member
  15189. tm_isdst should be zero. If you want local time instead, use localtime, below.
  15190. See the warning about shared data objects, above.
  15191. localtime -- Use this function to convert a calendar time to a broken-down
  15192. local time. The member tm_isdst should reflect whatever the system knows about
  15193. Daylight Savings Time for that particular time and date. If you want UTC time
  15194. instead, use gmtime, above. See the warning about shared data objects, above.
  15195. mktime -- This function first puts its argument, a broken-down time, in
  15196. canonical form. That lets you add seconds, for example, to the member tm_sec
  15197. of a broken-down time. The function increases tm_min for every 60 seconds it
  15198. subtracts from tm_sec until tm_sec is in the interval [0, 59]. The function
  15199. then corrects tm_min in a similar way, then each coarser division of time
  15200. through tm_year. It determines tm_wday and tm_yday from the other fields.
  15201. Clearly, you can also alter a broken-down time by minutes, hours, days,
  15202. months, or years just as easily.
  15203. mktime then converts the broken-down time to an equivalent calendar time. It
  15204. assumes the broken-down time represents a local time. If the member tm_isdst
  15205. is less than zero, the function endeavors to determine whether Daylight
  15206. Savings Time was in effect for that particular time and date. Otherwise, it
  15207. honors the original state of the flag. Thus, the only reliable way to modify a
  15208. calendar time is to convert it to a broken-down time by calling localtime,
  15209. modify the appropriate members, then convert the result back to a calendar
  15210. time by calling mktime.
  15211.  
  15212.  
  15213. Implementing the Conversion Functions
  15214.  
  15215.  
  15216. Listing 1 shows the file gmtime.c. The function gmtime is the simpler of the
  15217. two functions that convert a calendar time in seconds (type time_t) to a
  15218. broken-down time (type struct_tm). It simply calls the internal function
  15219. _Ttotm. The first argument is a null pointer to tell _Ttotm to store the
  15220. broken-down time in the communal static data object. The third argument is
  15221. zero to insist that Daylight Savings Time is not in effect.
  15222. Listing 2 shows the file xttotm.c. It defines the function _Ttotm that tackles
  15223. the nasty business of converting seconds to years, months, days, and so forth.
  15224. The file also defines the function _Daysto that _Ttotm and other functions use
  15225. for calendar calculations.
  15226. _Daysto counts the extra days beyond 365 per year. To do so, it must determine
  15227. how may leap days have occurred between the year you specify and 1900. The
  15228. function also counts the extra days from the start of the year to the month
  15229. you specify. To do so, it must sometimes determine whether the current year is
  15230. a leap year. The function recognizes that 1900 was not a leap year. It doesn't
  15231. bother to correct for the non-leap years 1800 and earlier, or for 2100 and
  15232. later. (Other problems arise within just a few decades of those extremes
  15233. anyway.)
  15234. _Daysto handles years before 1900 only because the function mktime can develop
  15235. intermediate dates in that range and still yield a representable time_t value.
  15236. (You can start with the year 2000, back up 2,000 months, and advance 2 billion
  15237. seconds, for example.) The logic is carefully crafted to avoid integer
  15238. overflow regardless of argument values. Also, the function counts excess days
  15239. rather than total days so that it can cover a broader range of years without
  15240. fear of having its result overflow.
  15241. _Ttotm uses _Daysto to determine the year corresponding to its time argument
  15242. secsarg. Since the inverse of _Daysto is a nuisance to write, _Ttotm guesses
  15243. and iterates. At worst, it should have to back up one year to correct its
  15244. guess. Both functions use the macro MONTAB, defined at the top of the file, to
  15245. determine how many days precede the start of a given month. The macro also
  15246. assumes that every fourth year is a leap year, except 1900.
  15247. The isdst (third) argument to _Ttotm follows the convention for the isdst
  15248. member of struct tm:
  15249. If isdst is greater than zero, Daylight Savings Time is definitely in effect.
  15250. _Ttotm assumes that its caller has made any necessary adjustment to the time
  15251. argument secsarg.
  15252. If isdst is zero, Daylight Savings Time is definitely not in effect. _Ttotm
  15253. assumes that no adjustment is necessary to the time argument secsarg.
  15254. If isdst is less than zero, the caller doesn't know whether Daylight Savings
  15255. Time is in effect. _Ttotm should endeavor to find out. If the function
  15256. determines that Daylight Savings Time is in effect, it advances the time by
  15257. one hour (3,600 seconds) and recomputes the broken-down time.
  15258. Thus, _Ttotm will loop at most once. It calls the function _Isdst only if it
  15259. needs to determine whether to loop. Even then, it loops only if _Isdst
  15260. concludes that Daylight Savings Time is in effect.
  15261. Listing 3 shows the file xisdst.c. The function _Isdst determines the status
  15262. of Daylight Savings Time (DST). _Times._Isdst points at a string that spells
  15263. out the rules. (I'11 show the file asctime.c next month. It contains the
  15264. definition of _Times.)
  15265. _Isdst works with the rules in encoded form. Those rules are not current the
  15266. first time you call the function or if a change of locale alters the last
  15267. encoded version of the string _Times._Isdst. If that string is empty, _Isdst
  15268. looks for rules appended to the time-zone information _Times._Tzone. It calls
  15269. _Getzone as necessary to obtain the time-zone information. It calls _Gettime
  15270. to locate the start of any rules for DST. The function _Getdst then encodes
  15271. the current array of rules, if that is possible.
  15272. Given an encoded array of rules, _Isdst scans the array for rules that cover
  15273. the relevant year. It adjusts the day specified by the rule for any weekday
  15274. constraint, then compares the rule time against the time that it is testing.
  15275. Note that the first rule for a given starting year begins not in DST.
  15276. Successive rules for the same year go in and out of DST.
  15277. Listing 4 shows the file xgetdst.c. It defines the function _Getdst that
  15278. parses the string pointed to by _Times._Isdst to construct the array of rules.
  15279. The first character of a (non-empty) string serves as a field delimiter, just
  15280. as with other strings that provide locale-specific time information. The
  15281. function first counts these delimiters so that it can allocate the array. It
  15282. then passes over the string once more to parse and check the individual
  15283. fields.
  15284. _Getdst calls the internal function getint to convert the integer subfields in
  15285. a rule. No overflow checks occur because none of the fields can be large
  15286. enough to cause overflow. The logic here and in _Getdst proper is tedious but
  15287. straightforward.
  15288.  
  15289.  
  15290. Local Time
  15291.  
  15292.  
  15293. Listing 5 shows the file localtim.c. The function localtime calls _Ttotm much
  15294. like gmtime. Here, however, localtime assumes that it must convert a UTC time
  15295. to a local time. To do so, the function must determine the time difference, in
  15296. seconds, between UTC and the local time zone.
  15297.  
  15298. The file localtim.c also defines the function _Tzoff that endeavors to
  15299. determine this time difference (tzoff, in minutes). The time difference is not
  15300. current the first time you call the function or if a change of locale alters
  15301. the last encoded version of the string _Times._Tzone. If that string is empty,
  15302. _Tzoff calls the function _Getzone to determine the time difference from
  15303. environment variables, if that is possible.
  15304. However obtained, the string _Times._Tzone takes the form :EST:EDT:+0300.
  15305. _TzoffS calls the function _Gettime to determine the starting position (p) and
  15306. length (n) of the third field (#2, counting from zero). The function strtol,
  15307. declared in <stdlib.h> must parse this field completely in converting it to an
  15308. encoded integer. Moreover, the magnitude must not be completely insane. (The
  15309. maximum magnitude is greater than 12*60 because funny time zones exist on
  15310. either side of the International Date Line.)
  15311. Listing 6 shows the file xgettime.c. It defines the function _Gettime that
  15312. locates a field in a string that specifies locale-specific time information.
  15313. See the description of _Getdst, above, for how _Gettime interprets field
  15314. delimiters. If _Gettime cannot find the requested field, it returns a pointer
  15315. to an empty string.
  15316. Listing 7 shows the file xgetzone.c. The function_Getzone calls getenv,
  15317. declared in <stdlib.h>, to determine the value of the environment variable
  15318. "TIMEZONE". That value should have the same format as the locale-specific time
  15319. string _Times. _Tzone, described above (possibly with rules for determining
  15320. Daylight Savings Time bolted on).
  15321. If no value exists for "TIMEZONE", the function _Getzone then looks for the
  15322. environment variable "TZ". That value should match the UNIX format ESTO5EDT.
  15323. The internal function reformat uses the value of "TZ" to develop the preferred
  15324. form in its static buffer.
  15325. If _Getzone finds neither of these environment variables, it assumes that the
  15326. local time zone is UTC. In any event, it stores its decision in the static
  15327. internal buffer tzone. Subsequent calls to the function return this remembered
  15328. value. Thus, the environment variables are queried at most once, the first
  15329. time that _Getzone is called.
  15330.  
  15331.  
  15332. Converting to Scalar Time
  15333.  
  15334.  
  15335. Listing 8 shows the file mktime.c. The function mktime computes an integer
  15336. time_t from a broken-down time struct tm. It takes extreme pains to avoid
  15337. overflow in doing so. (The function is obliged to return the value --1 if the
  15338. time cannot be properly represented.)
  15339. The first part of mktime determines a year and month. If they can be
  15340. represented as type int, the function calls_Daysto to correct for leap days
  15341. since 1900. mktime then accumulates the time in seconds as type double, to
  15342. minimize further fretting about integer overflow. If the final value is
  15343. representable as type time_t, the function converts it to that type. mktime
  15344. calls _Ttotm to put the broken-down time in canonical form. Finally, the
  15345. function corrects the time in seconds for Daylight Savings Time and converts
  15346. it from local time to UTC. (The resultant code reads much easier than it
  15347. wrote.)
  15348.  
  15349.  
  15350. Conclusion
  15351.  
  15352.  
  15353. Next month, I conclude my discussion of the time functions. I also conclude my
  15354. guided tour of the Standard C library. The trip is almost over.
  15355. This article is excerpted in part from P.J. Plauger, The Standard C Library,
  15356. (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
  15357.  
  15358. Listing 1 gmtime.c
  15359. /* gmtime function */
  15360. #include "xtime.h"
  15361.  
  15362. struct tm *(gmtime)(const time_t *tod)
  15363. { /* convert to Greenwich Mean Time (UTC) */
  15364. return (_Ttotm(NULL, *tod, 0));
  15365. }
  15366. /* End of File */
  15367.  
  15368.  
  15369. Listing 2 xttotm.c
  15370. /* _Ttotm and_Daysto functions */
  15371. #include "xtime.h"
  15372.  
  15373. /* macros */
  15374. #define MONTAB(year) \
  15375. ((year) & 03 (year) == 0 ? mos : lmos)
  15376.  
  15377. /* static data */
  15378. static const short lmos[] = {0, 31, 60, 91, 121, 152,
  15379. 182, 213, 244, 274, 305, 335};
  15380. static const short mos[] = {0, 31, 59, 90, 120, 151,
  15381. 181, 212, 243, 273, 304, 334};
  15382.  
  15383. int _Daysto(int year, int mon)
  15384. { /* compute extra days to start of month */
  15385. int days;
  15386.  
  15387. if (0 < year) /* correct for leap year: 1801-2099 */
  15388. days = (year - 1) / 4;
  15389. else if (year <= -4)
  15390. days = 1 + (4 - year) / 4;
  15391. else
  15392. days = 0;
  15393. return (days + MONTAB(year)[mon]);
  15394. }
  15395.  
  15396.  
  15397. struct tm *_Ttotm(struct tm *t, time_t secsarg, int isdst)
  15398. { /* convert scalar time to time structure */
  15399. int year;
  15400. long days;
  15401. time_t secs;
  15402. static struct tm ts;
  15403.  
  15404. secsarg += _TBIAS;
  15405. if (t == NULL)
  15406. t = &ts;
  15407. t->tm_isdst = isdst;
  15408. for (secs = secsarg; ; secs = secsarg + 3600)
  15409. { /* loop to correct for DST */
  15410. days = secs / 86400;
  15411. t->tm_wday = (days + WDAY) % 7;
  15412. { /* determine year */
  15413. long i;
  15414.  
  15415. for (year = days / 365; days <
  15416. (i = _Daysto(year, 0) + 365L * year); )
  15417. --year; /* correct guess and recheck */
  15418. days -= i;
  15419. t->tm_year = year;
  15420. t->tm_yday = days;
  15421. }
  15422. { /* determine month */
  15423. int mon;
  15424. const short *pm = MONTAB(year);
  15425.  
  15426. for (mon = 12; days < pm[--mon]; )
  15427. ;
  15428. t->tm_mon = mon;
  15429. t->tm_mday = days - pm[mon] + 1;
  15430. }
  15431. secs %= 86400;
  15432. t->tm_hour =secs / 3600;
  15433. secs %= 3600;
  15434. t->tm_min = secs / 60;
  15435. t->tm_sec = secs % 60;
  15436. if (0 <= t->tm_isdst (t->tm_isdst = _Isdst(t)) <= 0)
  15437. return (t); /* loop only if <0 => 1 */
  15438. }
  15439. }
  15440. /* End of File */
  15441.  
  15442.  
  15443. Listing 3 xisdst.c
  15444. /* _Isdst function */
  15445. #include <stdlib.h>
  15446. #include "xtime.h"
  15447.  
  15448. int _Isdst(const struct tm *t)
  15449. { /* test whether Daylight Savings Time
  15450. in effect */
  15451. Dstrule *pr;
  15452. static const char *olddst = NULL;
  15453. static Dstrule *rules = NULL;
  15454.  
  15455. if (olddst != _Times._Isdst)
  15456.  
  15457. { /* find current dst_rules */
  15458. if (_Times._Isdst[0] == '\0')
  15459. { /* look beyond time zone info */
  15460. int n;
  15461.  
  15462. if (_Times._Tzone[0] == '\0')
  15463. _Times._Tzone = Getzone();
  15464. _Times._Isdst = _Gettime(_Times._Tzone,
  15465. 3, &n);
  15466. if (_Times._Isdst[0] != '\0')
  15467. --_Times._Isdst; /* point to
  15468. delimiter */
  15469. }
  15470. if ((pr = _Getdst(_Times._Isdst)) == NULL)
  15471. return (-1);
  15472. free(rules);
  15473. rules = pr;
  15474. olddst = _Times._Isdst;
  15475. }
  15476. { /* check time against rules */
  15477. int ans = 0;
  15478. const int d0 = _Daysto(t->tm_year, 0);
  15479. const int hour = t->tm_hour + 24 * t->tm_yday;
  15480. const int wd0 = (365L * t->tm_year + d0 + WDAY)
  15481. % 7 + 14;
  15482.  
  15483. for (pr = rules; pr->wday != (unsigned char)-1; ++pr)
  15484. if (pr->year <= t->tm_year)
  15485. { /* found early enough year */
  15486. int rday = _Daysto(t->tm_year, pr->mon)
  15487. - d0 + pr->day;
  15488.  
  15489. if (0 < pr->wday)
  15490. { /* shift to specific weekday */
  15491. int wd = (rday + wd0 - pr->wday) % 7;
  15492.  
  15493. rday += wd == 0 ? 0 : 7 - wd;
  15494. if (pr->wday <= 7)
  15495. rday -= 7; /* strictly before */
  15496. }
  15497. if (hour < rday * 24 + pr->hour)
  15498. return (ans);
  15499. ans = pr->year == (pr + 1)->year
  15500. ? !ans : 0;
  15501. }
  15502. return (ans);
  15503. }
  15504. }
  15505. /* End of File */
  15506.  
  15507.  
  15508. Listing 4 xgetdst.c
  15509. /* _Getdst function */
  15510. #include <ctype.h>
  15511. #include <stdlib.h>
  15512. #include <string.h>
  15513. #include "xtime.h"
  15514.  
  15515. static int getint(const char *s, int n)
  15516.  
  15517. { /* accumulate digits */
  15518. int value;
  15519.  
  15520. for (value = 0; 0 <= --n && isdigit(*s); ++s)
  15521. value = value * 10 + *s - '0';
  15522. return (0 <= n ? -1 : value);
  15523. }
  15524.  
  15525. Dstrule *_Getdst(const char *s)
  15526. { /* parse DST rules */
  15527. const char delim = *s++;
  15528. Dstrule *pr, *rules;
  15529.  
  15530. if (delim == '\0')
  15531. return (NULL);
  15532. { /* buy space for rules */
  15533. const char *s1, *s2;
  15534. int i;
  15535.  
  15536. for (s1 = s, i = 2; (s2 = strchr(s1, delim)) != NULL; ++i)
  15537. s1 = s2 + 1;
  15538. if ((rules = (Dstrule *)malloc(sizeof (Dstrule) * i)) == NULL)
  15539. return (NULL);
  15540. }
  15541. { /* parse rules */
  15542. int year = 0;
  15543.  
  15544. for (pr = rules; ; ++pr, ++s)
  15545. { /* parse next rule */
  15546. if (*s == '(')
  15547. { /* got a year qualifier */
  15548. year = getint(s + 1, 4) - 1900;
  15549. if (year < 0 s[5] != ')')
  15550. break; /* invalid year */
  15551. s += 6;
  15552. }
  15553. pr->year = year;
  15554. pr->mon = getint(s, 2) - 1; s += 2;
  15555. pr->day = getint(s, 2) - 1; s += 2;
  15556. if (isdigit(*s))
  15557. { pr->hour = getint(s, 2); s += 2; }
  15558. else
  15559. pr->hour = 0;
  15560. if (12 <= pr->mon 99 < pr->day 99 < pr->hour)
  15561. break; /* invalid month, day, or hour */
  15562. if (*s != '+' && *s != '-')
  15563. pr->wday = 0;
  15564. else if (s[1] < '0' '6' < s[1])
  15565. break; /* invalid week day */
  15566. else
  15567. { /* compute week day field */
  15568. pr->wday = s[1] == '0' ? 7 : s[1] - '0';
  15569. if (*s == '+') /* '-': strictly before */
  15570. pr->wday += 7; /* '+': on or after */
  15571. s += 2;
  15572. }
  15573. if (*s == '\0')
  15574. { /* done, terminate list */
  15575. (pr + 1)->wday = (unsigned char)-1;
  15576.  
  15577. (pr + 1)->year = year;
  15578. return (rules);
  15579. }
  15580. else if (*s != delim)
  15581. break;
  15582. }
  15583. free(rules);
  15584. return (NULL);
  15585. }
  15586. }
  15587. /* End of File */
  15588.  
  15589.  
  15590. Listing 5 localtim.c
  15591. /* localtime function */
  15592. #include <stdlib.h>
  15593. #include "xtime.h"
  15594. time_t _Tzoff(void)
  15595. { /* determine local time offset */
  15596. static const char *oldzone = NULL;
  15597. static long tzoff = 0;
  15598. static const long maxtz = 60*13;
  15599. if (oldzone ! = _Times._Tzone)
  15600. { /* determine time zone offset
  15601. (East is +) */
  15602. const char *p, *pe;
  15603. int n;
  15604. if {_Times._Tzone[0] == '\0')
  15605. _Times._Tzone = _Getzone();
  15606. p = _Gettime(_Times._Tzone, 2, &n);
  15607. tzoff = strtol(p, (char **)&pe, 10);
  15608. if (pe - p != n
  15609.  tzoff <= -maxtz maxtz <= tzoff)
  15610. tzoff = 0;
  15611. oldzone = _Times._Tzone;
  15612. }
  15613. return (-tzoff * 60);
  15614. }
  15615. struct tm *(localtime){const time_t *tod)
  15616. { /* convert to local time structure */
  15617. return (_Ttotm(NULL, *tod + _Tzoff(), -1));
  15618. }
  15619. /* End of File */
  15620.  
  15621.  
  15622. Listing 6 xgettime.c
  15623. /* _Gettime function */
  15624. #include <string.h>
  15625. #include "xtime.h"
  15626.  
  15627. const char *_Gettime(const char *s, int n, int *len)
  15628. { /* get time info from environment */
  15629. const char delim = *s ? *s++ : '\0';
  15630. const char *s1;
  15631.  
  15632. for (; ; --n, s = s1 + 1)
  15633. { /* find end of current field */
  15634. if ((s1 = strchr(s, delim)) == NULL)
  15635. s1 = s + strlen(s);
  15636.  
  15637. if (n <= 0)
  15638. { /* found proper field */
  15639. *len = s1 - s;
  15640. return (s);
  15641. }
  15642. else if (*s1 == '\0')
  15643. { /* not enough fields */
  15644. *len = 1;
  15645. return (s1);
  15646. }
  15647. }
  15648. }
  15649. /* End of File */
  15650.  
  15651.  
  15652. Listing 7 xgetzone.c
  15653. /*_Getzone function */
  15654. #include <ctype.h>
  15655. #include <stdlib.h>
  15656. #include <string.h>
  15657. #include "xtime.h"
  15658.  
  15659. /* static data */
  15660. static const char *defzone = ":UTC:UTC:0";
  15661. static char *tzone = NULL;
  15662.  
  15663. static char *reformat(const char *s)
  15664. { /* reformat TZ */
  15665. int i, val;
  15666. static char tzbuf[] = ":EST:EDT:+0300";
  15667.  
  15668. for (i = 1; i <= 3; ++i)
  15669. if (isalpha(*s))
  15670. { tzbuf[i] = *s; tzbuf[i + 4] = *s++; }
  15671. else
  15672. return (NULL);
  15673. tzbuf[9] = *s == '-' *s == '+' ? *s++ : '+';
  15674. if (!isdigit(*s))
  15675. return (NULL);
  15676. val = *s++ - '0';
  15677. if (isdigit(*s))
  15678. val = 10 * val + *s++ - '0';
  15679. for (val *= 60, i = 13; 10 <= i; --i, val /= 10)
  15680. tzbuf[i] = val % 10 + '0';
  15681. if (isalpha(*s))
  15682. for (i = 5; i <= 7; ++i)
  15683. if (isalpha(*s))
  15684. tzbuf[i] = *s++;
  15685. else
  15686. return (NULL);
  15687. return (*s == '\0' ? tzbuf = NULL);
  15688. }
  15689.  
  15690. const char *_Getzone(void)
  15691. { /* get time zone information */
  15692. const char *s;
  15693.  
  15694. if (tzone)
  15695. ;
  15696.  
  15697. else if ((s = getenv("TIMEZONE")) != NULL)
  15698. { /* copy desired format */
  15699. if ((tzone = (char *)malloc(strlen(s) + 1))
  15700. != NULL)
  15701. strcpy(tzone, s);
  15702. }
  15703. else if ((s = getenv("TZ")) != NULL)
  15704. tzone = reformat(s);
  15705. if (tzone == NULL)
  15706. tzone = (char *)defzone;
  15707. return (tzone);
  15708. }
  15709. /* End of File */
  15710.  
  15711.  
  15712. Listing 8 mktime.c
  15713. /* mktime function */
  15714. #include <limits.h>
  15715. #include "xtime.h"
  15716.  
  15717. time_t (mktime)(struct tm *t)
  15718. { /* convert local time structure
  15719. to scalar time */
  15720. double dsecs;
  15721. int mon, year, ymon;
  15722. time_t secs;
  15723.  
  15724. ymon = t->tm_mon / 12;
  15725. mon = t->tm_mon - ymon * 12;
  15726. if (mon < 0)
  15727. mon += 12, --ymon;
  15728. if (ymon < 0 && t->tm_year < INT_MIN - ymon
  15729.  0 < ymon && INT_MAX - ymon < t->tm_year)
  15730. return ((time_t)(-1));
  15731. year = t->tm_year + ymon;
  15732. dsecs = 86400.0 * (_Daysto(year, mon) - 1)
  15733. + 31536000.0 * year + 86400.0 * t->tm_mday;
  15734. dsecs += 3600.0 * t->tm hour + 60.0 * t->tm_min
  15735. + (double)t->tm-sec;
  15736. if (dsecs < 0.0 (double)(time_t)(-1) <= dsecs)
  15737. return ((time_t)(-1));
  15738. secs = (time_t)dsecs - _TBIAS;
  15739. _Ttotm(t, secs, t->tm_isdst);
  15740. if (0 < t->tm_isdst)
  15741. secs -= 3600;
  15742. return (secs - _Tzoff());
  15743. }
  15744. /* End of File */
  15745.  
  15746.  
  15747.  
  15748.  
  15749.  
  15750.  
  15751.  
  15752.  
  15753.  
  15754.  
  15755.  
  15756.  
  15757.  
  15758.  
  15759.  
  15760.  
  15761.  
  15762.  
  15763.  
  15764.  
  15765.  
  15766.  
  15767.  
  15768.  
  15769.  
  15770.  
  15771.  
  15772.  
  15773.  
  15774.  
  15775.  
  15776.  
  15777.  
  15778.  
  15779.  
  15780.  
  15781.  
  15782.  
  15783.  
  15784.  
  15785.  
  15786.  
  15787.  
  15788.  
  15789.  
  15790.  
  15791.  
  15792.  
  15793.  
  15794.  
  15795.  
  15796.  
  15797.  
  15798.  
  15799.  
  15800.  
  15801.  
  15802.  
  15803.  
  15804.  
  15805.  
  15806.  
  15807.  
  15808.  
  15809.  
  15810.  
  15811.  
  15812.  
  15813.  
  15814.  
  15815.  
  15816.  
  15817.  
  15818.  
  15819.  
  15820. On the Networks
  15821.  
  15822.  
  15823. What Happened -- Again?
  15824.  
  15825.  
  15826.  
  15827.  
  15828. Sydney S. Weinstein
  15829.  
  15830.  
  15831. Sydney S. Weinstein, CDP, CCP is a consultant, columnist, author, and
  15832. president of Datacomp Systems, Inc., a consulting and contract programming
  15833. firm specializing in databases, data presentation and windowing, transaction
  15834. processing, networking, testing and test suites, and device management for
  15835. UNIX and MS-DOS. He can be contacted care of Datacomp Systems, Inc., 3837
  15836. Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail on the
  15837. Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
  15838. Internet addressing).
  15839.  
  15840.  
  15841. Once again, things have dried up in comp.sources.unix. But this time they have
  15842. also dried up in comp.sources.x. Both groups have been extremely quiet, with
  15843. no postings in comp.sources.unix. and only one in comp.sources.x. But what is
  15844. unusual is that there hasn't been any word from the moderators of each group
  15845. about any problems.
  15846. Also different this time is that the other groups have also quieted down. This
  15847. is unusual. Normally when one of the groups quiets down, the others overflow
  15848. with the items that would have been posted to that group. This time even the
  15849. catchall group, comp.sources.misc has been relatively quiet.
  15850. I can only chalk this up to a combination of three things:
  15851. Fewer new sources are being written for free distribution.
  15852. The concept of FTP mirror sites and the vast number of people now able to
  15853. access them has reduced the importance of having the software posted to a main
  15854. stream USENET Network News group.
  15855. CD-ROMs are being widely used as an alternative distribution medium.
  15856. I can say that the last two are true about the Version 2.4 release of Elm. It
  15857. was submitted to the moderators of comp.sources.unix two months ago as of this
  15858. writing, and I haven't pressed as to why it hasn't been distributed yet
  15859. because we now use a set of FTP mirror sites that include ones able to be
  15860. accessed via UUCP. In addition we publish it on several of the CD-ROM
  15861. collections.
  15862. The single posting in comp.sources.x consisted of wscrawl from Brian Wilson
  15863. <brianw@apple.com>. The word "wscrawl" stands for "window-scrawl." The user
  15864. may think of wscrawl v2.0 as a paint program shared by any number of people at
  15865. the same time. When wscrawl is run, it opens up a separate window on each
  15866. participant's display. From that point onward, each participant sees the
  15867. actions and ideas of every other participant as they occur. Each individual
  15868. may simply watch, or participate at any moment. Any individual may exit out of
  15869. the session at any time without affecting the other participants. wscrawl
  15870. requires Motif 1.1 to compile but binaries for common architectures are
  15871. available from 128.109.178.23 via anonymous ftp.
  15872.  
  15873.  
  15874. Reviews Alive, Barely
  15875.  
  15876.  
  15877. The only controlled newsgroup that had activity was comp.sources.reviewed,
  15878. with updates to two packages and two patches.
  15879. Mike Lijewski <lijewski@theory.tc.cornell.edu> updated his Directory Editor,
  15880. written in C++, to version 1.8. dired was posted as Volume 2, Issues 32-37.
  15881. dired is a directory editor modeled after Dired Mode of GNU Emacs, but
  15882. targeted for non-emacs users and designed for the UNIX environment. In
  15883. addition to fixing bugs, and portability problems, about two pages worth of
  15884. new features have been added.
  15885. Version 1.7 of cextract from Adam Bryant <adb@cs.bu.edu> was posted in Volume
  15886. 2, Issues 38-43. cextract is a C prototype extractor. It is designed to
  15887. generate header files for large multifile C programs, and will provide an
  15888. automated method for generating all of the prototypes for all of the functions
  15889. in such a program. It may also function as a rudimentary documentation
  15890. extractor, generating a sorted list of all functions and their locations.
  15891. Changes since the last posted version (1.2) include portability fixes, file
  15892. name and line numbers in error messages, improved documentation, support for
  15893. C++ // comments, proper support of the __STDC__ #if construct, and support for
  15894. runtime selection of which C preprocessor to be used.
  15895. On the patch front, ncompress received patch 1 in Volume 2, Issue 27. Peter
  15896. Jannesen <peter@ncs.nl> fixed the -c flag, a utime error, and added support
  15897. for the AMIGA. ncompress is an enhancement to the USENET de facto standard
  15898. file-compaction program to increase its speed.
  15899. Volume 2, Issues 28-31 were patch 2 to mawk from Mike Brennan
  15900. <brennan@boeing.com>. This was a bug fix patch to this awk work-alike and also
  15901. added new configuration support for AIX, Convex, and SVR4/386.
  15902.  
  15903.  
  15904. Even misc is Updates
  15905.  
  15906.  
  15907. Most of the postings in comp.sources.misc were also patches and updates to
  15908. existing packages. Please don't tell me that everything good has already been
  15909. written.
  15910. The patches included patch 2 to oraperl v2 from Kevin Stock
  15911. <kstock@encore.com> in Volume 32, Issue 93. oraperl is a set of extensions to
  15912. perl to access Oracle databases. This patch handles null fields properly and a
  15913. change to ora_ titles to allow truncating the titles to the data width or
  15914. retrieving the full titles of the columns.
  15915. ECU, which was highlighted in the last column, had patch 3 posted by Warren
  15916. Tucker <wht@n4hgf.Mt-Park.GA.US> for Volume 32, Issue 96. Changes include
  15917. addition of a -l flag to fkmap to load an alternate map from the library
  15918. directory, changes to how seven-bit mode is reported and recognizing the ,M
  15919. suffix in the UUCP Devices tables for all platform types.
  15920. John F. Haugh II's <jfh@rpp386.cactus.org> shadow password suite, shadow, was
  15921. updated to version 3.2.2 by patch 6 posted as Volume 32, Issues 98-100. The
  15922. most significant change made for this patch is the addition of
  15923. administrator-defined authentication mechanisms. This allows the system
  15924. administrator to replace the standard encrypted password with a program which
  15925. performs the authentication. Any user-written program may be used. This
  15926. feature may be used to add any of a number of authentication schemes. Haugh
  15927. has also started to revise the commands to work both with the shadow password
  15928. and with the standard password file.
  15929. The Mail Users Shell has been updated with patch 5 for version 7.2 by Bart
  15930. Schaefer <bart@zigzag.z-code.com> in Volume 32, Issues 101-103. New in this
  15931. version is POP Support, MIME support, expansion of variables now more closely
  15932. resembles how the shells handle it, and checking for the UNIX From_ line even
  15933. if MMDF is being used. Of course there are also numerous bugs fixed.
  15934. Chip Rosenthal <chip@chinacat.unicom.com> issued a portability patch for SCO
  15935. XENIX 2.3.3 users to his prtscrn2 screen capture program (for SCO UNIX and
  15936. XENIX console screens). This very short patch fixes up the doc a bit and adds
  15937. a missing define for that version of the OS. It appeared as Volume 33, Issue
  15938. 29.
  15939. The delete/undelete utilities for UNIX from Jonathan I. Kamens
  15940. <jik@Athena.MIT.EDU> were updated to patchlevel 15 with Volume 33, Issue 70.
  15941. This is a bug fix patch which corrects malloc problems, POSIX dirent support,
  15942. AFT mount point problems, some memory allocation problems on nesting, and a
  15943. bug in symlink support.
  15944. Leaving the patches and turning to packages, yet another graphical directory
  15945. tree program was contributed by Tom A. Baker <tombaker@world.std.com> for
  15946. Volume 32, Issue 97. tbtree allows users to get a visual idea of where things
  15947. are on their system. It was written on SunOS, and with a little work should
  15948. port to other flavors of UNIX.
  15949. A device driver for UNIX System V.3 to implement the poll and select system
  15950. calls was submitted for Volume 32, Issue 105 by Bruce Momjian
  15951. <candle!root>.poll and select are UNIX C library functions that allow programs
  15952. to determine if a number of file descriptors are ready for reading or writing.
  15953. In addition, his pol package includes a modified version of the public domain
  15954. System V pty device driver written by Jens-Uwe Mager, with changes for System
  15955. V by Michael Bloom.
  15956. Guido Gronek <gg@trillian.tp1.ruhr-uni-bochum.de> had the need for a fast
  15957. substring search routine. He created qsearch, an ANSI-C implementation that
  15958. searches for the leftmost or rightmost occurrence of a pattern string in a
  15959. text string. The algorithm used is "quick search" by D. M. Sunday, which is a
  15960. simple but fast practical method. It's supposed to be even faster than the
  15961. well known Boyer-Moore algorithm. (See Sunday's original paper, CACM 33.8,
  15962. page 132 for several improvements of the basic method as well.) He implemented
  15963. the reverse text scanning by a rather simple variation of the original
  15964. algorithm. qsearch was posted as Volume 32, Issue 106.
  15965. A newer derivative to an older vi (a UNIX text editor) clone package called
  15966. stevie, was contributed by John Downey <jmd@cyclone.bt.co.uk>. Xvi was posted
  15967. as Volume 33, Issues 10-27. While the name starts with X, there is as of yet
  15968. no specific X-window version of the editor. Xvi is a portable multi-window
  15969. version of vi that uses text windows separated by horizontal status lines on
  15970. character-mode displays. The windows may represent different files being
  15971. edited, or different views on to the same file.
  15972. For those just learning vi, Wes Craig <wes.craig@umich.edu> contributed
  15973. vilearn for Volume 33, Issue 35. There are five short tutorials, each a text
  15974. file intended to be edited with vi. The first, "Basic Editing," covers the
  15975. handful of commands required to both navigate all five tutorials and do basic
  15976. editing. The second tutorial, "Moving Efficiently," covers all of the cursor
  15977. positioning commands. These are the commands used later as arguments to
  15978. editing commands. Tutorial three, "Cutting and Pasting," introduces the first
  15979. compound commands, numbering, and copy buffers. The "Inserting Techniques"
  15980. tutorial continues the discussion of compound commands, while completing the
  15981. list of insertion commands first discussed in tutorial one. The final
  15982. tutorial, "Tricks and Timesavers," is less a tutorial than a description of
  15983. common vi commands which don't fit correctly into normal vi logic.
  15984. An interpreter for a superset of the ANSI Standard for Minimal BASIC
  15985. (X3.60-1978) was contributed by Ted A. Campbell <tcamp@acpub.duke.edu> for
  15986. Volume 33, Issues 37-47. bwbasic is implemented in ANSI C, and offers a simple
  15987. interactive environment including some shell program facilities as an
  15988. extension of BASIC. The interpreter has been compiled successfully on a range
  15989. of ANSI C compilers on varying platforms with no alterations to source code
  15990. necessary.
  15991. An update to tarmail v2.3 was contributed for Volume 33, Issue 36 by Paul Lew
  15992. <lew@gsg.gsg.com>. tarmail and untarmail provide a reliable way to send files
  15993. through electronic mail systems. Large files will be divided into smaller
  15994. chunks for transmission. Unlike the previous version of tarmail, the new
  15995. tarmail will attach a CRC checksum on an individual chunk instead of on the
  15996. entire file(s). The ability to retransmit only the faulty chunks make tarmail
  15997. the idea tool for sending files by electronic mail.
  15998. Brendan Kehoe <brendan@cygnus.com> has updated his archie client in Volume 33,
  15999. Issues 50-56. archie is a system to locate programs stored in the various
  16000. anonymous ftp archives on the Internet. This patch takes archie to 1.4.1.
  16001. David F. Skoll <dfs@doe.carleton.ca> has released an update to his remind
  16002. package. Version 3.0.0 was contributed for Volume 33, Issues 58-69. remind is
  16003. a sophisticated reminder program. It has a flexible and powerful script
  16004. language, and allows you to easily specify reminders for most occasions
  16005. including: particular dates (birthdays, etc.), holidays like Labor day, which
  16006. occur on a particular weekday of a month, dates which repeat with an arbitrary
  16007. period, meetings which are automatically moved in the event of holidays among
  16008. others. remind also includes a feature to activate timed alarms in the
  16009. background. remind should work on most UNIX systems, as well as MS-DOS. This
  16010. allows you to use the same script on your UNIX and MS-DOS systems.
  16011. A program to calculate dates in the Jewish calendar for a given gregorian year
  16012. was contributed by Danny Sadinoff <sadinoff@unagi.cis.upenn.edu>. hebcal v1.2,
  16013. Volume 33, Issue 71, is fairly flexible in terms of which events in the Jewish
  16014. calendar it displays. Each of the following can be individually turned on or
  16015. off: the Hebrew date, Jewish holidays (including Yom Ha'atzmaut and Yom
  16016. HaShoah etc.), the weekly Sedrah, the day of the week, and the days of the
  16017. Omer.
  16018. Mike Lijewski <lijewski@rosserv.gsfc.nasa.gov> contributed problem v1.1, a
  16019. database manager for bug reports and such, meant to be used in a UNIX
  16020. environment. It is written in C++, uses the GNU Database Management Library
  16021. (GDBM) for low-level database operations, and uses the termcap(3) library for
  16022. screen control. The basic idea is to provide a central front-end for managing
  16023. the various databases of bugs and miscreant behaviour that a large UNIX site
  16024. might be interested in tracking, and facilitating the sharing of this
  16025. information amongst all interested parties. Version 1.1 was posted in Volume
  16026. 33, Issues 72-78.
  16027.  
  16028.  
  16029.  
  16030. Twice, Twice
  16031.  
  16032.  
  16033. The trend in comp.sources.games is to post things twice. It happened a bit
  16034. this time, where the same game got posted and then updated with a newer
  16035. version.
  16036. A version of the popular game scrabble was contributed as scrabble2 by James
  16037. A. Cherry <jac@doe.carleton.ca> for Volume 14, Issues 93-110 with patch1 in
  16038. Volume 15, Issue 9. This version of scrabble is curses-based and uses a
  16039. dictionary to allow play against one or more computer opponents. It only
  16040. requires a text-based screen.
  16041. A generic tetris game for X11R4/5 was submitted by Qiang Alex Zhao
  16042. <azhao@cs.arizona.edu> for Volume 15, Issues 5 and 6. gtetris2 is a generic
  16043. version that uses no toolkit, only Xlib, and is highly portable.
  16044. Thomas Grennefors <etxtsg@solsta.ericsson.se> contributed xminesweeper for
  16045. Volume 15, Issue 3. It is a game where your task is to find the hidden mines
  16046. in a minefield. Play is with the mouse and buttons control marking where the
  16047. mines are, or marking safe squares. One wrong move and the game is over.
  16048. Patch1 appeared in Volume 15, Issue 10.
  16049. Xstratego is a X-window-based stratego interface for two players submitted by
  16050. Henk-Jan Visscher <hjvissc@cs.vu.nl> for Volume 15, Issues 11-14. You can
  16051. either play against another player (on the same or different host) or create a
  16052. board setup for later use. It uses the Xaw toolkit.
  16053. Two different curses-based versions of reversi were posted. The first,
  16054. reversi, contributed by Elias Martensson <elias@proxxi.se> was posted in
  16055. Volume 15, Issues 7 and 8. Cursor movement is via the usual h, j, k, and l
  16056. keys. The second, reversi2, was contributed, but not written, by Eric Safern
  16057. <esafern@shearson.COM> and was posted in Volume 15, Issues 18 and 19. This
  16058. version supports both human vs. computer and two human player mode.
  16059. A restructuring of bt4, the broken throne multiplayer real-time conquest game
  16060. was submitted by Tom Boutell <boutell@isis.cshl.org> in Volume 15, Issues
  16061. 15-18. Added are some new features and support for AIX. Note, this game
  16062. requires INET sockets (the BSD socket interface) as well as curses.
  16063.  
  16064.  
  16065. Previews from alt. sources
  16066.  
  16067.  
  16068. At least this hasn't been quiet. It sounds like only the moderated groups are
  16069. being shunned. Here are the highlights of what I hope will reappear on the
  16070. moderated source groups.
  16071. Version 1.2 of uustatus, a real-time UUCP status monitor for BSD and System V
  16072. was posted on July 17, 1992 by Ed Carp <unislc!erc> in one part. This simple
  16073. program dynamically displays the status of your UUCP connections for you,
  16074. without your having to cd into all of those pesky directories. It's also
  16075. faster than uustat -m, and it's real-time! Bug fixes were posted in a one-part
  16076. patch on August 5, 1992.
  16077. A program to convert xwd (X-window dump) files into color or gray-scale
  16078. PostScript was posted on August 30, 1992 in two parts by Brian Totty
  16079. <totty@flute.cs.uiuc.edu>. Colors can be stored in an array to allow for
  16080. mapping to be modified after the file is generated.
  16081. Steve Cole <steve@sep.Stanford.EDU> contributed xtpanel, a program to build an
  16082. interactive X program from the command line using a simple scripting language.
  16083. It is not intended as a replacement for a full-featured interface-programming
  16084. toolkit or as a replacement for a simple menu builder. It falls somewhere in
  16085. the gap between the two. It is intended as an easy to use tool that can be
  16086. used to add an interactive wrapper to all those old programs and shells that
  16087. you have lying around. It was posted on September 3, 1992 in eight parts.
  16088. The tin newsreader was updated with two patches by Iain Lea
  16089. <Iain.Lea%anl433.uucp@Germany.EU.net> on September 14, 1992 in 15 parts, a
  16090. complete report at patchlevel 6, and on November 15, 1992 in 10 parts, a patch
  16091. to PL6 taking it to PL7. Many new features have been added to the threader
  16092. news reader including CD-ROM support, INN support, support for many more OS
  16093. types and of course more bug fixes.
  16094. If you find your cursor on the X display is always in the way, unclutter,
  16095. posted in one part on September 28, 1992 by Charles Hannum
  16096. <mycroft@hal.gnu.ai.mit.edu> will help. unclutter is a program which runs
  16097. permanently in the background of an X11 session. It checks on the X11 pointer
  16098. (cursor) position every few seconds, and when it finds it has not moved (and
  16099. no buttons are pressed on the mouse, and the cursor is not in the root window)
  16100. it creates a small sub-window as a child of the window the cursor is in. The
  16101. new window installs a cursor of size 1x1 but a mask of all 0, i.e. an
  16102. invisible cursor. This allows you to see all the text in an xterm or xedit,
  16103. for example. The human-factors crowd would agree it should make things less
  16104. distracting. Once created, the program waits for the pointer to leave the
  16105. window and then destroys it, restoring the original situation and thereby
  16106. redisplaying the cursor.
  16107. Last is the beta release of version 3.0 of Steven Grimm's
  16108. <koreth@hyperion.com> workman v3.0. Posted on November 16, 1992 in six parts,
  16109. its an X CD player program. It requires XView 3.0 or higher (XView source is
  16110. available with the X11R5 distribution) and runs under both SunOS 4.x and
  16111. Solaris 2. The major new feature is that tracks may now be split into sections
  16112. at arbitrary locations. You may split a track while the CD is playing, useful
  16113. for marking off particular sections of a song. Sections may be named and
  16114. selected just like tracks. Of course, if the CD-ROM drive doesn't support data
  16115. across the SCSI bus (and Sun's doesn't) you must use external speakers or a
  16116. patch cable to take the analog sound from the CD-ROM.
  16117.  
  16118.  
  16119.  
  16120.  
  16121.  
  16122.  
  16123.  
  16124.  
  16125.  
  16126.  
  16127.  
  16128.  
  16129.  
  16130.  
  16131.  
  16132.  
  16133.  
  16134.  
  16135.  
  16136.  
  16137.  
  16138.  
  16139.  
  16140.  
  16141.  
  16142.  
  16143.  
  16144.  
  16145.  
  16146.  
  16147.  
  16148.  
  16149.  
  16150.  
  16151.  
  16152.  
  16153.  
  16154.  
  16155.  
  16156.  
  16157.  
  16158. Questions & Answers
  16159.  
  16160.  
  16161. Macros and Debugging
  16162.  
  16163.  
  16164.  
  16165.  
  16166. Ken Pugh
  16167.  
  16168.  
  16169. Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++
  16170. language courses for corporations. He is the author of C Language for
  16171. Programmers and All On C, and was a member on the ANSI C committee. He also
  16172. does custom C programming for communications, graphics, image databases, and
  16173. hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707.
  16174. You may fax questions for Ken to (919) 489-5239. Ken also receives email at
  16175. kpugh@dukemvs.ac.duke.edu (Internet) and on Compuserve 70125,1142.
  16176.  
  16177.  
  16178. Q
  16179. It seems sort of ironic that the issue devoted to debugging contains an
  16180. example of using macros to solve the "Fixed Field Files." (This arrangement
  16181. produces a set of macro invocations.) The article, "Glass-Box Testing" hints
  16182. at how to use debugging to break code and verify coverage. Unfortunately
  16183. macros can't be debugged. After reviewing, understanding, and maintaining the
  16184. code in various products, I've come to the conclusion that C programmers abuse
  16185. the macros and the preprocessor in general.
  16186. I am a big convert to C++ because the macro preprocessor hardly needs to be
  16187. used. With true consts and inline functions there is no need to write nasty
  16188. macros. The following code is written in C-style
  16189. #define MAX_LINE 80
  16190. getline( char ** line )
  16191. {
  16192. static char myline[MAX_LINE];
  16193. }
  16194. Compare it to
  16195. const int MAX_LINE=80;
  16196. getline( char ** line )
  16197. {
  16198. static char myline[MAX_LINE];
  16199. }
  16200. which is written in C++-style.
  16201. Now with a debugger you can print MAX_LINE without saying
  16202. find /usr/project/x400 -name *.h -name *.c \
  16203. -exec grep '#define MAX_LINE'
  16204. to search through 200 header files to find the correct macro definition.
  16205. As for C++ I'm just praying that programmers will not go overboard and use
  16206. overloaded functions in C++ too much. Because of the inheritance/dynamic
  16207. binding we can have multiple overloaded function(s) in each class, and the
  16208. superclass (base class) can have those same overloaded classes defined. I
  16209. guess it would look something like Listing 1.
  16210. What's SIZE? The problem gets worse as the program gets larger. I was working
  16211. on debugging my project and reading the C Users Journal in between the
  16212. compiles. I hope in the future programmers will recognize that in the world of
  16213. "reuse" that readability must come before performance and space
  16214. considerations.
  16215. I know a programmer who does the following because he's too lazy to write the
  16216. whole line:
  16217. #define ptr
  16218. a.get.line.alpha.beta ptr->data
  16219. Now when I try and print ptr, the debugger says, ptr not defined. I guess you
  16220. probably get the message. By the way. David Staker's book, C: Style Standard &
  16221. Guidelines is quite informative.
  16222. David A. Dennerline
  16223. A
  16224. I definitely agree with you that C++ can help a programmer write better code,
  16225. if it is utilized properly. But basic design rules need to be followed,
  16226. especially in using meaningful names.
  16227. I cannot emphasize too much that when one writes a program, it should be
  16228. written in English or in whatever native language code set is supported by
  16229. one's compiler. Meaningful variable, member, parameter, and function names go
  16230. a long way to writing maintainable code. Making too many assumptions about
  16231. what the reader knows can result in programs that are difficult to understand.
  16232. The member function names are particularly important in C++ as one is not
  16233. supposed to look at the implementation to determine what the function is
  16234. doing.
  16235. Let me take your example functions, which were created for example purposes,
  16236. rather than as actual code. Looking at the parameter names gives no clue as to
  16237. what is really required.
  16238. virtual int get_size( char * s );
  16239. virtual int get_size( int idx );
  16240. In the first case, the question is whether s is the name of the car or the
  16241. model of the car or a combination of both. Whichever it is should be
  16242. demonstrated by the name, as
  16243. virtual int get_size( char * model_name);
  16244. In the second case, the parameter name is not an English word. It ought to be
  16245. replaced by a real word, such as "index." I do not think that even that is
  16246. enough, as some indication should be given as to what the index is used for.
  16247. The question is whether the index is based on passenger compartment size,
  16248. horsepower, consumer rating, or something else.
  16249. virtual int get_size( int index_of_consumer_rating);
  16250. We are still left with two overloaded functions. I would eliminate the
  16251. overloading by modifying the name of each member function. In addition, there
  16252. is no indication in the name of what size is really being gotten. So the
  16253. function might really be called
  16254. virtual int get_engine_size_by_model_name
  16255. ( char * model_name);
  16256. virtual int get_engine_size_by_consumer_rating
  16257. (int index_of_consumer_rating);
  16258. Then the code might read
  16259. get_engine_size_by_consumer_rating(SIZE);
  16260.  
  16261. Now the name SIZE looks rather funny here. That's because it is not long
  16262. enough to describe what it really is. If you are just the class provider, you
  16263. do not have any control over the names by which your classes will be invoked.
  16264. At least you have done your best to provide the means to write readable code.
  16265. One objection to requiring meaningful names is that the source code is longer.
  16266. Sometimes it will take two lines to write an expression that could have fit
  16267. onto one. My response is that statements are like sentences. Look at the last
  16268. letter or article that you wrote. See how many sentences fit onto a single
  16269. line.
  16270. Another objection is that the names can be too long and can exceed the
  16271. 31-character limitation of significance. The words in the name do not have to
  16272. all be in the dictionary. Abbreviations are okay, especially if they appear in
  16273. an industry dictionary or glossary. Proprietary abbreviations are okay, if the
  16274. company has a standard glossary. Abbreviations that are local to your programs
  16275. should be avoided, unless they are consistently applied and a glossary is
  16276. included. Every time you use your own abbreviation, you add an information
  16277. element that has to be absorbed by the reader.
  16278. I remember reading David Copperfield in my youth. It was a bit advanced for me
  16279. at the time, so I had it in one hand with a dictionary in the other. Every
  16280. time I came across an unknown word, I looked it up in the dictionary. You can
  16281. imagine how that affected following the plot. Using too many of your own
  16282. abbreviations can make the reader miss the flow of the code.
  16283. Let me point out that overloading constructors is almost unavoidable, since
  16284. there is only one name for the constructor. However, as pointed out in Tom
  16285. Cargill's C++ Programming Style, many overloaded constructors can be
  16286. eliminated by specifying default arguments in the parameter list for the
  16287. constructor.
  16288. Your other instance of bad programming shows that the designer did not
  16289. properly modularize the code in C. One has to learn to do this in C, as that
  16290. is a design technique, not a feature of the language. C++ makes it easier to
  16291. design modular code as many of the bookkeeping aspects are taken care of, but
  16292. it does not demand it.
  16293. My basic rule of thumb for structure access is that anytime you use more than
  16294. one member operator, you should examine how to redesign your code. If you use
  16295. more than two, you should start the redesign immediately. A program that
  16296. quires a reference as
  16297. a.get.line.alpha.beta
  16298. is going to be difficult to maintain.
  16299.  
  16300.  
  16301. External Declarations
  16302.  
  16303.  
  16304. Q
  16305. I had a long debugging session recently when I ran across the following
  16306. problem (see Listing 2). I've cut it down to the essentials. The code appears
  16307. all in the same source file. The value of cnt is incremented by both
  16308. functions. The correct code for the second function should have been:
  16309. static int cnt1;
  16310. ...
  16311. cnt1++;
  16312. printf("cnt is %d", cnt1);
  16313. This code was only a small portion of a large file with lots of small
  16314. functions, so it was not readily apparent that it was incorrect. However I
  16315. would have thought the compiler would have told me that cnt was declared
  16316. twice. No error message was generated, so the bad code was hard to find. What
  16317. gives?
  16318. Sam Mellon Boston, MA
  16319. A
  16320. You have been exposed to the new semantics of external declarations in ANSI C.
  16321. This is a major change from K&R C, but it is not noted as such in many texts.
  16322. Let me review how external variables are linked together, as well as the
  16323. differences in K&R and ANSI.
  16324. In this explanation of externals, K&R refers to the Appendix A in the
  16325. Kernighan & Ritchie The C Programming Language Book. Some compilers
  16326. implemented externals slightly differently.
  16327. The linker matches external references (REFs) to external definitions (DEFs).
  16328. There are REFs and DEFs for both functions and data, though they are generated
  16329. slightly differently. Take functions first. Their operation is the same in
  16330. both K&R and ANSI.
  16331. Each call to a function in your code generates a REF for that function. The
  16332. code for the function itself is the DEF for the function. For example, this
  16333. code segment:
  16334. void calling_function(void)
  16335. {
  16336. called_function();
  16337. ..
  16338. }
  16339. generates a DEF for calling_function and a REF for called_function. You can
  16340. have one and only one DEF for a function in all the files that are linked
  16341. together. Of course, you can and probably will have lots of REFs for a
  16342. function, but you don't even have to have any.
  16343. You may have gotten messages from the linker regarding duplicate definitions
  16344. or the failure to provide a definition. Depending on the vendor, it may or may
  16345. not allow you to create an executable file if those errors occur.
  16346. External REFs and DEFs for data work slightly differently. Under K&R C
  16347. (Appendix A), the same rules applied as to having one and only one DEF and
  16348. allowing zero or more REFs. A DEF was generated by a variable declaration
  16349. outside of any function, such as:
  16350. int global_i;
  16351. calling_function ()
  16352. {
  16353. global_i = 5;
  16354. ...
  16355. }
  16356. This declaration could appear in one and only one source file.
  16357. Optionally, you could initialize the variable as:
  16358. int global_i = 5;
  16359. and the initial value would be set to 5. If you did not specify an
  16360. initializer, its value would default to 0. To generate a REF, the declaration
  16361. with the keyword extern was used:
  16362. extern int global_i;
  16363. another_calling_function()
  16364. {
  16365. global_i = 7;
  16366. ...
  16367. }
  16368. It was permissible to have both a DEF and a REF in the same source file, such
  16369. as:
  16370. int global_i = 5;
  16371. extern int global_i;
  16372. This would not normally be done directly, but indirectly, as the REF or a set
  16373. of REFs would be placed into a header file, such as:
  16374. "external.h"
  16375.  
  16376. extern int global_i;
  16377. /* Other extern declarations */
  16378.  
  16379. The DEFs could be in individual files or a single file. The header file would
  16380. be included in this file, such as:
  16381. #include "external.h"
  16382. int global_i = 5;
  16383. If the declaration of the DEF does not match the declaration of the REF, the
  16384. compiler generates an error.
  16385. Although K&R was pretty straightforward on how to handle DEFs and REFs,
  16386. vendors developed their own styles, some of which were adapted from other
  16387. languages. I won't go into all the possibilities. ANSI C adopted an
  16388. amalgamation of the styles. It also added the concept of tentative DEF. The
  16389. first declaration of an external without an explicit initializer is considered
  16390. to be a tentative DEF.
  16391. All similar declarations that follow in the same file are also tentative DEFs.
  16392. For example, suppose your file contained:
  16393. int global_i; /* Tentative DEF */
  16394. ...
  16395. int global_i; /* Tentative DEF */
  16396. These are not double definitions of global_i, but simply a set of tentative
  16397. definitions of the same variable. One tentative DEF will be turned into a true
  16398. DEF by the compiler.
  16399. Now if you declare an initializer in the declaration, it becomes a true DEF.
  16400. The compiler will accept one true DEF and ignore all the other tentative DEFs.
  16401. int global_i = 5; /* True DEF */
  16402. ...
  16403. int global_i; /* Tentative DEF - refers to previous*/
  16404. The compiler will complain if two true DEFs appear in the same source file,
  16405. as:
  16406. int global_i = 5; /* True DEF */
  16407. int global_i = 6; /* Compiler error - double DEF */
  16408. Just as with K&R, you can only have a single true DEF in all the linked source
  16409. files. The linker should complain if two true DEFs appear in multiple linked
  16410. source files. However, the way many compilers/linkers work extends the concept
  16411. of tentative DEFs to multiple source files. A tentative DEF in a source file
  16412. continues to act as a tentative DEF, rather than being turned into a true DEF.
  16413. If there is no true DEF in any source file, then the linker turns a tentative
  16414. DEF into a true DEF with an initializer of zero. The linker will accept one
  16415. true DEF and ignore all the other tentative DEFs.
  16416. If you include the keyword static, the scope of the variable is only within
  16417. the source file. However the rules for tentative DEFs still apply:
  16418. static int cnt; /* Tentative DEF */
  16419. static int cnt; /* Tentative DEF */
  16420. Both tentative DEF's refer to the same variable. This is why your code had a
  16421. problem. However, it is acceptable ANSI C, so the compiler did not complain.
  16422. If you used initializations, you should get an error, such as:
  16423. static int cnt = 0; /* True DEF */
  16424. static int cnt = 0; /* 2nd True DEF - compiler
  16425. error */
  16426. If you had simply initialized one, the other would have been treated as a
  16427. tentative DEF for the same variable:
  16428. static int cnt = 0; /* True DEF */
  16429. static int cnt; /* Tentative DEF */
  16430. To complete the external puzzle, the keyword extern has been altered from K&R.
  16431. It now can be used with an initializer, as:
  16432. extern int global_i = 5; /* True DEF */
  16433. extern int global_i; /* REF */
  16434. If used without an initializer, it still means REF.
  16435. I should note that the concept of tentative DEF's does not exist in C++. A set
  16436. of declarations as:
  16437. int global_i;
  16438. int global_i;
  16439. is a compiler error due to the double definition.
  16440. I've covered my guidelines for using global variables long ago. It can be
  16441. summed up in a word--Don't. However, if you must, I suggested the style that
  16442. follows that as shown for K&R above. Use extern declarations (REF's) in a
  16443. header file that gets included in every source file. Then in a single file
  16444. initialize all the externals. For static externals, place all declarations
  16445. together near the top of the source file.
  16446. P.J. Plauger long ago pioneered a similar style with Whitesmith's C, using the
  16447. new ANSI standard for externals. Initialized declarations (extern int i = 1;)
  16448. in one file are marked true DEF's. Uninitialized declarations (extern int i;)
  16449. in a header file were used as REF's.
  16450.  
  16451.  
  16452. Social Security Feedback
  16453.  
  16454.  
  16455. I received a note from Bruce Bogert regarding the conventions on Social
  16456. Security numbers. The first three digits are assigned on a state by state
  16457. basis. For example 212 through 220 are for Maryland. The next two digits may
  16458. be used for individual divisions within a state and the last four digits are
  16459. sequentially assigned. If you do need to make up a Social Security number for
  16460. some outfit that requires it but is not legally obligated to have it, you
  16461. might want to know that numbers beginning with 900 to 999 are not used for the
  16462. most part. (KP)
  16463.  
  16464. Listing 1 A C++ base class with overloaded functions defined
  16465. class Car
  16466. {
  16467. public:
  16468. virtual int get_size( char * s );
  16469. virtual int get_size( int idx );
  16470. }
  16471.  
  16472. class SportsCar :Car
  16473. {
  16474. public:
  16475. virtual int get_size( char * s );
  16476. virtual int get_size( int idx );
  16477.  
  16478. }
  16479.  
  16480. SportsCar sportsCar;
  16481. main()
  16482. {
  16483. sportsCar.get_size( SIZE );
  16484. }
  16485. /* End of File */
  16486.  
  16487.  
  16488. Listing 2 The value of cnt is incremented by both functions.
  16489. static int cnt;
  16490. void test()
  16491. {
  16492. cnt++;
  16493. printf("Cnt is %d\n", cnt);
  16494. }
  16495.  
  16496. static int cnt;
  16497.  
  16498. void test1()
  16499. {
  16500. cnt++;
  16501. printf("cnt is %d", cnt);
  16502. }
  16503.  
  16504. /* End of File */
  16505.  
  16506.  
  16507.  
  16508.  
  16509.  
  16510.  
  16511.  
  16512.  
  16513.  
  16514.  
  16515.  
  16516.  
  16517.  
  16518.  
  16519.  
  16520.  
  16521.  
  16522.  
  16523.  
  16524.  
  16525.  
  16526.  
  16527.  
  16528.  
  16529.  
  16530.  
  16531.  
  16532.  
  16533.  
  16534.  
  16535.  
  16536.  
  16537.  
  16538.  
  16539.  
  16540.  
  16541. Code Capsules
  16542.  
  16543.  
  16544. A C++ Date Class, Part 1
  16545.  
  16546.  
  16547.  
  16548.  
  16549. Chuck Allison
  16550.  
  16551.  
  16552. Chuck Allison is a software architect for the Family History Department of the
  16553. Church of Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake
  16554. City. He has a B.S. and M.S. in mathematics, has been programming since 1975,
  16555. and has been teaching and developing in C since 1984. His current interest is
  16556. object-oriented technology and education. He is a member of X3J16, the ANSI C
  16557. ++ Standards Committee. Chuck can be reached on the Internet at
  16558. allison@decus.org, or at (801)240-4510.
  16559.  
  16560.  
  16561. Last month's column introduced a function, date_interval, that calculates the
  16562. number of years, months, and days between two arbitrary dates. This month's
  16563. column presents a C++ solution to the same problem. The essence of this
  16564. approach is to create a new data type, which behaves much the same as built-in
  16565. types do. In other words, you shift from a function-based approach ("How do I
  16566. want to do things?") to an object-based one ("What are the elements, the
  16567. objects, of my problem?"). Using C++ effectively requires a different way of
  16568. thinking about problem solving. To make that shift, it helps to know why C++
  16569. exists in the first place.
  16570.  
  16571.  
  16572. A Tale of Two Languages
  16573.  
  16574.  
  16575. C++ had its beginnings at AT&T in the early 1980's with Bjarne Stroustrup's "C
  16576. with Classes". He was seeking a way to speed up simulations written in
  16577. Simula-67. "Class" is the Simula term for a user-defined type, and being able
  16578. to define objects that mirror reality is a key to good simulations. What
  16579. better way to get fast simulations than to add classes to C, the fastest
  16580. procedural language?
  16581. Choosing C not only provided an efficient vehicle for classes, but a portable
  16582. one as well. Although other languages supported data abstraction through
  16583. classes long before C++, it is now the most widespread. Almost every major
  16584. platform that has a C compiler also supports C++. The last I heard, the C++
  16585. user base was doubling every seven months.
  16586. A first look at C++ can be overwhelming. If you're coming from C, you need to
  16587. add the following (and then some) to your vocabulary:abstract class, access
  16588. specifier, base class, catch clause, class, class scope, constructor, copy
  16589. constructor, default argument, default constructor, delete operator, derived
  16590. class, destructor, exception, exception handler, friend, inheritance, inline
  16591. function, manipulator, member function, multiple inheritance, nested class,
  16592. new handler, new operator, overloading, pointer to member, polymorphism,
  16593. private, protected, public, pure virtual function, reference, static member,
  16594. stream, template, this pointer, try block, type-safe linkage, virtual base
  16595. class, virtual function.
  16596. The good news is that C++ is a powerful, efficient, object-oriented language
  16597. able to handle complex applications. The bad news is that the language itself
  16598. must therefore be somewhat complex, and is more difficult to master than C.
  16599. And C itself is part of the problem. C++ is a hybrid, a blending of
  16600. object-oriented features with a popular systems programming language. It is
  16601. impossible to introduce such a rich set of new features without the host
  16602. language having to bend a little. Yet compatibility with C is a major goal of
  16603. the design of C++. As Bjarne stated in his keynote address to the ANSI C++
  16604. committee, C++ is an "engineering compromise," and must be kept "as close as
  16605. possible to C, but no closer." How close is still being determined.
  16606. But there is more good news.
  16607.  
  16608.  
  16609. An Incremental Journey
  16610.  
  16611.  
  16612. You can use C++ effectively without having to master all of it. In fact,
  16613. object-oriented technology promises that if vendors do their job (providing
  16614. well-designed class libraries, crafted for reuse and extensibility), then your
  16615. job of building applications will be easier. Current products, such as
  16616. Borland's Application Frameworks, are proving this to be true in many cases.
  16617. If you feel you must master the language, you can do it in steps, and still be
  16618. productive on the way. Three plateaus have emerged:
  16619. 1. A Better C
  16620. 2. Data Abstraction
  16621. 3. Object-oriented programming
  16622. You can use C++ as a better C because it is safer and more expressive than C.
  16623. Features on this plateau include type-safe linkage, mandatory function
  16624. prototypes, inline functions, the const qualifier (yes, ANSI C borrowed it
  16625. from C++), function overloading, default arguments, references, and direct
  16626. language support for dynamic memory management. You will also need to be aware
  16627. of the incompatibilities that exist between the two languages. There is a
  16628. robust subset common to both which Plum and Saks call "Type-safe C" (see C++
  16629. Programming Guidelines, Plum and Saks, Plum-Hall, 1992).
  16630. As I will illustrate in this article and the next, C++ supports data
  16631. abstraction--user-defined types that behave essentially as built-in types. The
  16632. data abstraction mechanisms are classes, access specifiers, constructors and
  16633. destructors, operator over-loading, templates, and exceptions.
  16634. Object-oriented programming takes data abstraction a step further by making
  16635. the relationships between classes explicit. The two key concepts are
  16636. inheritance (defining a new class by stating how it is the same and how it is
  16637. different from another, reusing the sameness) and polymorphism (providing a
  16638. single interface to a family of related operations, transparently resolved at
  16639. runtime). C+ + supports inheritance and polymorphism through class derivation
  16640. and virtual functions, respectively.
  16641.  
  16642.  
  16643. Classes
  16644.  
  16645.  
  16646. A class is just an extended struct. In addition to data members, you define
  16647. member functions that act upon objects of the class. The definition of the
  16648. Date class is in the file date.h in Listing 1. It differs from last month's C
  16649. version because interval is a member function instead of a global function.
  16650. The implementation of Date::interval() is in Listing 2. The double colon is
  16651. called the scope resolution operator. It tells the compiler that interval is a
  16652. member of the Date class. The ampersand in the prototype for Date::interval()
  16653. indicates that its parameter is passed by reference. (See the sidebar on
  16654. references.) The program in Listing 3 shows how to use the Date class. You
  16655. must use structure member syntax to call Date:: interval():
  16656. result = d1.interval (d2);
  16657. The structure tag Date serves as a type specifier, just like built-in types do
  16658. (i.e., you can define a Date object without using the struct keyword). There
  16659. is never a need to define
  16660. typedef struct Date Date;
  16661. In fact, the concept of class is so fundamental that C++ has merged the
  16662. separate name space for structure tags that exists in C with that of ordinary
  16663. identifiers.
  16664. Note that I have defined isleap as an inline function in Listing 2 (it was a
  16665. macro in the C version). Inline functions get expanded "in-line" like macros
  16666. do, but also perform all the scope and type checking that normal functions do.
  16667. Unless you need to use the stringizing or token-pasting operations of the
  16668. preprocessor, you don't need function-like macros in C++.
  16669. Now consider the statement
  16670. years = d2.year - year;
  16671. in Listing 2. What object does year refer to? In the C version, this statement
  16672. appeared as
  16673. years = d2.year - d1.year;
  16674. Since a member function is always called in association with an object (e.g.,
  16675. d1. interval (d2)), then un-prefixed occurrences of data members naturally
  16676. refer those of the associated object (in this case, year refers to d1.year).
  16677. The this keyword represents a pointer to the underlying object, so I could
  16678. make the more explicit statement:
  16679. years = d2.year - this->year
  16680. but this is rarely done.
  16681. In Listing 4 I have added the following statements to the class definition:
  16682.  
  16683. Date();
  16684. Date(int,int,int);
  16685. These are special member functions called constructors. Constructors allow you
  16686. to specify how to initialize an object when it is created. The first, called
  16687. the default constructor (because it takes no arguments), is used when you
  16688. define a date object without any initializers:
  16689. Date d;
  16690. The following statement invokes the second:
  16691. Date d(10,1,51);
  16692. When the implementation of member functions is trivial, you can make them
  16693. inline by moving the implementation inside the class definition itself. (See
  16694. Listing 7--don't forget to mentally remove them from Listing 5.) The test
  16695. program in Listing 6 puts off constructing the objects d1, d2, and result
  16696. until they are needed. (Object definitions can appear anywhere a statement can
  16697. in C++.)
  16698. I have almost illustrated the key feature of data abstraction, namely
  16699. encapsulation. Encapsulation occurs when a userdefined type has a well-defined
  16700. boundary between its internal representation and its public interface. To
  16701. truly say that I have created a new type that acts like a built-in type, I
  16702. must disallow any inadvertent access to its internal representation. For
  16703. example, at this point, the user could execute a statement such as
  16704. d1.month = 20;
  16705. A well-behaved object controls the access to its data members. In a practical
  16706. date class, I want to allow the user to query the month, day, and year, but
  16707. not to set them directly, so I make them private, and provide accessor
  16708. functions to retrieve their values (see Listing 8). Since having private
  16709. members is the more common situation, I usually replace the struct keyword
  16710. with the keyword class, whose members are private by default (see Listing 9).
  16711. Accessor functions like get_month don't alter the private part of a Date
  16712. object, so I declare them as const member functions. (Date::interval() is also
  16713. const--don't forget to add const to its definition in the implementation file
  16714. date3.cpp also.) I must now replace references to data members with calls to
  16715. the accessor functions in tdate3.cpp (see Listing 10).
  16716. We are only about half way to a complete C++-style approach to a date class.
  16717. Next month we will incorporate stream I/O, static members, and operator
  16718. overloading.
  16719. References in C++
  16720. A reference in C++ is an alias for another object. It can appear wherever the
  16721. object it refers to can. The following program uses the reference iref as a
  16722. substitute for i:
  16723. /* ref1.c: Illustrate references */
  16724. #include <stdio.h>
  16725.  
  16726. main()
  16727. {
  16728. int i = 10;
  16729. int &iref = i; // An alias for i
  16730.  
  16731. printf("%d\n",iref);
  16732. iref = 20;
  16733. printf("%d\n",i);
  16734. return 0;
  16735. }
  16736.  
  16737. /* Output:
  16738. 10
  16739. 20
  16740. */
  16741. You can think of a reference as a "smart" pointer, since it refers to another
  16742. object without requiring explicit addressing and dereferencing like pointers
  16743. do:
  16744. /* ptr.c: Do the same thing with pointers */
  16745. #include <stdio.h>
  16746.  
  16747. main()
  16748. {
  16749. int i = 10;
  16750. int *iref = &i;
  16751.  
  16752. printf("%d\n" ,*iref);
  16753. *iref = 20;
  16754. printf("%d\n",i);
  16755. return 0;
  16756. }
  16757. The major differences between a pointer and a reference are:
  16758. You must initialize a reference with the object it refers to. A declaration
  16759. such as
  16760. int &iref;
  16761. is meaningless (except as a function parameter). Once initialized, you can't
  16762. modify a reference to refer to a different object. Since a reference always
  16763. refers to something, you don't need to check for NULL as you do with pointers.
  16764. References neither require nor allow the use of the & or * operators. All
  16765. addressing and dereferencing are automatic. You can think of a reference as a
  16766. const pointer that is derefenced each time it is used.
  16767. Like pointers, however, references can be function return values. Since a
  16768. reference is by definition an lvalue, this allows the unusual practice of
  16769. placing a function call on the left-hand side of an assignment:
  16770. /* ref2.cpp: Returning a reference */
  16771. #include <stdio.h>
  16772.  
  16773. int & current(); // Returns a reference
  16774.  
  16775. int a[4] = {0,1,2,3};
  16776. int index = 0;
  16777.  
  16778.  
  16779. main()
  16780. {
  16781. current() = 10;
  16782. index = 3;
  16783. current() = 20;
  16784. for (int i = 0; i < 4; ++i)
  16785. printf("%d ",a[i]);
  16786. putchar('\n');
  16787. return 0;
  16788. }
  16789.  
  16790. int & current()
  16791. {
  16792. return a[index];
  16793. }
  16794.  
  16795. /* Output:
  16796. 10 1 2 20
  16797. */
  16798. Another use for references is to implement pass-by-reference semantics,
  16799. meaning that changes to function parameters persist in the calling procedure
  16800. after the called function returns. You can do this with pointers, but
  16801. references are cleaner:
  16802. /* ref3.cpp: Swap via references */
  16803. #include <stdio.h>
  16804.  
  16805. void swap(int &, int &);
  16806.  
  16807. main()
  16808. {
  16809. int i = 1, j = 2;
  16810.  
  16811. swap(i,j);
  16812. printf("i == %d, j == %d\n",i,j);
  16813. return 0;
  16814. }
  16815.  
  16816. void swap(int &x, int &y)
  16817. {
  16818. int temp = x;
  16819. x = y;
  16820. y = temp;
  16821. }
  16822.  
  16823. /* Output:
  16824. i==2, j == 1
  16825. */
  16826. Even if you aren't going to modify a function parameter, it is a good idea to
  16827. pass large objects by reference for efficiency. For example, if the data type
  16828. X is large:
  16829. struct X
  16830. {
  16831. // lotsa stuff
  16832. };
  16833. then a function f that takes a parameter of type X, but doesn't modify it,
  16834. should have a prototype similar to
  16835. void f(const X&);
  16836. For a more complete treatment of references, see Dan Saks' column in the
  16837. September 1991 issue ("Reference Types", CUJ Vol. 9, No. 9).
  16838.  
  16839. Listing 1 Member definitions for the Date class
  16840. // date.h: A simple date class
  16841.  
  16842. struct Date
  16843. {
  16844. int month;
  16845.  
  16846. int day;
  16847. int year;
  16848.  
  16849. Date * interval(const Date&);
  16850. };
  16851.  
  16852. /* End of File */
  16853.  
  16854.  
  16855. LISTING 2 Implementation of Date::interval()
  16856. //date.cpp: Implement the Date class
  16857.  
  16858. #include "date.h"
  16859.  
  16860. inline int isleap(int y)
  16861. {return y%4 == 0 && y%100 != 0 y%400 == 0;}
  16862.  
  16863. static int Dtab[2][13] =
  16864. {
  16865. {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  16866. {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  16867. };
  16868.  
  16869. Date * Date::interval(const Date& d2)
  16870. {
  16871. static Date result;
  16872. int months, days, years, prev_month;
  16873.  
  16874. // Compute the interval - assume d1 precedes d2
  16875. years = d2.year - year;
  16876. months = d2.month - month;
  16877. days = d2.day - day;
  16878.  
  16879. // Do obvious corrections (days before months!)
  16880. //
  16881. // This is a loop in case the previous month is
  16882. // February, and days < -28.
  16883. prev_month = d2.month - 1;
  16884. while (days < 0)
  16885. {
  16886. // Borrow from the previous month
  16887. if (prev_month == 0)
  16888. prev_month = 12;
  16889. --months;
  16890. days += Dtab[isleap(d2.year)][prev_month--];
  16891. }
  16892.  
  16893. if {months < 0)
  16894. {
  16895. // Borrow from the previous year
  16896. --years;
  16897. months += 12;
  16898. }
  16899.  
  16900. /* Prepare output */
  16901. result.month = months;
  16902. result.day = days;
  16903. result.year = years;
  16904. return &result;
  16905.  
  16906. }
  16907.  
  16908. /* End of File */
  16909.  
  16910.  
  16911. Listing 3 A program that shows how to use the Date class
  16912. // tdate.cpp: Test the Date class
  16913.  
  16914. #include <stdio.h>
  16915. #include <stdlib.h>
  16916. #include "date.h"
  16917.  
  16918. main()
  16919. {
  16920. Date d1, d2, *result;
  16921. int nargs;
  16922.  
  16923. // Read in two dates - assume 1st precedes 2nd
  16924. fputs("Enter a date, MM/DD/YY> ",stderr);
  16925. nargs = scanf("%d/%d/%d%*c", &d1.month,
  16926. &d1.day, &d1.year);
  16927. if (nargs != 3)
  16928. return EXIT_FAILURE;
  16929.  
  16930. fputs("Enter a later date, MM/DD/YY> ",stderr);
  16931. nargs = scanf("%d/%d/%d%*c", &d2.month,
  16932. &d2.day, &d2.year);
  16933. if (nargs != 3)
  16934. return EXIT_FAILURE;
  16935.  
  16936. // Compute interval in years, months, and days
  16937. result = d1.interval(d2);
  16938. printf("years: %d, months: %d, days: %d\n",
  16939. result->year, result->month, result->day);
  16940. return EXIT_SUCCESS;
  16941. }
  16942.  
  16943. /* Sample Execution:
  16944. Enter a date, MM/DD/YY> 10/1/51
  16945. Enter a later date, MM/DD/YY> 11/14/92
  16946. years: 41, months: 1, days: 13
  16947. */
  16948.  
  16949. /* End of File */
  16950.  
  16951.  
  16952. Listing 4 Definitions for a variation of the Date class shown in Listing 5
  16953. // date2.h
  16954.  
  16955. struct Date
  16956. {
  16957. int month;
  16958. int day;
  16959. int year;
  16960.  
  16961. // Constructors
  16962. Date();
  16963. Date(int, int, int);
  16964.  
  16965.  
  16966. Date * interval (const Date&);
  16967. };
  16968.  
  16969. /* End of File */
  16970.  
  16971.  
  16972. Listing 5 A variation on the Date class
  16973. // date2.cpp
  16974.  
  16975. #include "date2.h"
  16976.  
  16977. inline int isleap(int y)
  16978. {return y%4 == O && y%100 != 0 y%400 == 0;}
  16979.  
  16980. static int Dtab [2][13] =
  16981. {
  16982. {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  16983. {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  16984. };
  16985.  
  16986. Date * Date::interval (const Date& d2)
  16987. {
  16988. (same as in Listing 2)
  16989. }
  16990.  
  16991. Date::Date()
  16992. {
  16993. month = day = year = 0;
  16994. }
  16995.  
  16996. Date::Date(int m, int d, int y)
  16997. {
  16998. month = m;
  16999. day = d;
  17000. year = y;
  17001. }
  17002.  
  17003. /* End of File */
  17004.  
  17005.  
  17006. Listing 6 A program for testing the Date class
  17007. // tdate2.cpp
  17008.  
  17009. #include <stdio.h>
  17010. #include <stdlib.h>
  17011. #include "date2.h"
  17012.  
  17013. main()
  17014. {
  17015. int m, d, y, nargs;
  17016.  
  17017. // Read in two dates - assume 1st precedes 2nd
  17018. fputs("Enter a date, MM/DD/YY> ",stderr);
  17019. nargs = scanf("%d/%d/%d%*c", &m,&d,&y);
  17020. if (nargs != 3)
  17021. return EXIT_FAILURE;
  17022. Date d1(m,d,y);
  17023.  
  17024. fputs("Enter a later date, MM/DD/YY> ",stderr);
  17025.  
  17026. nargs = scanf("%d/%d/%d%*c", &m,&d,&y);
  17027. if (nargs != 3)
  17028. return EXIT_FAILURE;
  17029. Date d2(m,d,y);
  17030.  
  17031. // Compute interval in years, months, and days
  17032. Date *result = d1.interval(d2);
  17033. printf("years: %d, months: %d, days: %d\n",
  17034. result->year, result->month, result->day);
  17035. return EXIT_SUCCESS;
  17036. }
  17037. /* End of File */
  17038.  
  17039.  
  17040. Listing 7 Moving implementation of member functions into the class definition
  17041. // date2.h
  17042.  
  17043. struct Date
  17044. {
  17045. int month;
  17046. int day;
  17047. int year;
  17048.  
  17049. // Constructors
  17050. Date()
  17051. {month = day = year = 0;}
  17052. Date(int m, int d, int y)
  17053. {month = m; day = d; year = y;}
  17054.  
  17055. Date * interval(const Date&);
  17056. };
  17057.  
  17058. /* End of File */
  17059.  
  17060.  
  17061. Listing 8 Definitions for a class that uses accessor functions to retrieve
  17062. values
  17063. // date3.h
  17064.  
  17065. struct Date
  17066. {
  17067. private:
  17068. int month;
  17069. int day;
  17070. int year;
  17071.  
  17072. public:
  17073. // Constructors
  17074. Date()
  17075. {month = day = year = 0;}
  17076. Date(int m, int d, int y)
  17077. {month = m; day = d; year = y;}
  17078.  
  17079. // Accessor Functions
  17080. int get_month() const
  17081. {return month;}
  17082. int get_day() const
  17083. {return day;}
  17084. int get_year() const
  17085. {return year;}
  17086.  
  17087.  
  17088. Date * interval(const Date&) const;
  17089. };
  17090.  
  17091. /* End of File */
  17092.  
  17093.  
  17094. Listing 9 Definitions for a class that replaces struct with class
  17095. // date3.h
  17096.  
  17097. class Date
  17098. {
  17099. int month;
  17100. int day;
  17101. int year;
  17102.  
  17103. public:
  17104. // Constructors
  17105. Date()
  17106. {month = day = year = 0;}
  17107. Date(int m, int d, int y)
  17108. {month = m; day = d; year = y;}
  17109.  
  17110. // Accessor Functions
  17111. int get_month() const
  17112. {return month;}
  17113. int get_day() const
  17114. {return day;}
  17115. int get_year() const
  17116. {return year;}
  17117.  
  17118. Date * interval(const Date&) const;
  17119. };
  17120.  
  17121. /* End of File */
  17122.  
  17123.  
  17124. Listing 10 A test for version 3 of the Date class
  17125. // tdate3. cpp
  17126. #include <stdio.h>
  17127. #include <stdlib.h>
  17128. #include "date3.h"
  17129. main()
  17130. {
  17131. int m, d, y, nargs;
  17132.  
  17133. // Read in two dates - assume 1st precedes 2nd
  17134. fputs("Enter a date, MM/DD/YY> ",stderr);
  17135. nargs = scanf("%d/%d/%d%*c", &m,&d,&y);
  17136. if (nargs != 3)
  17137. return EXIT_FAILURE;
  17138. Date d1(m,d,y);
  17139.  
  17140. fputs("Enter a later date, MM/DD/YY> ",stderr);
  17141. nargs = scanf("%d/%d/%d%*c", &m,&d,&y);
  17142. if (nargs != 3)
  17143. return EXIT_FAILURE;
  17144. Date d2(m,d,y);
  17145.  
  17146.  
  17147. // Compute interval in years, months, and days
  17148. Date *result = d1.interval(d2);
  17149. printf("years: %d, months: %d, days: %d\n",
  17150. result->get_year (),
  17151. result->get_month (),
  17152. result->get_day ());
  17153. return EXIT_SUCCESS;
  17154. }
  17155. /* End of File */
  17156.  
  17157.  
  17158.  
  17159.  
  17160.  
  17161.  
  17162.  
  17163.  
  17164.  
  17165.  
  17166.  
  17167.  
  17168.  
  17169.  
  17170.  
  17171.  
  17172.  
  17173.  
  17174.  
  17175.  
  17176.  
  17177.  
  17178.  
  17179.  
  17180.  
  17181.  
  17182.  
  17183.  
  17184.  
  17185.  
  17186.  
  17187.  
  17188.  
  17189.  
  17190.  
  17191.  
  17192.  
  17193.  
  17194.  
  17195.  
  17196.  
  17197.  
  17198.  
  17199.  
  17200.  
  17201.  
  17202.  
  17203.  
  17204.  
  17205.  
  17206.  
  17207.  
  17208.  
  17209.  
  17210. C++ Components and Algorithms
  17211.  
  17212.  
  17213. Steve Halladay
  17214.  
  17215.  
  17216. Steve Halladay owns Creative C Corp. in Louisville, CO. He has over 10 years
  17217. experience developing products like CSIM and Multi-C in C and C++. Steve
  17218. received his MS in Computer Science in 1982 from Brigham Young University. You
  17219. can reach Steve at Creative C Corp. (303) 673-6683.
  17220.  
  17221.  
  17222. C++ Components and Algorithms is not just another text on how to use C++. This
  17223. book focuses on a few useful software components and shows how to implement
  17224. them in C++. In addition, Scott Ladd provides the kind of insight associated
  17225. with experienced software developers. Ladd expects the reader to have a
  17226. working knowledge of C++.
  17227.  
  17228.  
  17229. Organization and Contents
  17230.  
  17231.  
  17232. C++ Components and Algorithms includes C++ source code that shows the
  17233. implementation of strings, arrays, sorting algorithms, statistical functions,
  17234. persistent objects, hash tables, binary trees, and B-trees. The 779 pages of
  17235. the book are divided into four major sections entitled "Groundwork," "Arrays,"
  17236. "Indexing, Hashing and Filing," and "Appendices."
  17237. In his first section, entitled "Groundwork", Ladd describes the philosophy
  17238. that guides the development of the objects he presents. As he points out, it
  17239. is important to understand the developer's general philosophy to fully
  17240. understand the software. Ladd briefly describes how object-oriented software
  17241. differs from traditional software and defines some of its associated
  17242. terminology. Ladd also takes time to describe some pitfalls associated with
  17243. "trendy" C++ such as the additional opportunity the language gives you to make
  17244. mistakes.
  17245. In this first section Ladd also introduces some basic types such as booleans,
  17246. error reporters, random-number generators, and strings. Ladd mentions that the
  17247. string class he presents in the third chapter is very similar to string
  17248. classes he has presented in previous books, but notes that he has included the
  17249. string class in this book for completeness. This is certainly useful for those
  17250. who do not own his previous books.
  17251. The second section of the book, entitled "Arrays," introduces a couple
  17252. different types of arrays. The inherent implementation of arrays in C and C++
  17253. gives programmers enough rope with which to hang themselves. Ladd shows how to
  17254. build an array class that helps programmers avoid common array pitfalls by
  17255. supplying enhancements like index bounds checking. The array types he
  17256. describes include integer and floating-point arrays. Ladd avoids the use of
  17257. templates for his implementation of arrays because many current compilers have
  17258. template implementation deficiencies. But since Ladd gives both integer and
  17259. floating-point examples of arrays, it is easy to see how to create additional
  17260. types of arrays using his base classes.
  17261. In the section on arrays, Ladd also takes advantage of array constructs to
  17262. discuss some related topics. For example, Ladd presents one chapter on sorting
  17263. in which he describes the quicksoft algorithm and shows how to implement it in
  17264. C++. He also has a chapter about statistics in which he discusses some basic
  17265. statistical concepts like mean, variance, and correlation. Ladd shows how both
  17266. sorting and statistics relate to his array classes.
  17267. The third section of the book, entitled "Indexing, Hashing and Filing,"
  17268. discusses some basic database concepts. Ladd starts the third section by
  17269. talking about persistent objects. He shows how to store and retrieve objects
  17270. with files. The third section also discusses hash tables and shows how to use
  17271. them as an indexing mechanism for a simple database. Ladd ends the third
  17272. section of the book by discussing tree structures as database indexing
  17273. mechanisms. In the chapters on tree structures, Ladd shows a binary tree and a
  17274. B-tree implementation. He also explains the strengths and weaknesses of each
  17275. structure. Ladd's implementation of the B-tree is thorough enough that he
  17276. includes code to perform not only key insertion and searching, but also key
  17277. deletion.
  17278. The fourth section of the book is an appendix that includes the complete
  17279. source code for all the classes presented in the earlier parts of the book.
  17280. The appendix is 279 pages of listings. The book comes with an accompanying
  17281. disk that also contains the complete source listings as contained in the
  17282. appendix.
  17283.  
  17284.  
  17285. Commentary
  17286.  
  17287.  
  17288. C++ Components and Algorithms is Ladd's third book. His comfortable writing
  17289. style attests to his experience as a writer. The explanations and examples are
  17290. clear and concise. Most of the book is written in the first person as though
  17291. you were looking over his shoulder as he guides you through his code.
  17292. This book is not for the passive reader. Ladd includes lots of source code.
  17293. The average page probably has more lines of source code than accompanying
  17294. textual explanation. However, the code is commented where necessary and easy
  17295. to understand. If you like to see examples of good clean code, this book is
  17296. full of them.
  17297. Ladd's source code also is reasonably compiler and system independent. There
  17298. is nothing more frustrating than getting a book of programming examples only
  17299. to find that they will not work on your system. The only system specific code
  17300. handles the different PC compilers' file opening command modes.
  17301. You might think that a book with so much source code is bound to have many
  17302. errors. On the contrary, Ladd seems to have taken meticulous care to make sure
  17303. his code compiles under at least Borland's and Microsoft's compilers. The very
  17304. few errors I noticed in the book were not in the source code at all.
  17305. The most interesting part of the book to me was Ladd's use of sound
  17306. development techniques throughout the book. Ladd's class designs are elegant
  17307. and simple. He avoids the convoluted inheritance hierarchies that are often
  17308. prevalent in many current C++ programming examples. He also uses a fundamental
  17309. programming style that avoids the many confusing extensions that C++ offers.
  17310. But Ladd's avoidance of the exotic features of C++ does not prevent him from
  17311. showing how to use the major valuable aspects of the language. It was
  17312. refreshing to see code that did not try to impress you with its extreme use of
  17313. the language.
  17314.  
  17315.  
  17316. Conclusion
  17317.  
  17318.  
  17319. C++ Components and Algorithms fills a niche for the intermediate software
  17320. developer. The book focuses on useful, sound implementations of data
  17321. structures and algorithms in C++. Ladd's down-to-earth approach and clear
  17322. explanations make both his descriptions and examples useful to students of C++
  17323. and software engineering.
  17324. Title: C++ Components and Algorithms
  17325. Author: Scott Robert Ladd
  17326. Publisher: M&T Books, 411 Borel Avenue, San Mateo, CA 94402
  17327. Price: $39.95 (disk included)
  17328. ISBN: 1-55851-227-6
  17329.  
  17330.  
  17331.  
  17332.  
  17333.  
  17334.  
  17335.  
  17336.  
  17337.  
  17338.  
  17339.  
  17340.  
  17341.  
  17342.  
  17343.  
  17344.  
  17345.  
  17346.  
  17347. Editor's Forum
  17348. Here's an update on the name game. Back in the May 1990 Editor's Forum, I
  17349. discussed an offhand suggestion I'd heard. Someone felt we should change the
  17350. name of this magazine to The C++ Users Journal. After all, C++ is where all
  17351. the action is these days, nicht wahr? Why not change with the times? I
  17352. declined to make such a recommendation to Robert Ward at the time. I felt then
  17353. that C still had a lot of life left in it. And legions of programmers still
  17354. needed to read about style, techniques, and esoterica peculiar to the C
  17355. language proper. We were running articles on C++ (and other dialects) on a
  17356. regular basis. That was enough.
  17357. I predicted then that our C++ coverage would increase if the need arose. And
  17358. indeed the need has arisen. Lots of programmers are finding reasons to try
  17359. C++. Some are grappling with new coding styles. Some with new techniques. Many
  17360. are finding lots more esoterica to master. You'll now find that a typical
  17361. issue of The C Users Journal devotes substantial editorial real estate to
  17362. articles on C++. We still differ from the new "object-oriented" publications
  17363. in one important regard. Few of those articles we now include are preachy.
  17364. Rather, they are written by C/C++ programmers to help others use this new
  17365. dialect to advantage. That's been our strength in the C world for years. It
  17366. will continue to be our focus in the emerging era of C++. And we will continue
  17367. to cover the huge, and growing, C market for the foreseeable future.
  17368. So should we change our name? I cite two other organizations who kept their
  17369. old names even as they changed with the times. One is the Association for
  17370. Computing Machinery (ACM). Many members felt that the focus of that
  17371. organization had shifted from mere machinery to vastly more important topics.
  17372. They called for a referendum to keep the initials ACM, but have them in future
  17373. stand for Association for CoMputing. It was defeated. (I voted against the
  17374. change on the grounds that computer science would be just an esoteric branch
  17375. of mathematics were there no fast, reliable, and inexpensive computing
  17376. machines.)
  17377. The other group is the League of Women Voters. When they began allowing men to
  17378. join, someone suggested that they should henceforth be the League of Voters
  17379. (or the League of People Voters). My favorite argument against the change was
  17380. made by one of the leaders. She suggested that they simply explain to men that
  17381. "women" in this context was a generic term that also included men. The men
  17382. would understand. If we don't change our name, I'm sure the C++ programmers
  17383. will understand too.
  17384. P.J. Plauger
  17385. pjp@plauger.com
  17386.  
  17387.  
  17388.  
  17389.  
  17390.  
  17391.  
  17392.  
  17393.  
  17394.  
  17395.  
  17396.  
  17397.  
  17398.  
  17399.  
  17400.  
  17401.  
  17402.  
  17403.  
  17404.  
  17405.  
  17406.  
  17407.  
  17408.  
  17409.  
  17410.  
  17411.  
  17412.  
  17413.  
  17414.  
  17415.  
  17416.  
  17417.  
  17418.  
  17419.  
  17420.  
  17421.  
  17422.  
  17423.  
  17424.  
  17425.  
  17426.  
  17427.  
  17428.  
  17429.  
  17430.  
  17431.  
  17432.  
  17433.  
  17434.  
  17435.  
  17436.  
  17437.  
  17438.  
  17439.  
  17440. New Products
  17441.  
  17442.  
  17443. Industry-Related News & Announcements
  17444.  
  17445.  
  17446.  
  17447.  
  17448. Micro-Processor Services Releases MASM to C Translator
  17449.  
  17450.  
  17451. Micro-Processor Services, Inc., has released MASM2C, a tool designed to
  17452. translate MASM code generated by a disassembler into C source code. Working
  17453. with a disassembler, MASM2C can be used to translate from an .EXE file to
  17454. intelligible C source. MASM2C contains a syntax analyzer, a MASM-to-tertiary
  17455. converter, and a tertiary-to-C converter. The tertiary language is used to
  17456. maintain logical equivalence between source and target languages, and is used
  17457. in the entire Micro-Processor Services family of translators. MASM2C is
  17458. compatible with V Communications' Sourcer and other disassemblers, and is
  17459. error message compatible with "Brief" (or other editors supporting Microsoft
  17460. C) permitting translation within an editor environment. MASM2C supports 8086,
  17461. 80186, 80286, 80286P, 80386, and 80386P instructions. The translation includes
  17462. MASM error bypass and C beautifier.
  17463. MASM2C is available in both a standard version ($475) and a protected-mode
  17464. version ($850). The standard version can handle files of 2000 to 3000 lines,
  17465. while the protected-mode version can handle up to 40,000 lines at 16MB.
  17466. Translation speed is 400 to 500 lines per minute. For information, contact
  17467. Micro-Processor Services, Inc., 92 Stone Hurst Lane, Dix Hills, NY 11746,
  17468. (516) 499-4461; FAX: (516) 499-4727.
  17469.  
  17470.  
  17471. Compass Point Software Introduces application::ctor
  17472.  
  17473.  
  17474. Compass Point Software, Inc., has announced their application::ctor
  17475. (pronounced "application constructor") product, a GUI application-development
  17476. tool for Microsoft Windows. application::ctor includes an object-oriented
  17477. "view Editor", a user-interface class library containing over 100 classes, and
  17478. a C++ class browser. Application::ctor can be used to build Multiple Document
  17479. Interface (MDI) windows and provide attachment and gravity allowing automatic
  17480. rearranging and resizing of controls when the window is resized.
  17481. application::ctor can also subdivide a window into regions called "hot areas."
  17482. Protoypers can use the View Editor to construct an application's
  17483. user-interface and to attach pre-defined prototyping functions from the class
  17484. library. Once the prototype's functionality and appearance are established,
  17485. developers can attach C++ code to the events in the View Editor to build a
  17486. deliverable application.
  17487. application::ctor requires Windows 3.1 and a C++ compiler. The price is $99.
  17488. Contact Compass Point Software, 332A Hungerford Drive, Rockvill, MD 20850,
  17489. (301) 738-9109.
  17490.  
  17491.  
  17492. Intel's Tunes C Compiler for Superscalar Microprocessor
  17493.  
  17494.  
  17495. Intel Corporation has introduced CTOOLS960 Release 4.0, a C compiler which
  17496. exploits the superscalar architectures of their i960R CA and i960 CF
  17497. microprocessors. Intel's announcement claimed improvements of up to 40% in
  17498. application program performance. The CTOOLS960 compiler collects information
  17499. about an application program during execution of the program to drive its
  17500. optimization. The CTOOLS960 compiler was designed to bring optimization to
  17501. both superscalar and non-superscalar, and can optimize programs written for
  17502. any i960 microprocessor, including the original Kx and the Sx series.
  17503. The CTOOLS960 package includes the compiler, the assembler, and utilities
  17504. designed for appliciation development and performance optimization on the i960
  17505. microprocessor family. CTOOLS960 Release 4.0 is priced at $2000 for MS-DOS
  17506. hosts, $3500 for HP9000 hosts, and $4300 for SUN4 or IBM RS6000. For more
  17507. information, contact the Intel Literature Center at (800) 548-4725, or write
  17508. for: Intel Literature Pack #A9P71, P.O. Box 7641, Mt. Prospect, IL 60056-7641.
  17509.  
  17510.  
  17511. Microtech Research Supports Cross Development for Intel processors on IBM
  17512. RS/6000
  17513.  
  17514.  
  17515. Microtec Research, Inc., has announced it's Intel i960 and 8086-family
  17516. microprocessor development tools for the IBM RISC System/6000. These tools
  17517. complement the Motorola 68000-family tools already provided by Microtec
  17518. Research, and were ported under an agreement with IBM. On the RS/6000, the
  17519. cross development tools include an ANSI C cross compiler, a Macro Cross
  17520. Assembler, and the XRAY Debugger. A C++ cross compiler is available for 68000
  17521. targets. The XRAY Debugger debugs optimized C code, allowing engineers to work
  17522. with production-quality code. The XRAY Debugger provides an X Window System
  17523. Motif interface. The XRAY Debugger on the RS/6000 supports instruction set
  17524. simulation and debug monitor as execution environments. With instruction set
  17525. simulation, software development can begin before the target hardware is
  17526. available, while with monitor-based debugging, developers can perform
  17527. real-time debugging while software runs on the target hardware. Microtec
  17528. Research C compilers comply with ANSI C, but also support K&R C. The C++
  17529. compilers comply with version 2.1 of the AT&T specification.
  17530. For information, contact Microtec Research Inc., 2350 Mission College
  17531. Boulevard, Santa Clara, CA 95054, (800) 950-5554; FAX: (408) 982-8266.
  17532.  
  17533.  
  17534. TauMetric Announces C++/C Debugger/Browser for OpenWindows
  17535.  
  17536.  
  17537. TauMetric Corporation has announced a Windowed Debugger/Browser (WDB) for C++
  17538. and C programs on Sun SPARCs. WDB provides an OPEN LOOK interface to a
  17539. source-level debugger which includes source, data, and class browsing
  17540. facilities. WDB runs under OpenWindows 2.0 or 3.0, and debugs programs
  17541. compiled with TauMetric's Oregon C++/C compiler.
  17542. WDB includes both a Class Hierarchy Browser and Class Member Browsers. The
  17543. Class Hierarchy Browser graphically depicts inheritance relationships between
  17544. all classes in a program. The Class Member Browsers displays the members of a
  17545. class (both data members and member functions), including inherited members,
  17546. and allows access to the source code defining a class and its members.
  17547. WDB is sold with the Oregon C++ Development System for SPARC, and is available
  17548. at a discount for current Oregon C++ users. Oregon C++ is also available for
  17549. VAX/VMS and DECstation/Ultrix. TauMetric plans calls for ports of WDB to the
  17550. other platforms along with expansion into a complete object-oriented
  17551. programming environment. For information, contact TauMetric Corporation, 8765
  17552. Fletcher Parkway, Suite 301, La Mesa, CA 91942, (61) 697-7607, or (800)
  17553. 874-8501; FAX: (619) 697-1140.
  17554.  
  17555.  
  17556. SunPro Licenses MetaWare Code Generation Technology
  17557.  
  17558.  
  17559. MetaWare Incorporated and SunPro, a Sun Microsystems, Inc. business, have
  17560. announced a licensing agreement in which MetaWare's x86 code generation
  17561. technology will be used in a new family of SunPro compilers for Intel-based
  17562. personal computers. SunPro and MetaWare, in their release, project the new
  17563. family of compilers, ProCompilers, to be the first compiler suite available
  17564. for Solaris 2.0 for x86. ProCompilers will provide source compatibility across
  17565. SPARC and Intel x86.
  17566. ProCompilers are now available in SunSoft's Solaris 2.0 for x86 early access
  17567. kit, the developers version of the new operating environment for Intel 32-bit
  17568. platforms. SunSoft has announced that the Intel version of Solaris 2.0 willbe
  17569. available in volume toward the end of 1992. Contact MetaWare (408) 429-6832 or
  17570. SunPro (415) 336-4638.
  17571.  
  17572.  
  17573. Sierra Systems Upgrades Sierra C Compiler
  17574.  
  17575.  
  17576. Sierra Systems has upgraded their C cross compiler and development system for
  17577. the Motorola 68000 family. Sierra C 3.0 was designed specifically for the
  17578. embedded systems engineer, and generates position independent, re-entrant, and
  17579. ROMable code for any memory configuration. Version 3.0 incorporates a "Hole
  17580. Compression" Optimization Utility. Sierra C's Linker can determine on the
  17581. first pass when a more compact addressing mode can be used, relays the
  17582. information to the assembler, which makes a second pass and adjusts the code
  17583. improving size (4-7% on average) and performance. Version 3.0 also includes
  17584. extended-memory support, floating-point enhancements, enhanced multiplication
  17585. by constant, improved function-call stack cleanup, and inline strcpy and
  17586. unrollable structure copy code.
  17587. Sierra C is priced at $2,000 for PCs or $3,500 for UNIX workstations. Contact
  17588. Sierra Systems, 6728 Evergreen Avenue, Oakland, CA 94611, (800) 776-4888 or
  17589. (510) 339-8200; FAX: (510) 339-3844.
  17590.  
  17591.  
  17592.  
  17593. SLR Systems Releases OPTLINK v3.0 for Windows
  17594.  
  17595.  
  17596. SLR Systems, Inc. has released OPTLINK v3.0 for Windows. OPTLINK v3.0 for
  17597. Windows adds smart-linking technology and p-code support for Microsoft's C/C++
  17598. v7.0, and supports elimination of all unreferenced functions. OPTLINK v3.0
  17599. allows up to 64K each of libraries, symbols, segments, modules, and other link
  17600. specific information. OPTLINK v3.0 for Windows replaces LINK, and supports all
  17601. compilers and assemblers that generate standard object files. OPTLINK v3.0
  17602. also supports CodeView 3.x and 4.x. Operating from MS-DOS, OPTLINK v3.0 for
  17603. Windows generates 16-bit .EXEs and .DLLs for both Windows and OS/2. OPTLINK
  17604. v3.0 Segmented accomodates MS-DOS and OS/2 hosted linking for Windows and OS/2
  17605. executable. OPTLINK v3.0 for Windows and OPTLINK v3.0 Segmented are priced at
  17606. $350 and $499, respectively. Contact SLR Systems, Inc., 1622 N. Main Street,
  17607. Butler, PA 16001, (412) 282-0864; FAX: (412) 282-7965; BBS: (412) 282-2799.
  17608.  
  17609.  
  17610. Magna Carta Moves Communications Library to Windows
  17611.  
  17612.  
  17613. Magna Carta Software has released their C Communications Toolkit/Windows, a C
  17614. developer's library for serial communications under Windows. The library
  17615. supports single or multiport communications under Windows and works with all
  17616. Hayes-compatible modems. The library supports data transmission and reception,
  17617. flow control, and a variety of file transfer protocols, including XMODEM,
  17618. YMODEM, ZMODEM, and Kermit.
  17619. C Communications Toolkit/Windows costs $299. The library supports Borland C++,
  17620. Turbo C++, Microsoft C/C++, and Microsoft Quick C for WIndows. The package
  17621. comes with full, commented source code, 600 pages of documentation, an da
  17622. 30-day money-back guarantee. For more information, contact Magna Carta
  17623. Software, P.O. Box 475594, Garland, TX 75047-5594, (214) 226-6900.
  17624.  
  17625.  
  17626. NeruoDynamX Provides Embeddable Neural Network Software
  17627.  
  17628.  
  17629. NeuroDynamX, Inc., has released DynaMind Developer neural network software for
  17630. implementing neural networks. The package bundles DynaMind neural network
  17631. training software (3.0) with several runtime options, including linkable
  17632. C-code routines and neural network hardware simulation. Networks trained with
  17633. DynaMind can be run standalone, embedded into C applications, or used in
  17634. simulations of neural network hardware. The package includes NeuroLink v2.0, a
  17635. library of C routinges for linking networks into applications. The library can
  17636. link multiple networks serially or in parallel. The package includes a
  17637. simulator for Intel's 80170NX ETANN chip.
  17638. NeuroLink compiles under Borland Turbo C 2.0, Turbo C++, or Borland C++ and
  17639. carries no run-time license fee. DynaMind Developer is priced at $495, while
  17640. DynaMind 3.0 software is available separately for $145. Contact NeuroDymanX,
  17641. Inc., P.O. Box 323, Boulder, CO 80306, (800) 747-3531; FAX: (303)442-2854.
  17642.  
  17643.  
  17644. Tom Sawyer Software Introduces Graph Layout Toolkit
  17645.  
  17646.  
  17647. Tom Sawyer Software has introduced the Graph Layout Toolkit, an automated
  17648. object positioning tool which helps application programs provide visual
  17649. representations of objects and their connections. The Graph Layout Toolkit
  17650. provides real-time graph layout services for both directed and undirected
  17651. graphs through a set of extensible class libraries written in C++. An ANSI C
  17652. API is furnished as well. The package can handle cyclic graphs, multiple
  17653. disconnected subgraphs, multigraphs, and reflexive edges. The toolkit supports
  17654. crossing minimization, graph reduction, hierarchical layout, and subgraph
  17655. position balancing.
  17656. The software is available for PC, Mac, Sun, HP 9000, RS/6000, and NeXT
  17657. platforms. Tom Sawyer is seeking to license this graph layout technology for
  17658. embedding/repackaging by product-oriented companies. Price is determined by
  17659. contract. Contact Tom Sawyer Software, 1824B Fourth Street, Berkeley, CA
  17660. 94720, (510) 848-0853; FAX: (510) 848-0854.
  17661.  
  17662.  
  17663. XVT Announces Developer Toolkit for Windows NT
  17664.  
  17665.  
  17666. XVT Software, Inc., has announced support for Windows NT with its latest
  17667. version of the XVT Portability Toolkit. The XVT Portability Toolkit allows
  17668. programmers to build a C/C++ application, then recompile to other GUIs without
  17669. rewriting code. Applications built with XVT/NT will use a 32-bit linear memory
  17670. model. With NT's pre-emptive multitasking system, XVT/NT will offer memory
  17671. protection for each application. XVT/NT supports all Portability Toolkit
  17672. Release 3.x functionality.
  17673. The Portability Toolkit for NT is priced at $1,450 on Intel 486 systems, and
  17674. at $4,400 for DEC and MIPS. XVT does not charge royalties. Contact XVT
  17675. Software Inc., 4900 Pearl East Circle, Boulder, CO 80301, (303) 443-4223.
  17676.  
  17677.  
  17678. IDE Introduces C++ Tools and Support
  17679.  
  17680.  
  17681. Interactive Development Environments, Inc., has added Object-Oriented
  17682. Structured Design/C++ (OOSD/C++) to its Software through Pictures family of
  17683. CASE tools. OOSD/C++ provides developers with a graphical design tool, a Reuse
  17684. Browser, and a Code Generator for C++. IDE is also marketing a coordinated
  17685. program (the Success Package for C++) of tools, training, consulting,
  17686. maintenance, and support that includes OOSD/C++ and is based on the premise
  17687. that in order to exploit object-oriented design, comprehensive training and
  17688. support are essential.
  17689. Contact Interactive Development Environments, Inc., 595 Market Street, 10th
  17690. Floor, San Francisco, CA 94105, (415) 543-0900; FAX: (415) 543-0145.
  17691.  
  17692.  
  17693. Vitamin C/CE Released for COHERENT
  17694.  
  17695.  
  17696. Creative Programming Consultants, Inc., and Mark Williams Company have
  17697. announced the release of Creative Programming's Vitamin C/CE, an advanced user
  17698. interface library for COHERENT. An alternative to curses, Vitamin C/CE enables
  17699. programmers to create portable, multiwindowed, terminal independent
  17700. applications with full data entry, validation, help, and menuing capabilities.
  17701. Vitamin C/CE provides source level compatibility over a variety of platforms,
  17702. including MS-DOS, SYSV, BSD, SUN/OS, HP/UX, AIX, Ultrix, and VMS.
  17703. Vitamin C/CE is available for $99 from Creative Programming Consultants, 2013
  17704. N. Broadway, Carrollton, TX 75006, (214) 245-9139; FAX: (214) 245-9717.
  17705.  
  17706.  
  17707. Symantec Announces MultiScope Debuggers for Borland and Microsoft C++
  17708.  
  17709.  
  17710. Symantec Corporation has announced version 2.0 of its MultiScope Debuggers.
  17711. Version 2.0 supports Borland C++ and Microsoft C 6.0 and C/C++ 8.0 languages
  17712. for programming Windows and MS-DOS applications. The MultiScope Debuggers
  17713. provide a Windows-hosted user interface, with a "CommandBar" which allows
  17714. quick access to frequently-used commands. MultiScope debuggers are designed
  17715. specifically for C++ code and include C++ specific features. New features
  17716. include a "point-and-shoot" collapse and expand C++ class hierarchy browser,
  17717. C++ object browsing, automatics C++ object mapping for all C++ inheritance
  17718. types, alternative C++ class information member scope display, object-oriented
  17719. breakpoints directly on object methods, direct browsing of member pointers,
  17720. complete C++ expression evaluation, name unmangling, and the ability to update
  17721. the source window to the actual function by selecting the method while
  17722. browsing. MultiScope includes a Crash Analyzer System, with a Monitor
  17723. Execution and Dump (MED) utility, which allows analysis of program crashes.
  17724. MultiScope Debuggers for Windows are priced at #379. MS-DOS products are
  17725. priced at $179. Upgrades for registered users are available. For information
  17726. contact Symantec Corporation, 10201 Torre Avenue, Cupertino, CA 95014-2132,
  17727. (800) 441-7234 or (408) 252-3570; FAX: (408) 253-4092 or 252-4694.
  17728.  
  17729.  
  17730. Blaise Computing Announces C ASYNC MANAGER Version 4.0
  17731.  
  17732.  
  17733.  
  17734. Blaise Computing Inc., has announced version 4.0 of their C ASYNCH MANAGER,
  17735. for applications in Microsoft and Borland C/C++, Quick C, and Turbo C and C++.
  17736. Version 4.0 supports XMODEM, YMODEM, ZMODEM, and Kermit file transfer
  17737. protocols; background transfers; and multiple simultaneous transfers. Other
  17738. features include new modem control functions, enhanced support for 16550A
  17739. UART, and support for multiport boards such as Digiboard PC/X. Contact Blaise
  17740. Computing Inc., 819 Bancroft Way, Berkeley, CA 94710, (510) 540-5441; FAX:
  17741. (510) 540-1938.
  17742.  
  17743.  
  17744. GUI Computer Releases ObjectTable C/C++ V1.5
  17745.  
  17746.  
  17747. GUI Computer has released version 1.5 of their ObjectTable C/C++, a
  17748. Windows-oriented library that implements a programmable multicolumn table
  17749. object. This release supports protected column, draggable column width,
  17750. international currency, different true-type font and color for column, and
  17751. various title options. ObjectTable C/C++ can work as a custom control and is
  17752. compatible with Borland Resource Workshop and Microsoft Dialog Editor. Contact
  17753. GUI Computer, Inc., P. O. Box 795908, Dallas, TX 75379, (800) 800-9010; FAX:
  17754. (214) 250-1355; BBS: (214) 250-2077.
  17755.  
  17756.  
  17757. Lucid's Energize 1.1 Expands Tools and Utilities
  17758.  
  17759.  
  17760. Lucid, Inc., has announced Energize 1.1, with support for more tools and
  17761. utilities and improved performance in its native code C++ and C compiler. New
  17762. releases of Lucid C and Lucid C++ have begun shipping. Energize 1.1 supports
  17763. tools and libraries for C and C++, including system math libraries and the
  17764. latest version (3.01) of the InterViews library from Stanford University.
  17765. Energize support what Lucid calls Computer-Aided Programming (CAP). CAP
  17766. offloads routine and repetitive tasks to the computer. Energize also supports
  17767. incremental compilation and linking. Contact Lucid, Inc., 707 Laurel Street,
  17768. Menlo Park, CA 94025, (415) 329-8400; FAX: (415) 329-8480.
  17769.  
  17770.  
  17771. Non Standard Logics Introduces Version 2.0 of XFaceMaker
  17772.  
  17773.  
  17774. Non Standard Logics has introduced version 2.0 of XFaceMaker (XFM), its
  17775. X/Motif application interface management system. XFM's new WidgetMaker feature
  17776. allows designers to build customized widgets that can be used with Motif, Open
  17777. Look and other toolkits. XFM's templates can be used to assemble widgets into
  17778. objects with assigned behavior, inheritance, and open-ended reusability. XFM
  17779. includes a resource editor, a C-like scripting language (FACE) and a test
  17780. mode. Contact Non Standard Logics, 99 Bedford Street, Boston, MA 02111, (617)
  17781. 482-6393; (617) 482-9707.
  17782.  
  17783.  
  17784. Microware Announces ANSI C Compiler for Intel and Motorola Processors
  17785.  
  17786.  
  17787. Microware Systems Corporation has announced their Ultra C compiler, an ANSI C
  17788. compatible compiler with an architecture and optimizing algorithms designed to
  17789. maximize performance in real-time applications. Ultra C has been tested for
  17790. compliance with the Plum Hall C Validation Suite. Ultra C is closely tied to
  17791. Microware's OS-9 and OS-9000 real-time operating systems, including I/O and
  17792. system calls. The Ultra C compiler is compatible with any processor running a
  17793. Microware operating system. With Ultra C, Microware provides a tightly-coupled
  17794. compiler and operating system, and a single source of technical support.
  17795. The Ultra C architecture divides compilation into four processes: a front-end
  17796. language process, an optimization and linking function, a target processor
  17797. back end, and the assembly and linking function. Ultra C makes extensive use
  17798. of an intermediate code (I-Code), performing linking and optimization on the
  17799. I-Code, in addition to the other three stages of compilation. The language
  17800. processor is compatible with source for previous Microware compilers, ANSI C
  17801. source, and ANSI-extendable code. Ultra C can generate object code for any of
  17802. its target processors, facilitating application development for multiple
  17803. target systems.
  17804. For information, contact Microware Systems Corp., 1900 NW 114th Street, Des
  17805. Moines, IA 50325-7077, (800) 475-9000; FAX: (515) 224-1352; Telex: (910)
  17806. 520-2535.
  17807.  
  17808.  
  17809. Phar Lap's 286 DOS-Extender Supports Borland C++ 3.1 and Microsoft C/C++ 7.0
  17810.  
  17811.  
  17812. Phar Lap Software, Inc., has announced version 2.5 of their 286 DOS-Extender
  17813. which is compatible with Borland C++ 3.1 and Microsoft C/C++ 7.0, as well as
  17814. Turbo Debugger and CodeView for Windows. Version 2.5 includes faster floating
  17815. point for Microsoft C/C++ users and huge memory model support for Borland C++
  17816. users. 286 DOS-Extender allows programs to access up to 16MB of memory on any
  17817. DOS-based 80286, 386, or i486 PC. 286 DOS-Extender is priced at $495, upgrade
  17818. discounts are available. Contact Phar Lap Software, Inc., 60 Aberdeen Avenue,
  17819. Cambridge, MA 02138, (617) 661-1510; FAX: (617) 876-2972.
  17820.  
  17821.  
  17822. Sequiter Software Releases CodeTranslator 2.0
  17823.  
  17824.  
  17825. Sequiter Software, Inc., has released CodeTranslator 2.0, which translates
  17826. Clipper '87 and dBASE III PLUS applications into C. This tool can be used for
  17827. performance enhancement, porting to UNIX, Microsoft Windows, or OS/2, and
  17828. teaching xBASE programmers the C language. CodeTranslator generates ANSI
  17829. compliant C code and can be combined with a portable library, CodeBase
  17830. (currently version 4.5, but with version 5.0 planned for release in November
  17831. 1992). CodeTranslator 2.0 is priced at $245, and CodeBase 4.5 retails for
  17832. $395. Contact Sequiter Software, Inc., #209, 9644 - 54 Avenue, Edmonton,
  17833. Alberta, Canada, T6E 5V1, (403) 437-2410; FAX: (403) 436-2999.
  17834.  
  17835.  
  17836. Pinnacle Publishing Upgrades Graphics Server SDK to Version 2.0
  17837.  
  17838.  
  17839. Pinnacle Publishing, Inc. has announce Version 2.0 of the Graphics Server
  17840. Software Development Kit (SDK), a dynamic link library (DLL) for adding
  17841. graphing and charting capabilities to Windows applications. Graphics Server
  17842. SDK supports C/C++, Turbo Pascal, Visual Basic, SQL Windows, Superbase,
  17843. PowerBuilder, and Actor. Version 2.0 includes three new graph types--bubble
  17844. graph, tape graph, and 3d area graph, automatic "hot graphs", improved axis
  17845. and font control, multiple graphs, a print cancel option, and curve fitting
  17846. capabilities. Contact Pinnacle Publishing, Inc., P.O. Box 1088, Kent, WA
  17847. 98035, (206) 251-1900; FAX: (206) 251-5057.
  17848.  
  17849.  
  17850. JMI Announces C EXECUTIVE for AMD Am29205
  17851.  
  17852.  
  17853. MMI Software Consultants, Inc., has announced C EXECUTIVE support for AMD's
  17854. new Am29205 microcontroller. JMI's C EXECUTIVE is a real-time, multitasking,
  17855. ROMable operating system kernel used in embedded systems. C EXECUTIVE is
  17856. written in C for portability and is available for 80186, R3000, 29000, i960,
  17857. 80386, 68000 and other processors. C EXECUTIVE also offers a file system,
  17858. CE-DOSFILE, and a debugger, CE-VIEW. For some processors, C EXECUTIVE will be
  17859. available packaged with FUSION TCP/IP from Network Research Corp. Contact JMI
  17860. Software Consultants, Inc., 904 Sheble Lane, P.O. Box 481, Spring House, PA
  17861. 19477, (215) 628-0840.
  17862.  
  17863.  
  17864. Intermetrics Releases Three Debuggers for 68HC16 Processor
  17865.  
  17866.  
  17867. Intermetrics Microsystems Software, Inc., has announced CXDBmon, CXDBsim, and
  17868. CXDBice, C source-level debuggers for the Motorola 68HC16 processor. CXDBice
  17869. is pre-integrated with in-circuit emulators from Motorola, Pentica, Nohau,
  17870. EST, and Huntsville Microsystems; CXDBsim provides an instruction set
  17871. simulator; and CXDBmon provides a monitor target program and supports the
  17872. Motorola 68HC16 EVB development board. Contact Intermetrics Microsystems
  17873. Software, Inc., 733 Concord Avenue, Cambridge, MA 02138-1002, (617) 661-0072;
  17874. FAX: (617) 868-2843.
  17875.  
  17876.  
  17877.  
  17878. TeleSoft Announces TeleUSE Version 2.1
  17879.  
  17880.  
  17881. TeleSoft has announced version 2.1 of TeleUSE, a user interface management
  17882. system (UIMS) for developing GUIs based on OSF/Motif. The new version includes
  17883. a session manager, an improved interface, a more powerful Dialog Manager with
  17884. expanded C++ support, and third-party tool integration. TeleUSE is priced at
  17885. $7,500 and supports UNIX platforms including: SPARC, HP, and IBM. Contact
  17886. TeleSoft, (619) 457-2700.
  17887.  
  17888.  
  17889. Electronic Imagery's ImageScale Plus Adds JPEG Compression
  17890.  
  17891.  
  17892. Electronic Imagery has announced that its ImageScale Plus Developer's Toolkit
  17893. for MS-DOS and UNIX applications, now includes the JPEG compression algorithm.
  17894. The toolkit provides command-line executable programs, and graphics, text, and
  17895. cursor routines in an image processing library written in C. Contact
  17896. Electronic Imagery, Inc, 1100 Park Central Boulevard South, Suite 3400,
  17897. Pompano Beach, FL 33064, (305) 968-7100; FAX: (305) 968-7319.
  17898.  
  17899.  
  17900. EMS Professional Shareware Ships dBUtilily and C Utilily Libraries on CD-ROM
  17901.  
  17902.  
  17903. EMS Professional Shareware has begun shipping two new CD-ROMs: the Library of
  17904. PD/Shareware C Utilities, with 722 programs, and the dBUtility Library, with
  17905. 2238 different public domain and shareware products. The disks are priced at
  17906. $125, are updated quarterly, and include a substantial database of PC products
  17907. (35,000) and vendors (13,000). Contact EMS Professional Shareware, 4505
  17908. Buckhurst Ct., Olney, MD 20832, (301) 924-3594; FAX: (301) 963-2708.
  17909.  
  17910.  
  17911.  
  17912.  
  17913.  
  17914.  
  17915.  
  17916.  
  17917.  
  17918.  
  17919.  
  17920.  
  17921.  
  17922.  
  17923.  
  17924.  
  17925.  
  17926.  
  17927.  
  17928.  
  17929.  
  17930.  
  17931.  
  17932.  
  17933.  
  17934.  
  17935.  
  17936.  
  17937.  
  17938.  
  17939.  
  17940.  
  17941.  
  17942.  
  17943.  
  17944.  
  17945.  
  17946.  
  17947.  
  17948.  
  17949.  
  17950.  
  17951.  
  17952.  
  17953.  
  17954.  
  17955.  
  17956. We Have Mail
  17957. Dear Mr. Plauger,
  17958. For some years, I wrote most of my code in assembler, first on a CP\M system,
  17959. and later, under MS-DOS. A friend introduced me to C just over a year ago, and
  17960. I could kick myself for waiting so long to discover it. What used to take days
  17961. in MASM can be written in hours (or minutes) with C, especially the
  17962. input/output functions.
  17963. When I recently upgraded to QuickC v2.5, I returned the CUG membership card in
  17964. the package, and began receiving the C Users Journal. I've enjoyed the
  17965. magazine almost as much as I've enjoyed working with the language.
  17966. Your column and comments are always informative, and I appreciate the
  17967. magazine's coverage of specific issues--C++, Liana, Windows, etc.
  17968. These articles are so useful because they represent real-world programming,
  17969. where I might spend one evening coding a device controller, then spend three
  17970. months coding the user interface (even in robotics and automation, they won't
  17971. buy it unless it has pull-down menus and bells and whistles).
  17972. That brings me to my point.
  17973. A reader recently complained that C doesn't have a cls function. You answered
  17974. that the ANSI committee was reluctant to set any standards of that type,
  17975. because they insisted on viewing all input and output as streams.
  17976. That letter struck a chord with me. By being so fanatical about portability,
  17977. and by insisting on viewing I/O from a single standpoint--that of streams--the
  17978. ANSI committee may have ended up making the language less portable.
  17979. Simply put:
  17980. 1. Few people will be impressed with and/or use (or more importantly, buy) a
  17981. program that has a dumb-terminal display. Programs that scroll one line at a
  17982. time are almost as exciting as watching mold grow.
  17983. 2. Because the ANSI refused to specify standards for these basic display
  17984. functions, the programmer is forced to sacrifice portability with the first
  17985. clearscreen or locate cursor function.
  17986. Sure, I could write a very portable program that uses ANSI.SYS to control the
  17987. screen. The problem is, it would run slower than itch, and no one would buy
  17988. it. Now, I realize that setting a GUI standard might be beyond the scope of an
  17989. ANSI committee. But after giving it a lot of thought, I believe that the
  17990. current standard is too limited. More simply put: I respect ANSI's viewpoint,
  17991. but in this day and age of 486 PCs and VGA monitors and Visual BASIC and
  17992. MacIntoshes, can't I expect my programming language to have a standard
  17993. function for wiping a screen or locating a text cursor?
  17994. A number of objections are commonly raised. The first one goes, "You're
  17995. opening Pandora's box; where do we stop? Next, they'll want functions like
  17996. SET_CIRCLE_COLOR ..."
  17997. I agree that you have to know where to stop. Graphics functions, in
  17998. particular, might not be easily standardized. But couldn't we agree on a
  17999. couple of functions that would at least bring C up to par with BASIC for text
  18000. display?
  18001. My list would include cls, locate_cursor, and set_text_color. These could be
  18002. implemented on virtually all modern computer systems.
  18003. The next objection would be that many computers don't support text
  18004. positioning, color and/or graphics. So? If the computer won't support color,
  18005. the system just ignores set_color and displays in monochrome text. If the
  18006. computer won't support hardware functions such as moving the cursor, I'll just
  18007. have to work around it.
  18008. That's precisely what I'm doing now, so what have I lost?
  18009. In closing, I don't want to criticize the ANSI committee unfairly. I respect
  18010. what they've done, and agree that a standard was needed. All I'm saying is
  18011. that they could have looked a bit more at the real world, rather than just at
  18012. a theoretical stream concept, before setting that standard.
  18013. As computer hardware becomes more sophisticated, we're actually going to pay
  18014. the price down the road in reduced portability, as programmers are
  18015. increasingly forced to resort to their own--and quite non-ANSI--solutions to
  18016. each user interface.
  18017. Yours truly,
  18018. Stephen M. Poole
  18019. 122 N. Main Street
  18020. Raeford, NC 28376
  18021. You missed the point. We were not in love with a "theoretical stream concept"
  18022. to the exclusion of all reason. Rather, we did nearly all our work at a time
  18023. when streams were very real and screens were very new. It wasn't clear that
  18024. your particular set of primitives were worth standardizing. Now the issues are
  18025. more clear cut. You can still isolate the disturbance in a few
  18026. system-dependent functions. Remember, portability is a statement about
  18027. relative cost, not a true or false condition. -- pjp
  18028. Dear CUJ:
  18029. Mr. Wilbon Davis' article on time complexity in the September 1992 issue was
  18030. quite interesting.
  18031. However, it gave only a single sentence to a very serious problem concerning
  18032. many quicksort implementations; that the use a fixed pivot point can result in
  18033. O(n2) time, the same as a bubble sort. qsort's run time is very much
  18034. probabilistic. There are a great many programmers who believe otherwise.
  18035. In the Febrary 1992 issue of Unix Review, Nr. John Bently explained the reason
  18036. for this and showed that the problem is present in many of the popular qsort
  18037. implementations. Since any fixed pivot will result in quadratic run time he
  18038. suggests adding a bit of randomness to the pivot selection. The possibility of
  18039. quadratic run time still exists, but only if the data to be sorted is in
  18040. cahoots with the random number generator.
  18041. In the August 1992 issue of Unix Review. Mr. John Bently also examines the
  18042. heapsort and its problems.
  18043. Sincerely,
  18044. Mr. Carey Bloodworth
  18045. 1601 North Hills Blvd.
  18046. Van Bure, AR 72956
  18047. Your point is well taken. Quicksort can have nasty time complexity for some
  18048. patterns of input. -- pjp
  18049. Dear Sir/Madam:
  18050. I would like to know if you could help me with the following. I would like to
  18051. create computer games and animation (musical animation). I have some
  18052. programming background, and I have Borland's Turbo C++ and Assembler.
  18053. I have a 386SX IBM compatible (16 MHz) with a VGA monitor. I use v3.3 MS-DOS.
  18054. I have 1 Meg of RAM, a 60 Meg hard drive (Drive C), two high density drives 5
  18055. 1/4" (Drive A) and a 3 1/2" (Drive B), a 24 pin dot matrix printer (Roland
  18056. Raven 2417), and use a keyboard for everything. I am presently upgrading to
  18057. the following: MS-DOS 5.0; Windows 3.1; Soundblaster Pro; 10 Megs of RAM; 245
  18058. Meg hard drive; math co-processor; and a mouse.
  18059. I am considering at the end of this year looking into a 33 MHz or higher
  18060. motherboard and next year a CD-ROM, but I am considering waiting a little
  18061. longer for the CD-ROM as I want to read and write it and the ones available
  18062. now are read only. I would like to create computer games like Conquest of
  18063. Longbow, Wolfpack, Indiana Jones Last Crusade, Fate of Atlantis, Prince of
  18064. Persia etc. I would like to create my games as life like as the ones
  18065. mentioned.
  18066. I have a game in mind that deals with ancient Egypt. It needs to be able to
  18067. create pictures such as pyramids, coffins, King Tut's gold mask,
  18068. hieroglyphics, etc. I would also like to create animated films such as Walt
  18069. Disney's Beauty and the Beast and 101 Dalmations, etc. What I want to do (if
  18070. possible) is to create the animation, with the music from the CD-player, and
  18071. record it on diskette. Once this done I would like to transfer it on a VCR
  18072. tape and play it on my T. V. Can this be done?
  18073. Can you also provide me with the following:
  18074. 1. Can you recommend books that show how to create games and animation? If so,
  18075. please give title of book, author's name, and if possible where to purchase.
  18076. 2. Call you recommend any schools that provide correspondence learning in this
  18077. matter. Please give name and address.
  18078. For the programs listed in your magazine, can diskette with the programs
  18079. already in the magazines be purchased. If so please provide cost, shipping and
  18080. handling, and method of payment. Also can they be used with Borland's Turbo
  18081. C++?
  18082. If you can kindly provide me with any other information that is not requested
  18083. in this letter, please do so as I have written letters to schools here in
  18084. Canada, magazines, and computer companies. But all say they cannot help me.
  18085. Please understand that I would like very much to learn as I enjoy playing the
  18086. games and am fascinated by them. I would really like to learn how I can create
  18087. my own.
  18088. Thank you for your cooperation in this matter. Hope to hear from you soon.
  18089. Yours truly,
  18090. Victoria Ceolin
  18091. 510 Acadia Drive
  18092. Hamilton, Ontario
  18093. Canada
  18094. L8W 3A4
  18095. You've laid out a very ambitious program. You have much to learn to get where
  18096. you want to go. You'll also have to wait another year or two, as you've
  18097. already guessed, for inexpensive hardware to come within reach of your dreams.
  18098. But please don't stop dreaming. Your fascination with what can be done with
  18099. computers can see you through the tough learning.
  18100. My recommendation is that you get one of the simpler animation programs
  18101. currently available and start practicing your craft with it. Reading CUJ will
  18102. help you learn whatever programming you may need, but I suspect you want to
  18103. keep that to a minimum. Also read PC Magazine from time to time to keep track
  18104. of the latest advances in PC hardware and software. Sorry I can't be more
  18105. specific, but I encourage any readers who share your interests to contact you
  18106. directly. Good luck. -- pjp
  18107. Dear Dr. Plauger:
  18108. One of the few things more humbling than admitting to bugs in your
  18109. carefully-crafted software is the introduction of new bugs when attempting to
  18110. squash the old ones! Or, as Charlie Brown would say, ARRRRRRRRRRRGH! I am
  18111. referring to your article in the September, 1992 issue of the C Users Journal,
  18112. in which you attempt to fix a bug in which a failed memory allocation with
  18113. malloc is ignored and data are copied to an address specified by a null
  18114. pointer. (The code in question can be found on page 12.)
  18115. Either the replacement code has fallen victim to a typesetting error, or else
  18116. it must not been successfully passed through a conforming C compiler. This is
  18117. a very real risk that we expose ourselves to whenever a code change seems so
  18118. trivial that we only test it with the C compiler located between our ears!
  18119. Have you noticed the error, now that I've directed your attention to the code
  18120. fragment?
  18121.  
  18122. Maybe it's true that the only time we can say with assurance that all the bugs
  18123. are gone from a program is when the very last copy of the program in existence
  18124. has been incinerated! I feel that this is true for most non-trivial programs
  18125. I've written!
  18126. Yours truly,
  18127. John P. Toscano
  18128. PharmData Systems
  18129. P.O. Box 11537
  18130. St. Paul, MN 55111-0537
  18131. Error noted. It was indeed a transcription error. Thanks for reporting it. --
  18132. pjp
  18133. Mr. Plauger,
  18134. I thoroughly enjoyed Dwayne Phillips's, "The Foundation of Neural Networks:
  18135. The Adaline and Madaline" (September 1992). It very clearly presented the
  18136. fundamentals of neural network programming.
  18137. I feel that a few small changes in the C code provided will yield improved
  18138. performance. I am referring specifically to Listing 4, which makes the final
  18139. AND/OR/MAJORITY decision. In the first two cases AND and OR), basic logical
  18140. principles allow the for loops to be terminated immediately upon finding the
  18141. first of the appropriate values. For example, in the AND decision, the loop
  18142. may be terminated upon finding the first FALSE value (--1). There is no need
  18143. to check any further inputs, as only one FALSE input is necessary to ensure a
  18144. FALSE output in an AND condition. Similarly, the OR decision can be terminated
  18145. as soon as the first TRUE value (1) is found. This is the same method used by
  18146. C compilers to "short-circuit" logical tests. In both cases, inserting a break
  18147. statement into the loop should do the trick. [Code available on monthly code
  18148. disk.]
  18149. The MAJORITY decision code can also be improved by noting that the outputs
  18150. being checked conveniently have the values 1 and --1. All that is necessary is
  18151. to sum up all outputs. If the sum is positive, the 1 values are in the
  18152. majority; if the sum is negative, the --1 values are in the majority. To be
  18153. consistent with the original code, a sum of O should also yield a result of
  18154. --1. Refer to the listing for the exact method.
  18155. Admittedly, with the small size of the networks presented in the article, the
  18156. effects of these changes will probably be negligible. However, the performance
  18157. improvement from these modifications could be important if this (or similar)
  18158. code is used as the basis of larger, more complex networks.
  18159. Yours very truly,
  18160. Eric B. Schuyler
  18161. 81 Yorktown Road
  18162. Snyder, NY 14226
  18163. Dear Dr. Plauger,
  18164. Thanks for your encouraging response to my earlier letter (CUJ Sep. 1992)
  18165. concerning a proposed article on fail/fool-proof data input functions in C. At
  18166. your request, I will expand a bit further on my suggestion. I see two aspects
  18167. of the problem: input of individual data fields of various types, and
  18168. combination of these fields in a data input screen.
  18169. Concerning the first aspect, an input function for a given data type, e.g.
  18170. currency, date, string of limited length, and other specialized types, should
  18171. validate input immediately during data entry and alert the operator on errors.
  18172. It should also allow correction of mistakes made during entry.
  18173. Enclosed is an example of such a function for input of currency data that I
  18174. wrote some time ago. It uses non-portable, unbuffered input functions, getch
  18175. and putch, supported by Borland and Microsoft compilers, but not necessarily
  18176. by others. Possibly, getchar could be made to work as well. Of more
  18177. importance, I suspect that this function could be implemented more succinctly
  18178. by a better programmer--hence my suggestion for the article!
  18179. As to the second aspect, a collection of data input functions should be
  18180. combined in such a manner that the operator can return to an earlier data
  18181. field to make last-minute corrections, e.g. through use of the PgUp and PgDn
  18182. keys. This may be the easier of the two aspects, but is certainly not trivial.
  18183. After my letter appeared (the first time!) in the August issue of CUJ, I
  18184. received a response from Robert A. Radcliffe (Philadelphia, PA), author of
  18185. Data Handling Utilities in Microsoft C. Unfortunately, that text is now out of
  18186. print, although the author still has some of the code disks. Maybe you could
  18187. convince him to write the kind of article I have in mind!
  18188. Thanks for your interest,
  18189. W.F.H. Borman
  18190. 209 Logwood Drive
  18191. Evansville, IN 47710
  18192. Tel: 812 464-5435
  18193. Dear Mr. Plauger:
  18194. I was much impressed by your mea culpa contained in the "Standard C: Bugs"
  18195. column in the September, 1992 issue. It was a pleasure to find someone noble
  18196. enough to admit mistakes can occur and that he, too, is a mere mortal.
  18197. Unfortunately, character as fine as yours is a rare commodity!
  18198. I do think, however, that you were a little too hard on yourself. There is
  18199. another certainty in life besides Death and Taxes for anyone who writes (or
  18200. uses) software: bugs.
  18201. A bug in the vaunted Kernighan and Ritchie getline function on page 26 in the
  18202. first edition and page 29 in the second edition of The C Programming Language
  18203. causes the example to fail, as do dozens of other examples throughout both
  18204. editions of "the book." They use getline expecting it to return 0 (zero) for a
  18205. blank line when in fact it always returns 1 (one) or greater. The precedence
  18206. table on page 49 of the first edition has errors in it. Borland's C compilers
  18207. happily fopen files whose names have spaces in them (which of course can't be
  18208. deleted by normal means). In Turbo C v2.0 and older versions, free failed if
  18209. gets and related functions were used for keyboard input.
  18210. If the authors and all those people involved at Bell Labs and Prentice Hall
  18211. couldn't get a 13-line function right after ten years and two editions, why
  18212. should anyone expect you to write perfect software? A true cynic sees known
  18213. bugs as job insurance!
  18214. If even the originators of such a great lean and mean language as C can't
  18215. avoid a few bugs shouldn't one place simplicity and reliability at the top of
  18216. one's priorities and always expect bugs? Ronnie R's favorite Russian proverb,
  18217. "Trust but verify," should hang on every programmer's wall.
  18218. Kindest regards,
  18219. Elliott K. Rand
  18220. President, Keep It Simple Systems
  18221. P.O. Box 510093
  18222. Melbourne Beach, Florida 32951
  18223. (407) 729-0187
  18224. Thanks, I needed that. -- pjp
  18225. Mr. Plauger,
  18226. Jodi Leonard informs me that you doubled the price for your C library code
  18227. disk since it was "updated and expanded." Humpf! Other than fix bugs, what
  18228. does "expanded" include?
  18229. I read your article describing the bugs found, it was somewhat amusing as a
  18230. programmer's "confession", if not very scientific.... Care to expand in your
  18231. next column what was changed? Does the current disk also come with a list of
  18232. known bugs?
  18233. Thanks,
  18234. Mike MacFaden
  18235. Group Leader Unix NMS Development
  18236. Premisys Communications, Inc
  18237. 1032 Elwell Court Suite 111 Palo Alto,
  18238. CA 94303
  18239. email: ...!fernwood!premisys!mike
  18240. mike@premisys.com
  18241. Compuserve: 72711.2060
  18242. voice: 415-940-4787
  18243. fax: 415-940-7713
  18244. The new code disk contains no bug list because I fixed all known bugs at the
  18245. time I released it. I have since accumulated two (small) bug fixes, but I'm
  18246. not about to thaw a frozen release whenever I find yet another bug. -- pjp
  18247.  
  18248.  
  18249.  
  18250.  
  18251.  
  18252.  
  18253.  
  18254.  
  18255.  
  18256.  
  18257.  
  18258.  
  18259.  
  18260.  
  18261.  
  18262.  
  18263.  
  18264.  
  18265.  
  18266.  
  18267.  
  18268.  
  18269.  
  18270.  
  18271.  
  18272.  
  18273.  
  18274.  
  18275.  
  18276.  
  18277.  
  18278.  
  18279.  
  18280.  
  18281.  
  18282.  
  18283.  
  18284.  
  18285.  
  18286.  
  18287.  
  18288.  
  18289.  
  18290.  
  18291.  
  18292.  
  18293.  
  18294.  
  18295.  
  18296.  
  18297.  
  18298.  
  18299.  
  18300.  
  18301.  
  18302.  
  18303.  
  18304.  
  18305.  
  18306.  
  18307.  
  18308.  
  18309.  
  18310.  
  18311.  
  18312.  
  18313.  
  18314.  
  18315. SSX -- Stack Swap eXecutive
  18316.  
  18317.  
  18318. Tom Green and Dennis Cronin
  18319.  
  18320.  
  18321. Tom Green is a UNIX Software engineer who specializes in UNIX driver
  18322. development. He also writes MS-DOS, Windows and embedded 80x86 applications.
  18323. He may be reached via electronic mail at tomg@cd.com.
  18324.  
  18325.  
  18326. Dennis Cronin almost completed an EE degree but got lured into the sordid
  18327. world of fast computers, easy money, and loose connections. Specialties: UNIX
  18328. driver development and embedded systems. He may be reached via electronic mail
  18329. at denny@cd.com.
  18330.  
  18331.  
  18332.  
  18333.  
  18334. Introduction
  18335.  
  18336.  
  18337. A project we worked on recently required us to port existing disk-controller
  18338. software from an 80186-based board to a new 80386-based product. The total
  18339. port required that we port not only the actual controller software, but also
  18340. our real-time executive and a debugger as well. Our existing real-time
  18341. executive was written in C and assembly language. We simply set out to
  18342. directly port it to the 386. Oh yeah, we also committed to a pretty aggressive
  18343. (translation: insane) schedule.
  18344. The existing executive used a software-interrupt entry point with parameters
  18345. passed in registers--pretty standard stuff. Task switches were implemented in
  18346. assembly language, swapping in and out registers for the new task and the
  18347. running task. We used a similar interrupt interface in the protected-mode
  18348. version with 80386 task gates used to swap in and out registers for tasks.
  18349. After we ported the disk controller software to protected mode, we tied the
  18350. whole thing together and tried to run the new software. The new software
  18351. worked fine. There was one problem though--it was slower than the old version
  18352. running on the 80186!
  18353. We were quite surprised (the words dismayed and panicked come to mind as well)
  18354. by the results of our first test. We set out to find the problem. We
  18355. discovered that our disk-controller software was doing a rather large number
  18356. of task switches per second and making a pile of calls into the real-time
  18357. executive.
  18358. Referring to our trusty Intel black book, we then noted that calling an 80386
  18359. task gate takes 309 clock cycles--not good. But after looking over the
  18360. executive, we decided that calling 80386 task gates was not our only speed
  18361. problem. The whole process of calling our executive took a lot of time. First
  18362. an assembly-language routine was called from the disk-controller C-code
  18363. software. The assembly-language routine would get all of the parameters passed
  18364. C-style and then stuff them in registers. Next a software interrupt was
  18365. executed, followed by getting the registers back onto the stack so we could
  18366. call into the C portion of the executive. We also had to preserve the state of
  18367. the registers so that, on return from the executive, the registers would be
  18368. restored for the calling software. It should be noted that this basic approach
  18369. resulted from our early experiences with a particular commercial real-time
  18370. executive and its C support libraries.
  18371. After studying things, we realized we could streamline the whole process
  18372. dramatically. Why not simply take advantage of the fact that a C compiler
  18373. saves and restores as much context as necessary between C function calls? The
  18374. compiler knows when making a call to another C routine which registers will be
  18375. saved across the call. This meant that we could ditch swapping tasks with an
  18376. 80386 task gate and skip the 309 cycles used to do a task switch. And
  18377. switching tasks was now a simple C-language call into the linked executive--a
  18378. small set of subroutines linked with the main application, instead of a remote
  18379. set of services accessible only through software interrupts.
  18380. Since the basic mechanism of the task switch is now a rather trivial stack
  18381. frame switch, we called the design the Stack Swap eXecutive, or SSX (Listing
  18382. 1, Listing 2, and Listing 3).
  18383.  
  18384.  
  18385. Pros and Cons of SSX
  18386.  
  18387.  
  18388. There are many things to recommend SSX. The native C-language interface and
  18389. frisky stack-swap task switch make it very fast and efficient. And it is very
  18390. small. The small size can save precious RAM or EPROM space that a larger
  18391. executive might take up. SSX is also very portable. This article uses the
  18392. executive in the MS-DOS environment but it is flexible enough to use in
  18393. embedded applications or on different processor families. And since SSX is a
  18394. very minimal executive, it is also easy to understand, port, modify, and
  18395. extend.
  18396. There are, of course, a few limitations to using SSX. SSX is written in C and
  18397. works best with an application written in C or C++, although it's not too hard
  18398. to call into SSX from assembly after saving a few registers. SSX must be
  18399. linked with your application. If you have a number of separate programs which
  18400. much share CPU time and resources, they must all be linked together. Another
  18401. disadvantage with SSX is its lack of features compared with many
  18402. commercially-available executives.
  18403.  
  18404.  
  18405. Features of SSX
  18406.  
  18407.  
  18408. SSX is a real-time, preemptive, multitasking executive. Tasks are allowed to
  18409. run until:
  18410. The task readies another task of equal or higher priority
  18411. A time slice (a clock tick) passes and there are other ready tasks of equal
  18412. priority
  18413. An interrupt readies a task of equal or higher priority
  18414. The task explicitly gives up the CPU by calling the ssx_delay routine
  18415. Synchronization of tasks is accomplished via shared data structures of type
  18416. wait_q. A task that needs to wait for an event calls ssx_wait with a wait_q as
  18417. an argument. If the event has already occurred, as indicated by a message flag
  18418. at the queue, the task does not get suspended but remains ready.
  18419. When another task (or interrupt) wishes to ready a sleeping task, it issues an
  18420. ssx_alert to the queue. The highest priority task waiting is readied. If no
  18421. task is waiting, the message flag is set.
  18422. Out of these two basic primitives you can build just about anything you could
  18423. possibly need!
  18424. This model of synchronization has the advantage that it is quite efficient to
  18425. implement. There is no hashing of addresses onto sleep queues or any of that
  18426. kind of messiness. Moreover, it seems reasonable to assume in today's
  18427. object-oriented society that if tasks are cozy enough to be synchronizing with
  18428. each other, they should be well enough acquainted to share the wait_q data
  18429. object.
  18430. At the risk of making our lean mean executive seem feature-laden, we also
  18431. provided a primitive for waiting at a wait_q with an alarm timer set. If the
  18432. event does not occur within the specified period of time, the alarm goes off
  18433. and the task becomes ready again with a return value indicating the reason for
  18434. the resumption.
  18435. And to round out our real-time toolkit, we have the ssx_task_delay call which
  18436. simply excuses the task from execution for a specified number of clock ticks.
  18437. A crucial aspect of any real-time executive is how it is accessed from an
  18438. interrupt to signal events to task-level code. SSX provides two calls which
  18439. are used to frame the interrupt handler code. ssx_lock is called right after
  18440. the interrupt handler saves the necessary CPU register. ssx_lock disables task
  18441. switching so that the executive doesn't try to switch out the interrupt
  18442. handler before it has completed its processing. ssx_unlock is called at the
  18443. end of the interrupt handler right before it restores the CPU registers. This
  18444. allows task switching to take place again.
  18445. It should be noted that for extra efficiency you can skip both these calls if
  18446. the interrupt handler meets the following criteria:
  18447. It only makes one ssx_alert call.
  18448. All hardware processing is done before the call to ssx_alert (e.g. the
  18449. interrupt controller has been reset and any other steps necessary to clear the
  18450. interrupt at the requestor have been taken).
  18451. The idea is that at that point, the interrupt thread has just become a
  18452. continuation of the task that was running. The actual interrupt return can now
  18453. happen at any time.
  18454. The ssx_lock and ssx_unlock calls can also be used from the task level to
  18455. temporarily disable rescheduling. If a task is going to do something
  18456. time-consuming enough that it doesn't want to risk masking interrupts, but
  18457. can't afford to be switched out while performing the specific activities, it
  18458. can call ssx_lock to lock control of the CPU. Interrupts can still be handled,
  18459. but any changes they make to the task state will not be examined until the
  18460. ssx_unlock call is invoked.
  18461. For a complete list of function calls see Table 1.
  18462.  
  18463.  
  18464. Porting SSX
  18465.  
  18466.  
  18467.  
  18468. SSX currently works with Borland C and C++ compilers in the small model. SSX
  18469. will need porting to a different memory model or an environment other that
  18470. MS-DOS. To port SSX from the MS-DOS environment to a new one you must port
  18471. four functions. They are:
  18472. ssx_task_create
  18473. stack_swap
  18474. disable_ints
  18475. enable_ints
  18476. A task is created by a call to ssx_task_create before or after ssx_run is
  18477. called. You must have at least one task created before calling ssx_run. When a
  18478. task is created a stack space is allocated. This stack is then set up so that
  18479. when the task's stack pointer is swapped in, a simple return from stack_swap
  18480. will start the task off. Figure 1 shows what the newly-created task's stack
  18481. looks like after being created for the MS-DOS executive.
  18482. This sets the task up to be run for the first time. The code
  18483. /* stack_swap - switch from stack of current task
  18484. * to stack of new task
  18485. */
  18486.  
  18487. LOCAL void
  18488. stack_swap(unsigned int **old_stack_ptr,
  18489. unsigned int **new_stack_ptr)
  18490. {
  18491. asm or di,di /* fool compiler into saving */
  18492. asm or si,si /* di and si registers */
  18493.  
  18494. /* save stack ptr of old task */
  18495. *old_stack_ptr = (unsigned int *)_SP;
  18496. /* load stack ptr register with stack ptr */
  18497. /* of new task */
  18498. _SP=(unsigned int)*new_stack_ptr;
  18499. }
  18500. shows the C listing of stack_swap. On entry, the code executes two lines of
  18501. in-line assembly language. These instructions make sure the compiler saves and
  18502. restores the two register variables, 80X86 registers di and si. The last two
  18503. instructions use the pseudo-variable _SP. With Borland C compilers _SP allows
  18504. you to directly access the 80X86 sp (stack pointer) register. This part of the
  18505. code stores the stack pointer of the old task and puts the stack pointer of
  18506. the new task in the sp register. This is all the code has to do to switch
  18507. tasks.
  18508. The code
  18509. stack_swap proc near
  18510.  
  18511. ; prologue for a C function
  18512. push bp
  18513. mov bp,sp
  18514. push si
  18515. push di
  18516.  
  18517. ; this fools compiler into saving
  18518. ; si and di because
  18519. ; they are register variables
  18520. or di,di
  18521. or si,si
  18522.  
  18523. ; save stack pointer for old task
  18524. mov bx,word ptr [bp+4]
  18525. mov word ptr [bx],sp
  18526.  
  18527. ; load stack ptr register with stack
  18528. ; ptr of new task
  18529. mov bx,word ptr [bp+6]
  18530. mov sp,word ptr [bx]
  18531.  
  18532. ; epilogue for a C function
  18533. pop di
  18534. pop si
  18535. pop bp
  18536. ret
  18537. stack_swap endp
  18538. shows an 80X86 assembly-language listing of the C function stack_swap. In the
  18539. epilogue of the function stack_swap, registers di, si, and bp are popped off
  18540. the stack. We have placed these values on the task's stack during
  18541. ssx_task_create. The last instruction is ret which will pop the return address
  18542. off the stack and execute the function run_new_task. run_new_task enables
  18543. interrupts and runs the new task by calling a function pointer. The code
  18544. /* run_new_task - starts up a new
  18545. * task making sure interrupts are
  18546.  
  18547. * enabled
  18548. */
  18549.  
  18550. LOCAL void
  18551. run_new_task(void)
  18552. {
  18553. ints_on();
  18554. (t_current-task_ptr)();
  18555. }
  18556. shows the function run_new_task.
  18557. disable_ints is a routine that gets the current state of interrupts and then
  18558. disables interrupts and returns the previous state of interrupts. In the
  18559. MS-DOS version of SSX this function is coded as in-line assembly language
  18560. code. The pseudocode
  18561. disable_ints(void)
  18562. {
  18563. save current state of interrupts
  18564. (enabled or disabled);
  18565. disable interrupts;
  18566. returned saved state of interrupts
  18567. (positive integer if they were enabled
  18568. 0 is disabled)
  18569. }
  18570. indicates what is necessary to port disable_ints to other environments.
  18571. enable_ints enables interrupts. In the MS-DOS version of this function we have
  18572. used a Turbo C macro that places an 80X86 sti instruction in the code.
  18573.  
  18574.  
  18575. SSX Demo Code
  18576.  
  18577.  
  18578. The file demo.c (Listing 4) demonstrates many of the calls in SSX. This MS-DOS
  18579. demo program sets up a timer interrupt and then creates several tasks. The
  18580. timer interrupt handler uses vector 8 on the MS-DOS PC. This vector is called
  18581. 18.2 times a second. Each time the interrupt routine is called ssx_clock_tick
  18582. is called. This is one area of the code that is not portable. On an MS-DOS AT
  18583. PC you could also use the interrupt that is called 1,024 times a second if you
  18584. need more resolution than this timer supplies.
  18585. After the timer handler is setup the demo program sets up several tasks. Here
  18586. is a description of the tasks that are made.
  18587. Print queue tasks--These tasks increment a counter (one for each task) and
  18588. print the value of the counter. These tasks use a semaphore that allows only
  18589. one task at a time to call cprintf since it is not reentrant. The code
  18590. /* initialize semaphore to having
  18591. * waiting message */
  18592. #define SET_SEMAPHORE(wqptr) \
  18593. (wqptr)-mesg_flg=1; \
  18594. (wqptr)-task_ptr=NULL
  18595.  
  18596. /* initialize wait_q to NULL task_ptr
  18597. * and no */
  18598. /* message waiting */
  18599. #define INIT_WAIT_Q(wqptr) \
  18600. (wqptr)-mesg_flg=0; \
  18601. (wqptr)-task_ptr=NULL
  18602. contains macros to setup semaphores and wait queues. Many Standard C functions
  18603. are not reentrant, so keep that in mind when using C library functions. Five
  18604. of these tasks are created.
  18605. Time-slice tasks--These tasks get a full time slice to increment a counter
  18606. (one for each task). Since print-queue tasks have to get permission to print
  18607. each time through, their counters will increment slower than these time-slice
  18608. tasks. Five of these tasks are created.
  18609. Print-time-slice task--This task prints the counter values for each time-slice
  18610. task once every three seconds. This task must also use our print semaphore
  18611. each time it needs to print.
  18612. Keypress task--This task checks for a keypress every two seconds and calls
  18613. ssx_stop if it finds one.
  18614. System-time task--This prints the system time in seconds every second. This
  18615. task must also use our print semaphore each time it needs to print.
  18616. This code has been tested with Turbo C 2.0, Turbo C++ 1.0, and Borland C 3.1.
  18617. To make the demo program type:
  18618. tcc demo.c ssx.c
  18619. or
  18620. bcc demo.c ssx.c
  18621. Since this code uses in-line assembly language you will also need tasm if you
  18622. are using Turbo C 2.0 or Turbo C++ 1.0.
  18623. We hope you will find this executive portable and easy to use. For many
  18624. applications this will have more than enough features. If you find it is
  18625. missing something you need, no big deal. Change it. It's easy. It's all in C.
  18626. Figure 1
  18627. Table 1 SSX Function Calls
  18628. -----------------------------------------------------------------------
  18629. int ssx_alert(wait_q ssx_alert sends a message to a task waiting
  18630. *wqptr) on the specified wait_q. If there is already
  18631.  a message waiting on the specified wait_q,
  18632.  
  18633.  then a MW_ERR (see ssx.h) is returned. If
  18634.  there are any tasks waiting on the wait_q,
  18635.  the first task is scheduled if it has a high
  18636.  enoughpriority. If no task is waiting then a
  18637.  message is left on the wait_q.
  18638.  
  18639. int ssx_init(void) ssx_init is called before creating tasks or
  18640.  running the SSX executive. This initializes
  18641.  SSX executivedata.
  18642.  
  18643. int ssx_task_create ssx_task_create is called to create a task to
  18644. (unsigned char task_pri, run with the SSX executive. Following is list
  18645. unsigned char task_id, of parameters passed to ssx_task_create:
  18646. fptr task_ptr, unsigned name -- an eight-character name for the
  18647. int stack_size, char task. This is useful in debugging SSX
  18648. *name) applications. The size of name is setup in
  18649.  ssx_conf.h. stack_size -- the size of the
  18650.  stack for this task in bytes. In our demo
  18651.  application we are using 0x200. This will
  18652.  depend on your application. Start high if you
  18653.  think you will be deeply nested or you are
  18654.  using a lot of automatic variables.
  18655.  task_id -- the ID for the task being created.
  18656.  IDs range from 1 to 0xfe. ID 0xff is reserved
  18657.  for a background task that SSX creates. ID 0
  18658.  is used in some SSX calls when a task wants
  18659.  to refer to itself.
  18660.  task_pri -- the task priority. Tasks with the
  18661.  highest priority are scheduled to run first.
  18662.  Priorities range from 0 (highest priority) to
  18663.  0xff (lowest priority).
  18664.  task_ptr -- a pointer to a function. (Its
  18665.  typedef is in ssx.h.) Pass a pointer to the C
  18666.  function that is to be used as a task. A
  18667.  function used as a task must have no
  18668.  parameters passed and return no value. A task
  18669.  function is generally a loop without exit.
  18670.  
  18671. int ssx_task_delete ssx_task_delete deletes the task with the
  18672. (unsigned char task_id) task_id passed. If you pass in ID 0, the
  18673.  calling task is deleted.
  18674.  
  18675. int ssx_wait_with_alarm ssx_wait_with_alarm causes a task to wait for
  18676. (wait_q *wqptr, long a message for the number of ticks passed. If
  18677. timeout) no message is received within the timeout
  18678.  number of ticks, a TO_ERR is returned (see
  18679.  ssx.h). ssx_wait_with_alarm returns SUCCESS
  18680.  (see ssx.h) if the task gets a message on the
  18681.  wait_q.
  18682.  
  18683. long ssx_get_time(void) ssx_get_time gets the current clock ticks in
  18684.  SSX.
  18685.  
  18686. unsigned char ssx_change_priority changes the priority of
  18687. ssx_change_priority the calling task, but does not immediately
  18688. (unsigned char) reschedule tasks.
  18689. new_priority
  18690.  
  18691. void ssx_clock_tick(void) ssx_clock_tick is called to inject a clock
  18692.  
  18693.  tick into SSX. This is usually called by a
  18694.  timer interrupt handler.
  18695.  
  18696. void ssx_lock(void) ssx_lock disables task switching. Called
  18697.  either at the entry to an interrupt handler
  18698.  to prevent the interrupt handler from getting
  18699.  switched out or from task code to reserve
  18700.  control of the CPU for a while. Note: a task
  18701.  cannot wait, delay, or otherwise cause itself
  18702.  to become unrunnable while it has task
  18703.  switching locked.
  18704.  
  18705. void ssx_run(void) ssx_run is called after you have created at
  18706.  least one task and started the executive
  18707.  running.
  18708.  
  18709. void ssx_set_time(long ssx_set_time sets the number of clock ticks
  18710. ticks) in SSX to the number passed in.
  18711.  
  18712. void ssx_stop(void) ssx_stop is called to shutdown the SSX
  18713.  executive. It returns where ssx_run was
  18714.  called.
  18715.  
  18716. void ssx_switch(void) ssx_switch forces rescheduling of tasks by
  18717.  SSX. ssx_switch is usually called from within
  18718.  the executive. It schedules the highest
  18719.  priority task in the front of the ready
  18720.  queue.
  18721.  
  18722. void ssx_task_delay(long ssx_task_delay delays the calling task for
  18723. ticks) the number of ticks passed. It will be
  18724.  scheduled back in after the number of ticks
  18725.  have passed and when it is highest priority
  18726.  on the ready queue.
  18727.  
  18728. void ssx_unlock(void) ssx_unlock re-enables task switching. A
  18729.  matching call to ssx_lock must already have
  18730.  been made. Called at the exit of an interrupt
  18731.  handler, or from task level code to release
  18732.  the CPU to other tasks.
  18733.  
  18734. void ssx_wait(wait_q ssx_wait causes a task to wait for a message
  18735. *wqptr) on the passed wait_q. The typedef for a
  18736.  wait_q data structure is in the file ssx.h.
  18737.  If there is a message waiting, ssx_wait
  18738.  returns immediately. Ifthere is no message
  18739.  waiting, ssx_wait schedules a new task while
  18740.  the calling task waits for a message.
  18741.  
  18742. Listing 1 SSX.C -- Stack Swap eXecutive
  18743. /****************************************************/
  18744. /* By Tom Green and Dennis Cronin */
  18745. /* 10/19/92 */
  18746. /****************************************************/
  18747.  
  18748. /* turn on inline asm */
  18749. #pragma inline
  18750.  
  18751. #include <alloc.h>
  18752.  
  18753. #include <dos.h>
  18754. #include <string.h>
  18755. #include <setjmp.h>
  18756. #include "ssx.h"
  18757. #include "ssx_conf.h"
  18758.  
  18759. /* Task Control Block */
  18760.  
  18761. typedef struct tcb {
  18762. /* task chain forward ptr */
  18763. struct tcb *forw;
  18764. /* task chain backward ptr */
  18765. struct tcb *back;
  18766. /* delay chain forward ptr */
  18767. struct tcb *dforw;
  18768. /* delay chain backward ptr */
  18769. struct tcb *dback;
  18770. /* pointer to task code */
  18771. fptr task_ptr;
  18772. /* pointer to start of allocated stack */
  18773. unsigned int *stack;
  18774. /* task's current stack pointer */
  18775. unsigned int *stack_ptr;
  18776. /* delay counter */
  18777. long timeout;
  18778. /* flag for task timed out */
  18779. unsigned char timedout;
  18780. /* flag for TCB in use */
  18781. unsigned char active;
  18782. /* status flags */
  18783. unsigned char status;
  18784. /* task priority */
  18785. unsigned char priority;
  18786. /* task ID */
  18787. unsigned char id;
  18788. /* for storing extra task context */
  18789. char context[CNTXT_SZ];
  18790. } tcb;
  18791.  
  18792. /* misc. defines */
  18793. #define TRUE 1
  18794. #define FALSE 0
  18795.  
  18796. /* background task defines */
  18797. #define BG_TASK_ID 0xff
  18798. #define BG_TASK_PRI 0xff
  18799.  
  18800. /* make data and code local to this file */
  18801. #define LOCAL static
  18802.  
  18803. /* flags for the TCB status word */
  18804. #define T_READY 0 /* ready to run */
  18805. #define T_WAITING 1 /* waiting on wait_q */
  18806. #define T_DELAYED 2 /* delay timer running */
  18807.  
  18808. /* local function prototypes */
  18809.  
  18810. LOCAL tcb *get_tcb(void);
  18811. LOCAL void free_tcb(tcb *tbp);
  18812.  
  18813. LOCAL void put_ready(tcb *tbp);
  18814. LOCAL void rotate_tasks(tcb *tbp);
  18815. LOCAL void put_delay(long timeout);
  18816. LOCAL void run_new_task(void);
  18817. LOCAL void bg_task(void);
  18818. LOCAL void stack_swap(unsigned int **old_stack_ptr,
  18819. unsigned int **new_stack_ptr);
  18820. LOCAL int disable_ints(void);
  18821.  
  18822. /* local variables */
  18823.  
  18824. LOCAL long sys_time; /* system timer */
  18825. LOCAL unsigned char slice_cnt;
  18826. LOCAL int running;
  18827. LOCAL int initd;
  18828. LOCAL jmp_buf jbuf;
  18829. LOCAL int switch_lock;
  18830.  
  18831. /* task control */
  18832. LOCAL tcb t_pool[MAX_TASKS + 1]; /* pool of TCBs */
  18833. LOCAL tcb t_ready; /* head of ready task queue */
  18834. LOCAL tcb t_null; /* the NULL task */
  18835. LOCAL tcb *t_free; /* head of free queue */
  18836. LOCAL tcb *t_current; /* pointer to current task */
  18837.  
  18838. /* delay control */
  18839. LOCAL tcb d_chain; /* q head for delayed tasks */
  18840.  
  18841. /* MACROS to unlink from task and delay queues */
  18842.  
  18843. /* t_unlink - must be used w/ interrupts off */
  18844. #define t_unlink(tbp) \
  18845.  
  18846. { \
  18847. (tbp)->back->forw = (tbp)->forw; \
  18848. if((tbp)->forw != NULL) \
  18849. (tbp)->forw->back = (tbp)->back; \
  18850. }
  18851.  
  18852. /* d_unlink - must be used w/ interrupts off */
  18853. #define d_unlink(tbp) \
  18854. { \
  18855. (tbp)->dback->dforw = (tbp)->dforw; \
  18856. if((tbp)->dforw != NULL) \
  18857. (tbp)->dforw->dback = (tbp)->dback; \
  18858. }
  18859.  
  18860. /* ssx_init - init ssx data */
  18861.  
  18862. int
  18863. ssx_init(void)
  18864. {
  18865. int i;
  18866. tcb *tcbp;
  18867.  
  18868. if(initd)
  18869. return(INIT_ERROR);
  18870.  
  18871. memset(&d_chain,0,sizeof(d_chain));
  18872.  
  18873.  
  18874. /* init TCB free queue links */
  18875. for(i=0,tcbp=t_pool;i < MAX_TASKS-1;i++,tcbp++){
  18876. tcbp->forw = &t_pool[i+1];
  18877.  
  18878. }
  18879. t_pool[i].forw = NULL;
  18880.  
  18881. for(i = 0;i < MAX_TASKS;i++){
  18882. t_pool[i].active=FALSE;
  18883. }
  18884.  
  18885. t_current = NULL;
  18886. t_free = t_pool;
  18887. t_ready.forw = NULL;
  18888. switch_lack = 0;
  18889.  
  18890. /* set up background task */
  18891. if((ssx_task_create(BG_TASK_PRI,BG_TASK_ID,
  18892. bg_task,0x200, "bg_task"))
  18893. != SUCCESS)
  18894. return(INIT_ERROR);
  18895.  
  18896. initd = TRUE;
  18897.  
  18898. return(SUCCESS);
  18899. }
  18900.  
  18901. /* sx_run - this starts executive */
  18902.  
  18903. void
  18904. ssx_run(void)
  18905. {
  18906. int val;
  18907.  
  18908. val = setjmp(jbuf);
  18909.  
  18910. if(val != 0)
  18911. return;
  18912.  
  18913. slice_cnt = 0;
  18914. sys_time = 0;
  18915.  
  18916. /* make current task ptr point to dummy tcb so
  18917. * beginning of time stack pointer save will
  18918. * have a safe place to save to. */
  18919.  
  18920. t_current = &t_null;
  18921.  
  18922. /* mark SSX as active */
  18923. running = TRUE;
  18924.  
  18925. /* this will start the first task rolling */
  18926. ssx_switch( );
  18927. }
  18928.  
  18929. /* sx_stop - this stops executive */
  18930.  
  18931. void
  18932.  
  18933. ssx_stop(void)
  18934. {
  18935. int i;
  18936. int_state_var istate;
  18937.  
  18938. ints_off(istate);
  18939.  
  18940. /* free any allocated stacks */
  18941. for(i = 0; i < MAX_TASKS; i++){
  18942. if(t_pool[i].stack != NULL){
  18943. free(t_pool[i].stack);
  18944. t_pool [i].stack = NULL;
  18945. }
  18946. }
  18947. initd = FALSE;
  18948. running = FALSE;
  18949.  
  18950. restore_ints(istate);
  18951.  
  18952. longjmp(jbuf,1);
  18953. }
  18954.  
  18955. /* ssx_task_create - create task and set up tcb */
  18956.  
  18957. int
  18958. ssx_task_create(unsigned char task_pri,
  18959. unsigned char task_id,fptr task_ptr,
  18960. unsigned int stack_size,char *name)
  18961. {
  18962. unsigned int i;
  18963. tcb *tbp;
  18964. int_state_var istate;
  18965.  
  18966. ints_off(istate);
  18967.  
  18968. if(task_id == 0) {
  18969. restore_ints(istate);
  18970. return(TID_ERR);
  18971. }
  18972.  
  18973. /* check for TID already in use */
  18974. for(i = 0,tbp = t_pool;i < MAX_TASKS;i++,tbp++) {
  18975. if(tbp->active && tbp->id == task_id) {
  18976. restore_ints(istate);
  18977. return(TID_ERR);
  18978. }
  18979. }
  18980.  
  18981. if((tbp = get_tcb()) == NULL) { /* get a tcb */
  18982. restore_ints(istate);
  18983. return(TCB_ERR);
  18984. }
  18985.  
  18986. /* allocate stack for this task */
  18987. if((tbp->stack = (unsigned int *)
  18988. malloc(stack_size)) == NULL){
  18989. restore_ints(istate);
  18990. return(STACK_ERR);
  18991. }
  18992.  
  18993.  
  18994. /* fill in the blanks */
  18995. strncpy(tbp->context,name,CNTXT_SZ);
  18996. tbp->priority = task_pri;
  18997. tbp->id = task_id;
  18998. tbp->status = T_READY;
  18999. tbp->timedout = FALSE;
  19000. tbp->timeout = OL;
  19001. tbp->forw = tbp->back =
  19002. tbp->dforw = tbp->dback = NULL;
  19003.  
  19004. tbp->task_ptr = task_ptr;
  19005.  
  19006. tbp->stack_ptr = (unsigned int *)(tbp->stack +
  19007. (stack_size / 2));
  19008.  
  19009. /* setup task stack to have address of start up
  19010. * routine and fake di, si, bp registers to pop.
  19011. * This part is not portable. the stack looks
  19012. * like this:
  19013. *
  19014. * - high
  19015. * address of run_new_task 
  19016. * -
  19017. * bp 
  19018. * ---------------------
  19019. * si 
  19020. * -
  19021. * di 
  19022. * - low */
  19023.  
  19024. *(-tbp->stack_ptr) = (unsigned int)run_new_task;
  19025. *(-tbp->stack_ptr) = 0; /* fake BP,SI,DI */
  19026. *(-tbp->stack_ptr) = 0; /* on stack */
  19027. *(--tbp->stack_ptr) = 0;
  19028.  
  19029. /* put on active chain */
  19030. rotate_tasks(tbp);
  19031.  
  19032. ssx_switch();
  19033.  
  19034. restore_ints(istate);
  19035.  
  19036. return(SUCCESS);
  19037. }
  19038.  
  19039. /* ssx_task_delay - cause task to delay for number of ticks */
  19040.  
  19041. void
  19042. ssx_task_delay(long timeout)
  19043. {
  19044. int_state_var istate;
  19045.  
  19046. ints_off(istate);
  19047.  
  19048. if(timeout == 0) {
  19049. ssx_switch();
  19050. restore_ints(istate);
  19051. return;
  19052.  
  19053. }
  19054.  
  19055. put_delay(timeout); /* put current task on */
  19056. /* delay queue */
  19057. t_unlink(t_current); /* take off ready queue */
  19058.  
  19059. ssx_switch();
  19060. restore_ints(istate);
  19061. }
  19062.  
  19063. /* ssx_task_delete - delete a task and remove from queues */
  19064.  
  19065. int
  19066. ssx_task_delete(unsigned char task_id)
  19067. {
  19068. unsigned int i;
  19069. tcb *tp;
  19070. int_state_var istate;
  19071.  
  19072. ints_off(istate);
  19073.  
  19074. /* look for background task ID */
  19075. if(task_id == BG_TASK_ID){
  19076. restore_ints(istate);
  19077. return(TID_ERR);
  19078. }
  19079.  
  19080. /* look for 'self' form */
  19081. if(task_id == 0) {
  19082. if(t_current) {
  19083. /* get current task's id */
  19084. task_id = t_current->id;
  19085. }
  19086. }
  19087.  
  19088. /* brute force, search all TCBs for matching ID */
  19089. for(i = 0,tp = t_pool;i < MAX_TASKS;i++,tp++) {
  19090. if(tp->active && tp->id == task_id) {
  19091. break;
  19092.  
  19093. }
  19094. }
  19095.  
  19096. /* see if found match */
  19097. if(i == MAX_TASKS){
  19098. restore_ints(istate);
  19099. return(TID_ERR);
  19100. }
  19101.  
  19102. switch(tp->status & (T_DELAYED T_WAITING)) {
  19103. case T_DELAYED:
  19104. d_unlink(tp); /* remove from delay q */
  19105. break;
  19106. case T_WAITING:
  19107. t_unlink(tp); /* remove from some */
  19108. /* wait_q */
  19109. break;
  19110. case T_DELAYED T_WAITING:
  19111. t_unlink(tp); /* remove from some */
  19112.  
  19113. /* wait_q */
  19114. d_unlink(tp); /* remove from delay q */
  19115. break;
  19116. case T_READY:
  19117. t_unlink(tp); /* remove from ready q */
  19118. break;
  19119. }
  19120.  
  19121. free(tp->stack); /* free allocated stack */
  19122. tp->stack = NULL;
  19123. free_tcb(tp); /* free up the TCB */
  19124.  
  19125. ssx_switch();
  19126. restore_ints(istate);
  19127. return(SUCCESS);
  19128. }
  19129.  
  19130. /* ssx_change_priority - change priority for currently
  19131. * running task,
  19132. * don't immediately reschedule
  19133. * returns old priority */
  19134.  
  19135. unsigned char
  19136. ssx_change_priority(unsigned char new_priority)
  19137. {
  19138. unsigned char old_priority;
  19139. int_state_var istate;
  19140.  
  19141. ints_off(istate);
  19142. old_priority = t_current->priority;
  19143. t_current->priority = new_priority;
  19144. t_unlink(t_current);
  19145. rotate_tasks(t_current);
  19146. restore_ints(istate);
  19147. return(old_priority);
  19148. }
  19149.  
  19150. /* ssx_wait - wait on wait_q. reschedule later */
  19151.  
  19152. void
  19153. ssx_wait(wait_q *wqptr)
  19154. {
  19155.  
  19156. tcb *tp;
  19157. tcb *t_cur;
  19158. int_state_var istate;
  19159.  
  19160. ints_off(istate);
  19161.  
  19162. /* check for message flag already set */
  19163. if(wqptr->mesg_flg){
  19164. wqptr->mesg_flg = FALSE;
  19165. restore_ints(istate);
  19166. return;
  19167. }
  19168.  
  19169. t_cur = t_current;
  19170. t_unlink(t_cur); /* take off ready queue */
  19171.  
  19172.  
  19173. tp = (tcb *)&wqptr->task_ptr;
  19174.  
  19175. /* find where to insert waiting task into
  19176. * wait queue */
  19177. while((tp->forw) != NULL) {
  19178. if(t_cur->priority <= tp->forw->priority)
  19179. break;
  19180. tp = tp->forw;
  19181. }
  19182.  
  19183. /* insert into queue */
  19184. if((t_cur->forw = tp->forw)!= NULL)
  19185. t_cur->forw->back = t_cur;
  19186.  
  19187. tp->forw = t_cur;
  19188. t_cur->back = tp;
  19189. t_cur->status = T_WAITING;
  19190.  
  19191. ssx_switch();
  19192. restore_ints(istate);
  19193. }
  19194.  
  19195. /* ssx_wait_with alarm - wait on wait_q with alarm.
  19196. * reschedule now */
  19197.  
  19198. int
  19199. ssx_wait_with_alarm(wait_q *wqptr,long timeout)
  19200. {
  19201. tcb *tp;
  19202. tcb *t_cur;
  19203. int_state_var istate;
  19204.  
  19205. ints_off(istate);
  19206.  
  19207. /* check for message flag already set */
  19208. if(wqptr->mesg_flg){
  19209. wqptr->mesg_flg = FALSE;
  19210. restore_ints(istate);
  19211. return(SUCCESS);
  19212. }
  19213.  
  19214. t_cur = t_current;
  19215. t_unlink(t_cur); /* take off ready queue */
  19216.  
  19217. tp = (tcb *)&wqptr->task_ptr;
  19218.  
  19219. /* find where to insert waiting task into wait queue */
  19220. while((tp->forw) != NULL) {
  19221. if(t_cur->priority <= tp->forw->priority)
  19222. break;
  19223. tp = tp->forw;
  19224. }
  19225.  
  19226. /* insert into queue */
  19227. if((t_cur->forw = tp->forw) != NULL)
  19228. t_cur->forw->back = t_cur;
  19229. tp->forw = t_cur;
  19230. t_cur->back = tp;
  19231. t_cur->status = T_WAITING;
  19232.  
  19233.  
  19234. /* if there is timeout value, put task on delay queue */
  19235. if(timeout)
  19236. put_delay(timeout);
  19237.  
  19238. ssx_switch();
  19239.  
  19240. /* we were scheduled back in so
  19241. * see if task timed out and return error */
  19242.  
  19243. if(t_cur->timedout){
  19244. t_cur->timedout = FALSE;
  19245. restore_ints(istate);
  19246. return(TO_ERR);
  19247. }
  19248.  
  19249. restore_ints(istate);
  19250.  
  19251. /* task did not time out, so return success */
  19252. return(SUCCESS);
  19253. }
  19254.  
  19255. /* ssx_alert - alert wait_q. reshedule if task
  19256. * alerted is equal or higher
  19257. * priority than current task */
  19258.  
  19259. int
  19260. ssx_alert(wait_q *wqptr)
  19261. {
  19262. tcb *np;
  19263. tcb *oldtcb;
  19264. int_state_var istate;
  19265.  
  19266. ints_off(istate);
  19267.  
  19268. /* check for message waiting */
  19269. if(wqptr->mesg_flg){
  19270. restore_ints(istate); /* cannot alert if */
  19271. return(MW_ERR); /* messgae is waiting */
  19272. }
  19273.  
  19274. np=(tcb *)wqptr->task_ptr;
  19275.  
  19276. /* check if there is a task waiting on wait_q */
  19277. if(np != NULL){
  19278. t_unlink(np);
  19279. if(np->status & T_DELAYED)
  19280. d_unlink(np);
  19281. np->status &= ~(T_WAITING T_DELAYED);
  19282. put_ready(np);
  19283. /* switch to waiting task if it is equal
  19284. * or higher priority */
  19285. if(np->priority <= t_current->priority){
  19286. oldtcb = t_current;
  19287. t_current = np;
  19288. /* check and see if scheduling is disabled */
  19289. if(switch_lock == 0)
  19290. stack_swap(&oldtcb->stack_ptr
  19291. ,&np->stack_ptr);
  19292.  
  19293. }
  19294. restore_ints(istate);
  19295. return(SUCCESS);
  19296. }
  19297.  
  19298. /* fell thru, simply leave message in wait_q */
  19299. wqptr->mesg_flg = TRUE;
  19300. restore_ints(istate);
  19301. return(SUCCESS);
  19302. }
  19303.  
  19304. /* ssx_clock_tick - call this to update ssx clock from
  19305. * timer interrupt handler */
  19306.  
  19307. void
  19308. ssx_clock_tick(void)
  19309. {
  19310. tcb *tp;
  19311. int_state_var istate;
  19312.  
  19313. ints_off(istate);
  19314.  
  19315. if(running == FALSE){
  19316. restore_ints(istate);
  19317. return;
  19318. }
  19319.  
  19320. sys_time++;
  19321.  
  19322. /* do time updates */
  19323. tp = (tcb *)&d_chain;
  19324. /* check for timed out tasks */
  19325. while((tp = tp->dforw) != NULL) {
  19326. if((sys_time - tp->timeout) >= 0) {
  19327. d_unlink(tp); /* this one's ready */
  19328. tp->timedout = TRUE;
  19329. if(tp->status & T_WAITING)
  19330. t_unlink(tp);
  19331. tp->status = T_READY;
  19332. /* put task on ready queue */
  19333. rotate_tasks(tp);
  19334. }
  19335. else
  19336. break; /* passed the ready ones */
  19337. }
  19338.  
  19339. /* round robin rotation */
  19340. if((++slice_cnt) == TIME_SLICE){
  19341. slice_cnt = 0;
  19342. /* if task is running and was left ready */
  19343. if(t_current && t_current->status
  19344. == T_READY){
  19345. /* remove from ready queue */
  19346. t_unlink(t_current);
  19347. /* puts at back of pri group */
  19348. rotate_tasks(t_current);
  19349. }
  19350. }
  19351.  
  19352.  
  19353. ssx_switch();
  19354. restore_ints(istate);
  19355. }
  19356.  
  19357. /* ssx_set_time - this sets SSX system time */
  19358.  
  19359. void
  19360. ssx_set_time(long time)
  19361. {
  19362. int_state_var istate;
  19363.  
  19364. ints_off(istate);
  19365. sys_time = time;
  19366. restore_ints(istate);
  19367. }
  19368.  
  19369. /* ssx_get_time - this returns SSX system time */
  19370.  
  19371. long
  19372. ssx_get_time(void)
  19373. {
  19374. return(sys_time);
  19375. }
  19376.  
  19377. /* ssx_lock - disable task switching */
  19378.  
  19379. void
  19380. ssx_lock(void)
  19381. {
  19382. int_state_var istate;
  19383.  
  19384. ints_off(istate);
  19385. switch_lock++;
  19386. restore_ints(istate);
  19387. }
  19388.  
  19389. /* ssx_unlock - enable task switching */
  19390.  
  19391. void
  19392. ssx_unlock(void)
  19393. {
  19394. int_state_var istate;
  19395.  
  19396. ints_off(istate);
  19397. /* call ssx_switch if we are not nested */
  19398. if(-switch_lock == 0)
  19399. ssx_switch();
  19400. restore_ints(istate);
  19401. }
  19402.  
  19403. /* ssx_switch - run next ready task
  19404. * notes: there must always be a runnable task w/
  19405. * SSX. a background task is created by ssx_init
  19406. * and this task can never wait or do anything
  19407. * that would remove it from the active queue.
  19408. * this saves checks in this routine, making
  19409. * it more efficient. */
  19410.  
  19411. void
  19412.  
  19413. ssx_switch(void)
  19414. {
  19415. tcb *oldtcb;
  19416.  
  19417. if(!running)
  19418. return;
  19419.  
  19420. oldtcb = t_current;
  19421.  
  19422. /* switch tasks */
  19423. t_current = t_ready.forw; /* get next */
  19424. /* ready task */
  19425. /*
  19426. * if new task is same as old, do not
  19427. * bother with switch
  19428. */
  19429. if(t_current == oldtcb)
  19430. return;
  19431.  
  19432. /* check and see if scheduling is disabled */
  19433. if(switch_lock == 0){
  19434. /* we have a new task so do a task switch */
  19435. stack_swap(&oldtcb->stack_ptr,
  19436. &t_current->stack_ptr);
  19437. }
  19438. }
  19439.  
  19440. /* get_tcb - get task control block */
  19441.  
  19442. LOCAL tcb *
  19443. get_tcb(void)
  19444. {
  19445. tcb *tbp;
  19446.  
  19447. if(t_free == NULL)
  19448. return(NULL);
  19449. tbp = t_free;
  19450. t_free = tbp->forw;
  19451. tbp->active = TRUE;
  19452.  
  19453. return(tbp);
  19454. }
  19455.  
  19456. /* free_tcb - free task control block */
  19457.  
  19458. LOCAL void
  19459. free_tcb(tcb *tbp)
  19460. {
  19461. /* '****' for debug TCB not in use */
  19462. tbp->timeout = 0x2a2a2a2aL;
  19463. tbp->active = FALSE;
  19464. tbp->forw = t_free;
  19465. t_free = tbp;
  19466. }
  19467.  
  19468. /* put_ready - put task at head of ready queue */
  19469.  
  19470. LOCAL void
  19471. put_ready(tcb *tbp)
  19472.  
  19473. {
  19474. tcb *tp,*np;
  19475. unsigned int priority;
  19476.  
  19477. /* get priority of task to be inserted in chain */
  19478. priority = tbp->priority;
  19479.  
  19480. /* put on the active chain */
  19481. tp = (tcb *)&t_ready;
  19482. /* sort in order of decreasing priority, put at
  19483. * head of pri group */
  19484. while((np = tp->forw) != NULL && np->priority
  19485. < priority)
  19486. tp = np;
  19487. /* link in */
  19488. tbp->forw = np;
  19489. tbp->back = tp;
  19490. tp->forw = tbp;
  19491. if(np != NULL)
  19492. np->back = tbp;
  19493. }
  19494.  
  19495. /* rotate_tasks - put task at back of ready queue */
  19496.  
  19497. LOCAL void
  19498. rotate_tasks(tcb *tbp)
  19499. {
  19500.  
  19501. tcb *tp,*np;
  19502. unsigned int priority;
  19503.  
  19504. /* get priority of task to be inserted in chain */
  19505. priority = tbp->priority;
  19506.  
  19507. /* put on the active chain */
  19508. tp = (tcb *)&t_ready;
  19509. /* sort in order of decreasing priority,
  19510. * put at back of pri group */
  19511. while((np = tp->forw) != NULL && np->priority
  19512. <= priority)
  19513. tp = np;
  19514. /* link in */
  19515. tbp->forw = np;
  19516. tbp->back = tp;
  19517. tp->forw = tbp;
  19518. if(np != NULL)
  19519. np->back = tbp;
  19520. }
  19521.  
  19522. /* put_delay - put task on delay queue */
  19523.  
  19524. LOCAL void
  19525. put_delay(long timeout)
  19526. {
  19527. tcb *tp,*np;
  19528.  
  19529. t_current->timeout =
  19530. timeout + sys_time; /* actual time ready */
  19531. t_current->timedout=FALSE;
  19532.  
  19533. t_current->status = T_DELAYED;
  19534. tp = (tcb *)&d_chain;
  19535. /* sort in order increasing target time */
  19536. /* trick to solve wrap of sys_time */
  19537. while((np = tp->dforw) != NULL) {
  19538. if(timeout <= np->timeout - sys_time)
  19539. /* hit a more future one */
  19540. break;
  19541. tp = np;
  19542. }
  19543. /* link in */
  19544. t_current->dforw = np;
  19545. t_current->dback = tp;
  19546. tp->dforw = t_current;
  19547. if(np != NULL)
  19548. np->dback = t_current;
  19549. }
  19550.  
  19551. /* run_new_task - starts up a new task making sure
  19552. * interrupts are enabled */
  19553.  
  19554. LOCAL void
  19555. run_new_task(void)
  19556. {
  19557. ints_on();
  19558. (t_current->task_ptr) ();
  19559. }
  19560.  
  19561. /* bg_task - must have a background task */
  19562.  
  19563. LOCAL void
  19564. bg_task(void)
  19565. {
  19566. while(1);
  19567. }
  19568.  
  19569. /* WARNING - routines from here on are not portable */
  19570.  
  19571. /* stack_swap - switch from stack of curent task to
  19572. * stack of new task */
  19573.  
  19574. LOCAL void
  19575. stack_swap(unsigned int **old_stack_ptr,
  19576. unsigned int **new_stack_ptr)
  19577. {
  19578. asm or di,di /* fool compiler into saving */
  19579. asm or si,si /* di and si registers */
  19580.  
  19581. /* save stack pointer of old task from sp reg */
  19582. *old_stack_ptr = (unsigned int *)_SP;
  19583.  
  19584. /* load sp reg with stack pointer of new task */
  19585. _SP=(unsigned int)*new_stack_ptr;
  19586. }
  19587.  
  19588. /* disable_ints - disable interrupts and return state
  19589. * of interrupts before before they
  19590. * were disabled. Returns positive
  19591. * integer if they were enabled */
  19592.  
  19593.  
  19594. LOCAL int
  19595. disable_ints(void)
  19596. {
  19597. asm pushf /* save flags to get */
  19598. /* interupt status */
  19599. asm cli /* interrupts off */
  19600. asm pop ax /* get interrupt state from */
  19601. /* flags that were pushed */
  19602. asm and ax,0200h /* and flags to get interrupt */
  19603. /* status */
  19604. return(_AX);
  19605. }
  19606. /* End of File */
  19607.  
  19608.  
  19609. Listing 2 SSX.H -- definitions for SSX
  19610. /****************************************************
  19611. /* By Tom Green and Dennis Cronin */
  19612. /* 10/19/92 */
  19613. /****************************************************/
  19614.  
  19615. /* function pointer */
  19616. typedef void (*fptr)(void);
  19617.  
  19618. /* this is a wait_q structure */
  19619. typedef struct wait_q{
  19620. void *task_ptr;
  19621. int mesg_flg;
  19622. }wait_q;
  19623.  
  19624. /* SSX prototypes */
  19625. int ssx_init(void);
  19626. void ssx_run(void);
  19627. void ssx_stop(void);
  19628. int ssx_task_create(unsigned char task_pri,
  19629. unsigned char task_id,fptr task_ptr,
  19630. unsigned int stack_size,char *name);
  19631. void ssx_task_delay(long ticks);
  19632. int ssx_task_delete(unsigned char task_id);
  19633. unsigned char ssx_change_priority(unsigned char
  19634. new_priority);
  19635. void ssx_wait(wait_q *wqptr);
  19636. int ssx_wait_with_alarm(wait_q *wqptr,long timeout);
  19637. int ssx_alert(wait_q *wqptr);
  19638. void ssx_clock_tick(void);
  19639. void ssx_set_time(long ticks);
  19640. long ssx_get_time(void);
  19641. void ssx_lock(void);
  19642. void ssx_unlock(void);
  19643. void ssx_switch(void);
  19644.  
  19645. /* SSX status codes */
  19646.  
  19647. #define SUCCESS 0
  19648. /* task ID error */
  19649. #define TID_ERR 1
  19650. /* message waiting error */
  19651. #define MW_ERR 2
  19652.  
  19653. /* no TCBs error */
  19654. #define TCB_ERR 3
  19655. /* could not allocate stack for task */
  19656. #define STACK_ERR 4
  19657. /* task timed out (wait_with_alarm) */
  19658. #define TO_ERR 5
  19659. /* error initializing SSX */
  19660. #define INIT_ERROR 6
  19661.  
  19662. /* initialize semaphore to having waiting message */
  19663. #define SET_SEMAPHORE(wqptr) (wqptr)->mesg_flg=1; \
  19664. (wqptr)->task_ptr=NULL
  19665.  
  19666. /* initialize wait_q to NULL task_ptr and no
  19667. * message waiting */
  19668. #define INIT_WAIT_Q(wqptr) (wqptr)->mesg_flg=0; \
  19669. (wqptr)->task_ptr=NULL
  19670. /* End of File */
  19671.  
  19672.  
  19673. Listing 3 SSX_CONF.H -- configuration info for SSX
  19674. /*****************************************************/
  19675. /* By Tom Green and Dennis Cronin */
  19676. /* 10/19/92 */
  19677. /*****************************************************/
  19678.  
  19679. /* main config params */
  19680.  
  19681. /* maximum number of tasks */
  19682. #define MAX_TASKS 24
  19683. /* 1 tick per slice */
  19684. #define TIME_SLICE 1
  19685. /* number of bytes of context info */
  19686. #define CNTXT_SZ 8
  19687.  
  19688. /* enable interrupts - this will have to be ported
  19689. * in other environments */
  19690. #define enable_ints enable
  19691.  
  19692. /* variable type to keep interrupt status in */
  19693. typedef
  19694. int int_state_var;
  19695. /* save off current state, disable all interrupts */
  19696. #define ints_off(isv) isv=disable_ints()
  19697. /* explicitly enable interrupts */
  19698. #define ints_on() enable_ints()
  19699. /* reload previous interrupt enables */
  19700. #define restore_ints(isv) { if(isv) enable(); }
  19701. /* End of File */
  19702.  
  19703.  
  19704. Listing 4 DEMO.C -- demo code for SSX
  19705. *****************************************************/
  19706. /* By Tom Green and Dennis Cronin */
  19707. /* 10/19/92 */
  19708. /****************************************************/
  19709.  
  19710. #include <stdio.h>
  19711. #include <stdlib.h>
  19712.  
  19713. #include <dos.h>
  19714. #include <bios.h>
  19715. #include <conio.h>
  19716. #include "ssx.h"
  19717.  
  19718. /* vector for PC timer interrupt */
  19719. #define TIMER_INT_VEC 8
  19720. /* 18.2 clock ticks per second on PC */
  19721. #define TICKS_PER_SECOND 18L
  19722.  
  19723. #define NUM_PQ_TASKS 5
  19724. #define NUM_TS_TASKS 5
  19725.  
  19726. void key_task(void);
  19727. void print_ts_count_task(void);
  19728. void sys_time_task(void);
  19729. void ts_task(void);
  19730. void print_q_task(void);
  19731. void interrupt tick_handler(void);
  19732.  
  19733. unsigned int pq_task_counter=0;
  19734. unsigned int ts_task_counter=0;
  19735. wait_q print_q;
  19736. void interrupt (*old_timer_int)(void);
  19737. unsigned long pq_count[NUM_PQ_TASKS];
  19738. unsigned long ts_count[NUM_TS_TASKS];
  19739.  
  19740. void main(void)
  19741.  
  19742. {
  19743. unsigned int i;
  19744.  
  19745. clrscr();
  19746.  
  19747. pq_task_counter=0;
  19748. ts_task_counter=0;
  19749.  
  19750. if((ssx_init()) == INIT_ERROR){
  19751. printf("\nError initting SSX");
  19752. exit(1);
  19753. }
  19754.  
  19755. /* set up timer tick interrupt handler */
  19756. old_timer_int = getvect(TIMER_INT_VEC);
  19757. setvect(TIMER_INT_VEC,tick_handler);
  19758.  
  19759. /* install print q tasks */
  19760. for(i = 1;i <= NUM_PQ_TASKS;i++){
  19761. if((ssx_task_create(1,i,print_q_task,0x200,
  19762. "task"))!=SUCCESS)
  19763. cprintf("\nError creating task %d",i);
  19764. }
  19765. /* install time slice tasks */
  19766. for(i = NUM_PQ_TASKS + 1;i <= (NUM_TS_TASKS +
  19767. NUM_PQ_TASKS);i++){
  19768. if((ssx_task_create(1,i,ts_task,0x200,
  19769. "ts_task"))!=SUCCESS)
  19770. cprintf("\nError creating ts task");
  19771. }
  19772.  
  19773.  
  19774. /* install print ts count task */
  19775. if((ssx_task_create(1,22,print_ts_count_task,0x200,
  19776. "pts_task"))!=SUCCESS)
  19777. cprintf("\nError creating ts count task");
  19778. /* install keypress task */
  19779. if((ssx_task_create(1,21,key_task,0x200,
  19780. "key_task"))!=SUCCESS)
  19781. cprintf("\nError creating keypress task");
  19782. /* install system time task */
  19783. if((ssx_task_create(1,23,sys_time_task,0x200,
  19784. "st_task"))!=SUCCESS)
  19785. cprintf("\nError creating system time task");
  19786. cprintf("\r\nDone creating tasks");
  19787. cprintf("\r\nPress key to start SSX");
  19788. bioskey(0);
  19789. clrscr();
  19790. SET_SEMAPHORE(&print_q);
  19791. ssx_run();
  19792.  
  19793. /* reset to old timer int handler */
  19794. setvect(TIMER_INT_VEC,old_timer_int);
  19795.  
  19796. clrscr();
  19797. }
  19798.  
  19799. /* ts_task - time slice task. this task gets a full
  19800. * time slice to increment counter.
  19801. * print_ts_count_task prints counters. */
  19802.  
  19803. void ts_task(void)
  19804.  
  19805. {
  19806. int task_num;
  19807.  
  19808. task_num = ts_task_counter++;
  19809. ts_count[task_num] = 0L;
  19810.  
  19811. while(1){
  19812. ts_count[task_num]++;
  19813. }
  19814. }
  19815.  
  19816. /* print_q_task - this task waits for print q we have
  19817. * set up and then increments counter
  19818. * and prints and then releases print q */
  19819.  
  19820. void print_q_task(void)
  19821. {
  19822. unsigned int y,task_num;
  19823.  
  19824. task_num = pq_task_counter++;
  19825. y = task_num + 1;
  19826. pq_count[task_num] = 0L;
  19827.  
  19828. while(1){
  19829. ssx_wait(&print_q);
  19830. pq_count[task_num]++;
  19831. gotoxy(1,y);
  19832.  
  19833. cprintf("Task %02d %08lx",task_num,
  19834.  
  19835. pq_count[task_num]);
  19836. ssx_alert(&print_q);
  19837. }
  19838. }
  19839.  
  19840. /* print_ts_count_task - task that prints counts
  19841. * from time sliced tasks
  19842. * once every 3 seconds */
  19843.  
  19844. void print_ts_count_task(void)
  19845. {
  19846. int task_num;
  19847.  
  19848. while(1){
  19849. ssx_wait(&print_q);
  19850. for(task_num = 0;task_num < NUM_TS_TASKS;
  19851. task_num++){
  19852. gotoxy(1,task_num + NUM_TS_TASKS + 1);
  19853. cprintf("TS Task %02d %08lx",task_num,
  19854. ts_count[task_num]);
  19855. }
  19856. ssx_alert(&print_q);
  19857. ssx_task_delay(3 * TICKS_PER_SECOND);
  19858. }
  19859. }
  19860.  
  19861. /* key_task - task that checks for keypress every
  19862. * 2 seconds. calls ssx_stop when key
  19863. * is pressed */
  19864.  
  19865. void key_task(void)
  19866. {
  19867. while(1){
  19868. if((bioskey(1)) != 0){
  19869. bioskey(0);
  19870. ssx_stop();
  19871. }
  19872. ssx_task_delay(2 * TICKS_PER_SECOND);
  19873. }
  19874. }
  19875.  
  19876. /* sys_time_task - prints how long it has been
  19877. * running once every minute */
  19878.  
  19879. void sys_time_task(void)
  19880. {
  19881. int task_num;
  19882.  
  19883. while(1){
  19884. ssx_wait(&print_q);
  19885. gotoxy(1,15);
  19886. cprintf("Sytem time = %08lx seconds",
  19887. ssx_get_time()/ TICKS_PER_SECOND);
  19888. ssx_alert(&print_q);
  19889. ssx_task_delay(1 * TICKS_PER_SECOND);
  19890. }
  19891. }
  19892.  
  19893.  
  19894. /* tick_handler - handles 18.2 per second timer
  19895. * interrupts on PC */
  19896.  
  19897. void interrupt tick_handler(void)
  19898. {
  19899. /* call original PC timer handler */
  19900. (*old_timer_int)();
  19901. /* inform SSX of clock tick */
  19902. ssx_clock_tick();
  19903. }
  19904. /* End of File */
  19905.  
  19906.  
  19907.  
  19908.  
  19909.  
  19910.  
  19911.  
  19912.  
  19913.  
  19914.  
  19915.  
  19916.  
  19917.  
  19918.  
  19919.  
  19920.  
  19921.  
  19922.  
  19923.  
  19924.  
  19925.  
  19926.  
  19927.  
  19928.  
  19929.  
  19930.  
  19931.  
  19932.  
  19933.  
  19934.  
  19935.  
  19936.  
  19937.  
  19938.  
  19939.  
  19940.  
  19941.  
  19942.  
  19943.  
  19944.  
  19945.  
  19946.  
  19947.  
  19948.  
  19949.  
  19950.  
  19951.  
  19952.  
  19953.  
  19954.  
  19955.  
  19956. Developing 80x86-Based Embedded Systems
  19957.  
  19958.  
  19959. Andrew P. Beck
  19960.  
  19961.  
  19962. Andrew Beck is a systems engineer specializing in the design of computers for
  19963. industrial and scientific applications. He has been designing embedded systems
  19964. more than 15 years. Andrew can be reached at (908) 806-8262.
  19965.  
  19966.  
  19967.  
  19968.  
  19969. Introduction
  19970.  
  19971.  
  19972. Embedded systems were once programmed only by those brave souls who to dared
  19973. write in assembly language and who knew enough about the hardware to program
  19974. to the chip level. But as the embedded systems market continues to expand, and
  19975. the need for embedded systems programmers grows, all of that has changed.
  19976. Most manufacturers are reducing the size of their products. With the shortage
  19977. of experienced embedded systems programmers, more MS-DOS programmers will be
  19978. required to develop embedded programs. In this article, I will try to ease the
  19979. path for those of you who have never developed an embedded system. I address
  19980. the fundamental issues of developing a program to run on an 80x86-based
  19981. system. I explain the steps necessary to convert your C programs into embedded
  19982. code, And I give you some hints on how to avoid some of the common pitfalls of
  19983. embedded systems design.
  19984.  
  19985.  
  19986. A Little History
  19987.  
  19988.  
  19989. In 1978 Intel developed the 8086 microprocessor. Due primarily to IBM's use of
  19990. it in their first PC, the 80x86 family has become the industry's most popular
  19991. microprocessor. There are literally millions of 80x86-based computer systems
  19992. in operation today. Because of the PC's popularity, scores of lowcost,
  19993. high-quality software development tools have been created for it.
  19994. Soon after the introduction of the 8086, embedded systems designers started
  19995. developing new products around it. These early designers had to rely on
  19996. high-priced development systems to help them refine their designs and develop
  19997. their software. This, along with the large number of support chips required
  19998. for even a modest 8086 design, prevented many designers from using the 8086 in
  19999. their embedded designs. Only those who could justify the high initial
  20000. development cost and long development cycle could afford to use it.
  20001. Intel soon realized that embedded systems offered a potentially large market,
  20002. so in 1982 they announced the 80186 microcontroller. The 80186 is a
  20003. highly-integrated controller that includes some of the most often used
  20004. peripherals on the same chip as the microprocessor. In the past couple of
  20005. years there has been a dramatic increase in the number of new designs
  20006. utilizing this controller. In addition to Intel, NEC and AMD currently produce
  20007. microcontrollers that are code compatible with the 8086.
  20008. Users of all types of equipment, from consumer electronics to high-end
  20009. instrumentation, are demanding ever more intelligent devices. Manufacturers
  20010. are continuing to put more powerful systems into ever smaller boxes. One
  20011. market that has seen a tremendous growth in the last few years is the field of
  20012. industrial instrumentation and automation. The typical handheld instrument of
  20013. today is required to outperform desktop systems produced just a few years ago.
  20014. In addition, manufacturers are striving to make these systems easier to use,
  20015. while introducing new products faster than ever.
  20016. All of these factors have combined to force the embedded systems designer to
  20017. look for tools to help produce better products in a shorter period of time.
  20018. One of the most powerful tools currently available is the C programming
  20019. language. C allows the designer to develop programs faster, and with fewer
  20020. bugs, than assembly language. C programs can be better structured, and
  20021. therefore more portable and maintainable, than their assembly-language
  20022. counterparts.
  20023. A few years ago, many designers were reluctant to use C in their designs. They
  20024. felt the language used too much memory. It was often difficult to write
  20025. interrupt handlers in C, and the designer had little control over placement of
  20026. data or code. All of this has changed. Today's C compilers offer a high level
  20027. of optimization. In large complex programs, the C implementation is often no
  20028. larger than the assembly version. Most compilers provide support for
  20029. generating interrupt function completely in C.
  20030.  
  20031.  
  20032. What is an Embedded System?
  20033.  
  20034.  
  20035. An embedded system generally consists of a microprocessor and a few
  20036. peripherals that run a dedicated program. That program is usually stored in
  20037. non-volatile memory, such as PROM. Embedded systems are not programmable by
  20038. the end user, and usually have very limited I/O resources. While many embedded
  20039. systems are designed with off-the-shelf components, the majority of them are
  20040. based on custom-designed hardware.
  20041. Since many embedded systems do not run under an operating system, the
  20042. programmer is responsible for supplying all of the low-level I/O functions.
  20043. And unlike MS-DOS-based programs that can be loaded from disk, embedded
  20044. systems require you to load the program into PROM. Consequently, developing
  20045. and debugging these systems presents some unique challenges.
  20046.  
  20047.  
  20048. Embedded Systems Development Tools
  20049.  
  20050.  
  20051. Due to the popularity of the PC, there are a number of low-cost, high-quality
  20052. development tools available for the 80x86 processors. Two of the most popular
  20053. C compilers for the PC are produced by Borland and Microsoft. Because of their
  20054. popularity, these compilers have led to the development of many third-party
  20055. design tools that support embedded systems.
  20056. To create an 80x86-based embedded system, you will need a standard
  20057. MS-DOS-based assembler, compiler, and linker. You will also need a locate
  20058. program. This program converts a standard MS-DOS .exe file into a form that
  20059. can be burned into PROM, typically a hex or binary file.
  20060. If this is your first embedded system design, I'd recommend that you purchase
  20061. a third-party embedded-systems development package. In addition to the locate
  20062. program, the vendor will supply the requisite startup code and runtime support
  20063. functions for your compiler. Most vendors will also supply their own
  20064. libraries, or a set of utilities to remove non-ROMable functions from your
  20065. compiler's library.
  20066.  
  20067.  
  20068. Basic Principles
  20069.  
  20070.  
  20071. Programs written for MS-DOS-based systems run in the system's RAM. When a
  20072. program is executed, the program loader locates an available block of memory
  20073. and loads the program into it. MS-DOS .exe files are simply relocatable files.
  20074. As the program is loaded, the program loader uses a table stored in the file
  20075. to resolve the relocatable segment addresses.
  20076. Embedded systems require two types of memory, volatile RAM and non-volatile
  20077. PROM (or ROM). All of the program's variables, as well as the stack, are
  20078. located in RAM. The executable portion of the program is stored in PROM.
  20079. Constants, such as string data and initialized variables, are initially stored
  20080. in the PROM. However, before the program can be executed, this data must be
  20081. copied to RAM. This is the responsibility of the system's startup code. This
  20082. code is also responsible for initializing the segment registers, clearing the
  20083. uninitiliazed RAM, and setting up the heap. Finally the startup code must call
  20084. the program's main function.
  20085. The startup code is the most important piece of software in the system. It is
  20086. also the most difficult for the first-time embedded systems programmer to
  20087. develop. If you purchase a third-party embedded development package, the
  20088. vendor will supply the requisite startup code.
  20089. When the 80x86 processor begins running after reset, it executes the code
  20090. located at 0xffff:0xfff0. Normally the only instruction at this location is a
  20091. jump to a lower address in PROM, where the actual program is stored. The first
  20092. piece of code to be executed after the initial jump is the startup code. The
  20093. startup code is responsible for establishing an environment that the C program
  20094. can run in, consequently it is always written in assembly.
  20095. All embedded C programs require that at least three segments be defined. The
  20096. code segment contains executable instructions--your program. The data segment
  20097. contains all of the program's static variables. Finally, the stack segment is
  20098. where non-static variables, passed arguments, and function return addresses
  20099. are stored. Depending on the memory model used, some programs will also
  20100. require a far data segment. Some advanced applications may make use of
  20101. multiple code, data, or stack segments.
  20102. Since your programs, constants, and initialized data will be stored in PROM,
  20103. the startup code must copy them into RAM. It must also zero out the
  20104. uninitialized data area so that all other static variables will be initialized
  20105. to zero. Finally it has to setup all of the segment registers and the stack
  20106. pointer.
  20107. Most of the time the startup code will also be responsible for setting the
  20108. system's interrupt vectors. Interrupts 0 through 6 are special interrupts
  20109. generated by the CPU. Interrupts 0, 4, 5, 6 and 7 are processor exception
  20110. interrupts. They should point to an exception-handler function. Interrupt 2 is
  20111. the nonmaskable interrupt (NMI). If your hardware utilizes the NMI, its vector
  20112. must be initialized to your NMI handler's address.
  20113. If you are using a microcontroller, its on-board peripherals will use several
  20114. other interrupts. If you operate them in the interrupt mode, you must
  20115. initialize their vectors. The remaining interrupts are available for your
  20116. application. All unused interrupts should be initialized to point to a dummy
  20117. function that simply performs a return. This prevents an errant interrupt from
  20118. crashing the system.
  20119. In some systems, the startup code may also be responsible for initializing
  20120. some, or all, of the system's hardware. Once the initialization is complete,
  20121. the startup code calls the main function. Command-line arguments can be
  20122. emulated by pushing data onto the stack before main is called. These arguments
  20123. can then be accessed via the standard argv and argc variables. Typically,
  20124. these would be used to indicate the status of the system's setup switches, or
  20125. some other hardware interface.
  20126.  
  20127.  
  20128.  
  20129. Memory Considerations
  20130.  
  20131.  
  20132. Since constants are stored in PROM and then copied to RAM, you pay a penalty
  20133. in terms of memory usage. Constants require twice as much memory in embedded
  20134. systems as they do in MS-DOS-based system. Careful design can reduce this
  20135. penalty. One way to reduce memory usage is by eliminating storage of duplicate
  20136. constants. Both the Turbo-C and Microsoft compilers can be instructed to merge
  20137. duplicate string data. However, they can eliminate duplicate strings only
  20138. within a single program module. If you use a lot of strings, careful attention
  20139. to where and how you display them can help you conserve your system's precious
  20140. memory.
  20141. You can place all of your display functions, along with their associated
  20142. strings, in one module. Or you can simply place all of your text in one module
  20143. and reference it via pointers. Consider how your strings are constructed. If
  20144. you use a lot of common substrings, you may want to break them into separate
  20145. strings so that the compiler can eliminate duplicates for you.
  20146. For example, consider an application in which you need to display three labels
  20147. across the bottom of your screen. The text of each label varies depending on
  20148. which mode the system is in. You could simply generate separate print
  20149. statements for each set of labels as shown below:
  20150. printf("DOWN RUN UP");
  20151. printf("LEFT RUN RIGHT");
  20152. printf(" RUN STOP");
  20153. However, if you had several dozen statements like these, you would find that
  20154. you had a lot of duplicate substrings. In that case a better approach would be
  20155. to create pointers to each substring as depicted below. You could then direct
  20156. the compiler to merge duplicate strings, and save a considerable amount of
  20157. memory.
  20158. printf("%s %s %s", "DOWN ", " RUN ", " UP");
  20159. printf("%s %s %s", "LEFT ", " RUN ", "RIGHT");
  20160. printf("%s %s %s", " ", " RUN ", " STOP");
  20161. Another technique for conserving memory is to place all of your strings into
  20162. their own far data segment and reference them via far pointers. This technique
  20163. does not need the data to be copied to RAM. Unfortunately the Turbo-C compiler
  20164. does not support the printf far string pointer argument (%Fs), so this
  20165. technique is only valid if you are using the Microsoft compiler.
  20166. printf("%Fs %Fs %Fs", down_text, run_text, up_text);
  20167. printf("%Fs %Fs %Fs", left_text, run_text, right_text);
  20168. printf("%Fs %Fs %Fs", blank_text, run_text, stop_text);
  20169.  
  20170.  
  20171. Building an Application
  20172.  
  20173.  
  20174. You can program the bulk of your embedded program just as you would an
  20175. MS-DOS-based program. However, you must avoid using any library functions that
  20176. are not ROMable. The ROMability of functions varies tremendously from library
  20177. to library. If you are using an embedded-system development package, the
  20178. vendor will indicate which functions can be ROMed.
  20179. Many of the low-level I/O functions in MS-DOS, such as putch and getch,
  20180. interface to the hardware via MS-DOS's interrupt 21 handler. High-level
  20181. functions, such as printf and scanf, use this same interrupt. If you emulate
  20182. the MS-DOS interrupt 21 handler you can use most of your library's standard
  20183. I/O functions. Except for the disk-I/O functions, all of Turbo C's I/O
  20184. functions are fully-ROMable if you supply an interrupt 21 handler. If you use
  20185. your library's standard functions, instead of developing your own, your
  20186. development time will be reduced and your program will be more portable.
  20187. If your development package supports it, you can also include floating-point
  20188. math in your application. You will have to supply a runtime interface to the
  20189. floating-point emulator, or coprocessor, as well as a floating-point exception
  20190. handler. Most embedded-systems development packages supply the requisite code.
  20191. If you are developing your own embedded support functions, you have to make
  20192. sure you don't inadvertently use a library function that is MS-DOS dependent.
  20193. The library that comes with Borland's Turbo C compiler tends to be more
  20194. independent of MS-DOS than does Microsoft's. For example, if you supply
  20195. emulation for MS-DOS's interrupt 21 console I/O functions, you can use Turbo
  20196. C's printf function without any other support. On the other hand, Microsoft's
  20197. printf depends on several low-level MS-DOS functions and cannot be embedded
  20198. unless you supply emulations of them. If you are using Microsoft's C, refer to
  20199. their C Compiler User's Guide for a list of the functions that are MS-DOS
  20200. independent. Both Microsoft and Borland will sell you the source code for most
  20201. of their library functions, the exceptions being their math and graphics
  20202. libraries.
  20203.  
  20204.  
  20205. Compiling, Linking, and Locating
  20206.  
  20207.  
  20208. Once you've written your program, you can compile and link it just as you
  20209. would any MS-DOS-based program. The only difference is that you will have to
  20210. link in your startup code in place of the compiler's. You will also have to
  20211. link in any special runtime support functions that are required by your
  20212. system. Make sure that your startup code is the first module listed in the
  20213. link list, this ensures that it is the first code executed. You must also
  20214. instruct your linker to create a map file. This will be used by the locate
  20215. program to aid in determining where code and data are to be placed in your
  20216. target system.
  20217. The output file produced by your linker will be an .exe file, a relocatable
  20218. image of your program. This file contains two parts, a relocation header and
  20219. the actual program data and code. The relocation header consists of a series
  20220. of offsets into the program that point to segment references. The locate
  20221. program must modify these references so that they match the memory layout of
  20222. your target system. The locate program will use the link map to determine the
  20223. length and location of each of the segments contained in the .exe file.
  20224. As a final step the locate program must output your program in a form that can
  20225. be downloaded into a test system or burned into PROM. Usually this will be a
  20226. hex or binary file as dictated by the needs of your PROM programmer, emulator,
  20227. or debugger. The actual locate procedure will vary somewhat depending on your
  20228. locate program. Most of them require you to create a file that contains a
  20229. memory map of the target system, along with directives indicating where each
  20230. of the segments is to be located. You must also indicate which segments are to
  20231. be duplicated in PROM. Typically this will consist of only the segment that
  20232. contains your initialized data.
  20233.  
  20234.  
  20235. Debugging
  20236.  
  20237.  
  20238. Embedded systems present their own unique problems when it comes to debugging.
  20239. If you simply burned your program into PROM and ran it, it would be virtually
  20240. impossible to determine where a problem existed when it didn't run properly.
  20241. Most embedded systems have very limited I/O capability--often nothing more
  20242. than a DIP switch and a few LEDs. These would prove useless if you were trying
  20243. to debug anything more than the simplest of programs.
  20244. Traditionally embedded systems designers have used an incircuit emulator
  20245. (ICE), or a dedicated development system to debug their designs. While
  20246. development systems provide the most integrated development environment, they
  20247. tend to be very expensive, often costing upwards of $20,000. Many lowcost
  20248. emulators are available, and they are fine for developing hardware, but most
  20249. fall short when it comes to debugging complicated programs.
  20250. If you've opted to use your compiler's libraries, and you've carefully
  20251. designed and structured your program, you can test a large percentage of your
  20252. code right on your PC. You can generate test functions that emulate your
  20253. target system's low-level I/O routines. These routines can make calls to
  20254. MS-DOS to display data on the PC's screen and get input from the keyboard. If
  20255. you've emulated the MS-DOS interrupt 21 handler in your program you may not
  20256. need to generate any low-level test functions.
  20257. Perhaps's the best debugging environment for embedded systems is one with
  20258. which most programmer's are already familiar--the source-level debugger. Most
  20259. MS-DOS programmers have, at one time or another, debugged their applications
  20260. with either Borland's Turbo Debugger or Microsoft's Codeview. Both of these
  20261. products provide a host of options for debugging your program. They allow you
  20262. view your source code in its native form, including comments. Both support
  20263. multiple breakpoints and allow you to single step your program. They will also
  20264. display the contents of memory as well as the CPU registers. However, their
  20265. most powerful feature is their ability to display the values of complex data
  20266. types such as arrays, structures, and unions.
  20267. Several vendors offer a version of Borland's Turbo Debugger for use on
  20268. embedded systems. By utilizing the debugger's remote mode you can download and
  20269. debug your program on your target system.
  20270. Some vendors offer their own remote debuggers. Like Turbo Debugger, most of
  20271. these communicate with your target system via an RS232 serial link. Some, such
  20272. as Paradigm's DEBUG/RT, can be configured to communicate with any type of
  20273. interface, including parallel ports and PROM emulators.
  20274. With the power of these debuggers available to you, there is no reason to even
  20275. test your code on the PC. You can perform all of your testing and debugging
  20276. right on your target system. This will help you uncover subtle problems,
  20277. especially timing-related ones, early in the development cycle.
  20278.  
  20279.  
  20280. Special Considerations
  20281.  
  20282.  
  20283. Embedded systems are often required to maintain some of their data even when
  20284. their power has been turned off. This may be data collected and stored for
  20285. later retrieval, or simply setup information such as the configuration of its
  20286. peripherals. It's relatively easy to provide this ability in hardware. The
  20287. most popular way is by equipping the system with battery-backed RAM. While
  20288. system power is turned off, a small battery applies enough voltage to the RAM
  20289. for it to maintain its data.
  20290. From a programmer's standpoint, non-volatile data presents something of a
  20291. problem. You already know that at reset the startup code copies over any
  20292. initialized variables and zeros out the uninitialized area. This will
  20293. reinitialize all data stored in your data segment. So you have to find a way
  20294. to protect your non-volatile data from the startup code.
  20295. The simplest method would be eliminate the code that zeros out the
  20296. uninitialized data area. This presents its own set of problems. If you didn't
  20297. zero out this area you couldn't depend on uninitialized static variables to be
  20298. zero. An unwary maintenance programmer who wasn't aware of this particular
  20299. peculiarity could easily be tripped up by this.
  20300. The best way to solve this problem is to create a segment for all of your
  20301. non-volatile variables. Since they're not included in either the initialized
  20302. or uninitialized data areas, the startup code will not modify them.
  20303. You still face one other problem though. If your program depends on any of
  20304. them being in a known state at reset time, and the startup code isn't
  20305. modifying them, how do they every get initialized in the first place? Again a
  20306. simple solution is at hand. You must provide a function that sets all of these
  20307. variables to default values. Then in the startup code, or at the beginning of
  20308. main, you call this function if some predefined condition exists. For example,
  20309. you could generate a checksum for all of the non-volatile variables, and call
  20310. your reset function if the checksum is invalid. Or you can test for some
  20311. user-generated condition such as a certain switch setting or keyboard input.
  20312.  
  20313.  
  20314.  
  20315. Performance Issues
  20316.  
  20317.  
  20318. The first time you run your embedded application you may be disappointed with
  20319. its performance. Often your program will simply not run as fast as expected.
  20320. This problem is particularly prevalent if you test and debug your program on
  20321. the PC. Most embedded systems run much slower than today's PCs. Testing your
  20322. program on the target system is one way to identify performance problems
  20323. early, before they overwhelm the project.
  20324. Most PCs are so fast that many MS-DOS programmers do not pay much attention to
  20325. the speed issues that were a common concern just a couple of years ago.
  20326. Embedded systems programming still requires that extra attention. Careful
  20327. attention to selection and implementation of algorithms is paramount.
  20328. One of the simplest ways to get a little more performance out of your system
  20329. is to set a couple of your compiler's command-line switches. If you're using
  20330. the Intel 80186 microprocessor, set the switch that enables code generation
  20331. for it. The 80186 includes several instructions, not included in the 8086,
  20332. that can increase the speed of interrupts and function calls. If you have
  20333. plenty of PROM in your system, set your compiler to optimize for speed,
  20334. instead of for code size.
  20335. One area that causes a lot of problems for embedded systems programmers is the
  20336. use of floating-point math. Most embedded systems lack a math coprocessor, so
  20337. you'll have to depend on the floating-point emulator in your math library.
  20338. Even with a highly-optimized, floating-point library, floating-point math is
  20339. much slower than integer math. Many embedded applications don't require the
  20340. range of numbers that floating-point supports. Often fixed-point numbers will
  20341. offer more range and precision than you need. If addition to the improvement
  20342. in speed, switching from floating-point to fixed-point math can save a
  20343. considerable amount of RAM.
  20344. One of the biggest issues in the design of embedded systems is the tradeoff of
  20345. hardware versus software. Often hardware designers will opt to eliminate a
  20346. simple circuit that can be emulated in software. With the rising cost of
  20347. software development, these tradeoffs have to be considered carefully. In
  20348. low-volume applications, the software development cost may far exceed the
  20349. savings in hardware cost.
  20350. If your application makes heavy demands on the processor, one or two simple
  20351. circuits may significantly improve overall system performance. One example of
  20352. a common input device that is often emulated in software is the keyboard
  20353. controller. Often it will be implemented by simply connecting a few switches
  20354. to one of the system's input ports. The software must then periodically poll
  20355. the status of the switches to determine if any has been pressed. The software
  20356. must also determine if it is a valid switch closure or simply "switch bounce"
  20357. from the last key press. In simple systems this rarely taxes the processor.
  20358. In many real-time systems, however, the processor may service several I/O
  20359. devices. It may not be able to spend the time necessary to poll and debounce
  20360. the keyboard. In this case a simple hardware circuit can be used to decode and
  20361. debounce the keyboard and interrupt the processor only when a valid key press
  20362. occurs. Obviously, the better your understanding of the hardware, the easier
  20363. it will be for you to spot potential problems and inform the hardware
  20364. designers before the design is finalized.
  20365.  
  20366.  
  20367. Conclusion
  20368.  
  20369.  
  20370. Developing software for an embedded system is different than for a
  20371. MS-DOS-based system, but it isn't necessarily harder. In this article I've
  20372. only skimmed the surface of embedded systems programming. There are many
  20373. issues that are beyond the scope of an introductory text. Before embarking on
  20374. your first design, I'd recommend that you develop a good understanding of your
  20375. target hardware platform. Also look carefully at the development tools that
  20376. are available, and consider which best meet your needs and budget.
  20377.  
  20378.  
  20379.  
  20380.  
  20381.  
  20382.  
  20383.  
  20384.  
  20385.  
  20386.  
  20387.  
  20388.  
  20389.  
  20390.  
  20391.  
  20392.  
  20393.  
  20394.  
  20395.  
  20396.  
  20397.  
  20398.  
  20399.  
  20400.  
  20401.  
  20402.  
  20403.  
  20404.  
  20405.  
  20406.  
  20407.  
  20408.  
  20409.  
  20410.  
  20411.  
  20412.  
  20413.  
  20414.  
  20415.  
  20416.  
  20417.  
  20418.  
  20419.  
  20420.  
  20421.  
  20422.  
  20423. Three-Dimensional Modeling Under Windows 3.1
  20424.  
  20425.  
  20426. Thomas W. Olsen
  20427.  
  20428.  
  20429. Thomas writes a variety of Windows and DOS software for a major insurance
  20430. company. He can be reached on the CompuServe Information Service at
  20431. (76450,1767).
  20432.  
  20433.  
  20434. Nothing communicates an idea better than a picture. It's the defining
  20435. principle behind the popularity of Microsoft Windows and other graphical
  20436. environments. Not long ago, though, you might recall serious debate over
  20437. whether Windows would ever meet the performance demands of a graphical user
  20438. interface. Much of the trepidation resulted, no doubt, from the tremendous
  20439. freedom of MS-DOS programs to directly access video hardware. However, faster
  20440. CPUs, accelerated video adapters, local bus connections, multimedia, and a
  20441. mature Graphical Display Interface (GDI) have since conspired in favor of
  20442. Windows.
  20443. These changes pose significant opportunities in 3-D modeling software
  20444. development. Three-dimensional modeling has become increasingly important over
  20445. time because it mimics elements in the real world. This article focuses on
  20446. simple strategies for 3-D modeling. The accompanying source code was compiled
  20447. and linked with the Microsoft C/C++ Optimizing Compiler Version 7.0 and
  20448. Microsoft Windows 3.1 Software Developer Kit (SDK). Every effort has been made
  20449. to ensure compatibility with other compilers. Compile instructions are
  20450. provided in comments at the top of each listing. The bitmaps were created with
  20451. Microsoft Paintbrush.
  20452. This article barely scratches the surface of 3-D graphics. Rendering, shading,
  20453. ray tracing, and texture mapping get a more thorough treatment from the
  20454. references in the bibliography. This article will present a building block for
  20455. such advanced features.
  20456. Do not confuse 3-D modeling with Windows metafiles. A metafile is a
  20457. binary-encoded collection of GDI function calls. You create one by sending the
  20458. output from various GDI function calls to a special device context. A recorded
  20459. metafile can be played back by passing its associated resource handle to
  20460. PlayMetaFile. The resulting picture may look three-dimensional but it's just a
  20461. two-dimensional facade.
  20462.  
  20463.  
  20464. Vector Graphics
  20465.  
  20466.  
  20467. Microsoft Windows derives its characteristic look and feel primarily from
  20468. raster-based or bitmapped graphics technology. Most fonts, controls, and icons
  20469. are nothing more than bitmaps--two-dimensional blocks of pixels--that appear
  20470. three-dimensional due to varying color gradients. Bitmaps look great but
  20471. generally experience some kind of image distortion when rotated, stretched, or
  20472. scaled onto devices of varying resolutions. To address such deficiencies
  20473. Microsoft incorporated so-called TrueType font technology into Windows 3.1.
  20474. TrueType is a form of vector-based graphics, in which images are constructed
  20475. with individual graphical primitives such as lines, points, rectangles,
  20476. ellipses, and curves rather than bitmaps. Vector graphics are scalable,
  20477. require less memory than bitmaps, and enable us to model three-dimensional
  20478. objects with relative ease.
  20479.  
  20480.  
  20481. Coordinate Systems
  20482.  
  20483.  
  20484. The Windows GDI does not contain explicit support for 3-D modeling, but it is
  20485. not difficult to build a suitable framework atop existing primitive functions.
  20486. Nearly any 3-D object can be drawn with a set of interconnected points or
  20487. vertices. For example, a simple cube has eight vertices (one for each corner)
  20488. while the General Dynamics F16 Falcon aircraft in 3D.C in Listing 1 (along
  20489. with the files in Listing 2 and Listing 3) contains literally hundreds of
  20490. vertices. Of course, a collection of points is worthless without some frame of
  20491. reference, so they are placed in a domain called the World Coordinate System
  20492. (WCS). You can change the object's orientation in WCS by multiplying each
  20493. vertex by a series of transformation matrices. There are distinct
  20494. transformation matrices for rotation, reflection, shearing, scaling, and
  20495. translation operations, respectively.
  20496. Try to imagine yourself floating in space around a motionless object. As you
  20497. change position, each WCS vertex is transformed with respect to an Eye
  20498. Coordinate System (ECS) that emanates from your eye and points toward the
  20499. object (Figure 1). To complicate matters even more, the resulting ECS vertices
  20500. must be transformed from three-dimensional to two-dimensional screen
  20501. coordinates (SCS) before the object can be displayed. Once these screen
  20502. positions are known, you can connect-the-dots to produce a transparent
  20503. wireframe model, or use filled polygons for a more realistic solid model
  20504. (Figure 2). Three-dimensional objects are drawn one surface at a time, so
  20505. vertices are generally grouped in that order.
  20506.  
  20507.  
  20508. Hidden-Surface Removal
  20509.  
  20510.  
  20511. Hidden-surface removal is one of the more complicated and
  20512. computation-in-tensive facets of 3-D modeling. The most popular method of
  20513. hidden-surface removal is called backplane elimination. Basically, it involves
  20514. computing whether a normal (perpendicular) vector emanating from a given
  20515. surface points away from or toward the viewer's line of sight. Those surfaces
  20516. facing away from the viewer cannot be seen and, therefore, need not be drawn.
  20517. It does have its drawbacks, too. Objects with irregular or overlapping
  20518. surfaces will not be drawn properly. One quick-and-dirty solution is to sort
  20519. the surfaces in terms of decreasing distance from the viewer (ECS
  20520. z-coordinate). Surfaces lying farther away from the viewer are drawn first and
  20521. subsequently masked by closer surfaces. Depth sorting has its flaws but
  20522. performance-conscious applications usually don't mind.
  20523.  
  20524.  
  20525. Constructing Models
  20526.  
  20527.  
  20528. 3D.C (Listing 1) supports both wireframe and solid models. It first creates a
  20529. window with horizontal and vertical scroll bars, and uses SetScrollRange to
  20530. lock the thumb between 0 and 360 degrees. Depressing the scroll bars changes
  20531. the angular position of the viewer relative to the object. This is especially
  20532. convenient because the viewer's position is given in spherical coordinates
  20533. (distance, theta, phi). All measurements are given in device units. The F16
  20534. object "database" has been optimized to keep code size to a minimum.
  20535. 3D.C calls DrawObject whenever the viewer depresses the scroll bars or resizes
  20536. the window. DrawObject determines the center point of the window and creates a
  20537. compatible work bitmap. Using an intermediate bitmap prevents the flashing
  20538. effects that occur while drawing straight to a display context. DrawObject
  20539. also precalculates sine and cosine values for global variables theta and phi.
  20540. These values are needed when f16.vertex[].world vertices are transformed to
  20541. f16.vertex[].eye vertices and, finally, to f16.vertex[].screen coordinates.
  20542. DrawObject clears the work bitmap and loops through each surface in the
  20543. f16.info[] array. For solid models, DrawObject performs a depth sort on the
  20544. f16.vertex[].eye vertices, removes hidden surfaces with backplane elimination,
  20545. selects a brush color from f16.info[].brushColor, and calls Polygon;
  20546. otherwise, it calls PolyLine for a wire-frame surface. DrawObject then updates
  20547. the client area with the completed work bitmap and deletes unused resources.
  20548.  
  20549.  
  20550. Computations
  20551.  
  20552.  
  20553. Floating-point computations incur a considerable amount of overhead--even with
  20554. a math coprocessor installed--but you can improve performance significantly by
  20555. substituting fixed-point integers for floating-point numbers. Fixed-point
  20556. integers incorporate both the whole and fractional components of
  20557. floating-point numbers but can be manipulated in single arithmetic operations,
  20558. such as IMUL and IDIV. For example, it is possible to represent the
  20559. floating-point number 12.345 with integer 12345 by shifting the decimal place
  20560. three positions. There are certain problems with this technique, as well.
  20561. Integers can only represent so many digits before overflowing, so you must
  20562. strike a balance between the scale and precision of the 3-D model.
  20563. NASA's Jet Propulsion Laboratories unveiled a computer-generated film several
  20564. months ago that depicted the surface topography of some distant planet. JPL
  20565. had downloaded countless bits of radar imaging data from a distant probe into
  20566. three Cray super computers and rendered the surreal landscape frame-by-frame
  20567. over the course of three solid weeks. The resulting bitmaps were finally
  20568. transferred to videotape and made available for public consumption. There's a
  20569. lesson hiding behind this madness. Even though real-time 3-D graphics were out
  20570. of the question, JPL eventually got what it paid for by blending vector and
  20571. raster technologies.
  20572. References
  20573. Adams, Lee. 1986. High-Performance CAD Graphics in C. Blue Ridge Summit, PA:
  20574. Windcrest/Tab.
  20575. Microsoft Corp. 1991. Microsoft Windows Multimedia Authoring and Tools Guide.
  20576. Redmond, WA: Microsoft Press.
  20577. Microsoft Corp. 1991. Microsoft Windows Multimedia Programmer's Reference.
  20578. Redmond, WA: Microsoft Press.
  20579. Microsoft Corp. 1991. Microsoft Windows Multimedia Programmer's Workbook.
  20580. Redmond, WA: Microsoft Press.
  20581. Park, Chan S. 1985. Interactive Microcomputer Graphics. Reading, MA:
  20582. Addison-Wesley Publishing Company.
  20583. Petzold, Charles. 1990. Programming Windows. Redmond, WA: Microsoft Press.
  20584. Wilton, Richard. 1987. Programmer's Guide to PC & PS/2 Video Systems. Redmond,
  20585. WA: Microsoft Press.
  20586. Figure 1 The Viewing Transformation
  20587. Figure 2 Wire-Frame and Solid Models
  20588.  
  20589.  
  20590. Listing 1 3d.c -- creates a General Dynamics F16 Falcon aircraft
  20591. #include "windows.h"
  20592. #include <math.h>
  20593. #include <stdlib.h>
  20594.  
  20595. //******************************************************************
  20596. //Title: 3D.C
  20597. //Author: Thomas W. Olsen
  20598. //Version: 1.0
  20599. //Compiler: Microsoft C/C++ 7.0
  20600. // rc /r 3d. rc
  20601. // cl /c /AL /Gsw /W3 /Oas /Zpe /Zi /FPi 3d.c
  20602. // link /CO /NOD 3d,,, libw llibcew win87em, 3d.def
  20603. // rc 3d.exe
  20604. //*****************************************************************
  20605. //********************** Various Constants *************************
  20606. #define CENTER 0
  20607. #define PI 3.141593
  20608. #define RADIANS(a) (a * (PI / 180.0))
  20609. #define DEGREES(a) (a * (180.0 / PI))
  20610. #define NO_OF_VERTICES 66
  20611. #define NO_OF_SURFACES 45
  20612. #define NO_OF_SURFACE_VERTICES 220
  20613. #define VIEW_RATIO (40 / 10)
  20614. #define MIN_DEGREES 0
  20615. #define MAX_DEGREES 359
  20616. #define ROTATE_DEGREES 5
  20617. //********************* Structure Definitions **********************
  20618. typedef struct tagPOINT3D
  20619. {
  20620. double x;
  20621. double y;
  20622. double z;
  20623. } POINT3D;
  20624.  
  20625. typedef struct tagVERTEX
  20626. {
  20627. POINT3D world; // World Coordinates
  20628. POINT3D eye; // Eye Coordinates
  20629. POINT screen; // Screen Coordinates
  20630. } VERTEX;
  20631.  
  20632. typedef struct tagSURFACE
  20633. {
  20634. int mapIndex; // Index Into the Surface Map Array
  20635. int noOfVertices; // No of Vertices Forming the Surface
  20636. int depthIndex; // Used in Determining Depth of Surface
  20637. int brushColor;
  20638. } SURFACE;
  20639.  
  20640. typedef struct tagOBJECT
  20641. {
  20642. VERTEX vertex [NO_OF_VERTICES];
  20643. SURFACE info[NO._OF_SURFACES];
  20644. int map[NO_OF_SURFACE_VERTICES];
  20645. } OBJECT;
  20646. //************************* Static Data ***************************
  20647. OBJECT f16 =
  20648.  
  20649. { // Vertex Array
  20650. { 0.00, 0.00, 0.00 }, //Center
  20651. { 0.00, -17.00, 9.0 }, { 0.00, -13.00, 9.00 }, //Tail
  20652. { 0.00, -14.50, 3.00 }, { 0.00, -8.00, 3.00 },
  20653. { 0.00, -14.50, 2.00 }, { 0.00, -5.00, 2.00 },
  20654. { -0.50, -16.00, 1.00 }, { -1.00, -16.00, 0.00 }, //Exhaust
  20655. { -0.50, -16.00, -0.50 }, { 0.00, -16.00, -1.00 },
  20656. { 0.50, -16.00, -0.50 }, { 1.00, -16.00, 0.00},
  20657. { 0.50, -16.00, 1.00 },
  20658. { -1.00, -13.00, 2.00 }, { -2.00, -13.00, 0.00 }, //Fuse
  20659. { -1.50, -13.00, -1.50 }, { 0.00, -13.00, -2.00 },
  20660. { 1.50, -13.00, -1.50 }, { 2.00, -13.00, 0.00 },
  20661. { 1.00, -13.00, 2.00 }, { -1.00, 7.00, 2.00 },
  20662. { -2.00, 7.00, 0.00 }, { -1.50, 7.00, -1.50 },
  20663. { 0.00, 7.00, -2.00 }, { 1.50, 7.00, -1.50 },
  20664. { 2.00, 7.00, 0.00 }, { 1.00, 7.00, 2.00 },
  20665. { 2.00, -8.00, 0.00 }, { 11.00, -8.00, 0.00 }, //Wings
  20666. { 11.00,-5.00,0.00 }, { 5.00, 0.00, 0.00 },
  20667. { 2.00, 7.00, 0.00 }, { -2.00, -8.00, 0.00 },
  20668. { -11.00, -8.00, 0.00 }, { -11.00, -5.00, 0.00 },
  20669. { -5.00, 0.00, 0.00 }, { -2.00, 7.00, 0.00 },
  20670. { 0.50, 2.00, 2.00 }, { -0.50, 2.00, 2.00 }, //Cockpit
  20671. { 1.00, 5.00, 2.00 }, { 0.50, 5.00, 3.50 },
  20672. { -0.50, 5.00, 3.50 }, { -1.00, 5.00, 2.00 },
  20673. { 1.00, 8.00, 2.00 }, { 0.50, 8.00, 3.50 },
  20674. { -0.50, 8.00, 3.50 }, { -1.00, 8.00, 2.00 ),
  20675. { 0.50, 11.00, 2.00 }, { -0.50, 11.00, 2.00 },
  20676. { 0.00, 7.00, -1.00 }, { -1.00, 11.00, 2.00 }, //Subfuse
  20677. { -2.00, 11.00, 0.00 }, { 0.00, 11.00, -1.00 },
  20678. { 2.00, 11.00, 0.00 }, { 1.00, 11.00, 2.00 },
  20679. { 0.00, 11.00, -1.00 }, { 0.00, 17.00, 0.00 }, //Nose
  20680. { -2.00, -16.00, 0.00 }, { -8.00, -16.00, 0.00 }, //Elevators
  20681. { -8.00, -14.00, 0.00 }, { -2.00, -10.00, 0.00 },
  20682. { 2.00, -16.00, 0.00 }, { 8.00, -16.00, 0.00 },
  20683. { 8.00, -14.00, 0.00 }, { 2.00, -10.00, 0.00 }
  20684. },
  20685. { // Surface Info ... (Points to Surface Map)
  20686. { 0, 5, 60, DKGRAY_BRUSH }, { 5, 5, 64, DKGRAY_BRUSH }, //Elevators
  20687. { 10, 5, 60, DKGRAY_BRUSH }, { 15, 5, 64, DKGRAY_BRUSH },
  20688. { 20, 5, 1, GRAY_BRUSH }, { 25, 5, 4, LTGRAY_BRUSH }, //Tail
  20689. { 30, 5, 1, GRAY_BRUSH }, { 35, 5, 4, LTGRAY_BRUSH },
  20690. { 40, 5, 14, BLACK_BRUSH }, ( 45, 5, 15, DKGRAY_BRUSH }, //Exhaust
  20691. { 50, 5, 16, BLACK_BRUSH }, ( 55, 5, 17, BLACK_BRUSH },
  20692. { 60, 5, 18, DKGRAY_BRUSH }, { 65, 5, 19, BLACK_BRUSH },
  20693. { 70, 5, 20, DKGRAY_BRUSH },
  20694. { 75, 5, 22, DKGRAY_BRUSH }, { 80, 5, 23, GRAY_BRUSH }, //Fuse
  20695. { 85, 5, 24, DKGRAY_BRUSH }, { 90, 5, 25, DKGRAY_BRUSH },
  20696. { 95, 5, 26, GRAY_BRUSH }, { 100, 5, 27, DKGRAY_BRUSH },
  20697. { 105, 5, 20, GRAY_BRUSH, },
  20698. { 110, 6, 30, DKGRAY_BRUSH }, ( 116, 6, 35, DKGRAY_BRUSH }, //Wings
  20699. { 122, 6, 30, DKGRAY_BRUSH }, { 128, 6, 35, DKGRAY_BRUSH },
  20700. { 134, 5, 21, LTGRAY_BRUSH }, { 139, 5, 22, LTGRAY_BRUSH }, //Subfuse
  20701. { 144, 5, 50, LTGRAY_BRUSH }, { 149, 5, 26, LTGRAY_BRUSH },
  20702. { 154, 5, 27, LTGRAY_BRUSH },
  20703. { 159, 4, 55, GRAY_BRUSH }, { 163, 4, 54, GRAY_BRUSH }, //Nose
  20704. { 167, 4, 56, GRAY_BRUSH }, { 171, 4, 52, GRAY_BRUSH },
  20705. { 175, 4, 51, LTGRAY_BRUSH },
  20706. { 179, 5, 41, WHITE_BRUSH }, { 184, 5, 45, WHITE_BRUSH }, //Cockpit
  20707. { 189, 5, 46, WHITE_BRUSH }, { 194, 4, 41, WHITE_BRUSH },
  20708.  
  20709. { 198, 4, 42, WHITE_BRUSH }, { 202, 5, 41, WHITE_BRUSH },
  20710. { 207, 5, 46, WHITE_BRUSH }, { 212, 4, 45, WHITE_BRUSH },
  20711. { 216, 4, 46, WHITE_BRUSH }
  20712. },
  20713. { // Surface Map ... (Points to Vertex Array)
  20714. 58, 61, 60, 59, 58, 62, 63, 64, 65, 62, 58, 59, 60, 61, 58, // Elevators
  20715. 62, 65, 64, 63, 62,
  20716. 1, 3, 4, 2, 1, 3, 5, 6, 4, 3, 1, 2, 4, 3, 1, // Tail
  20717. 3, 4, 6, 5, 3,
  20718. 14, 15, 8, 7, 14, 15, 16, 9, 8, 15, 16, 17, 10, 9, 16, // Exhaust
  20719. 17, 18, 11, 10, 17, 18, 19, 12, 11, 18, 19, 20, 13, 12, 19,
  20720. 20, 14, 7, 13, 20,
  20721. 14, 21, 22, 15, 14, 15, 22, 23, 16, 15, 16, 23, 24, 17, 16, // Fuse
  20722. 17, 24, 25, 18, 17, 18, 25, 26, 19, 18, 19, 26, 27, 20, 19,
  20723. 20, 27, 21, 14, 20,
  20724. 28, 29, 30, 31, 32, 28, 33, 37, 36, 35, 34, 33, 28, 32, 31, // Wings
  20725. 30, 29, 28, 33, 34, 35, 36, 37, 33,
  20726. 21, 51, 52, 22, 21, 22, 52, 53, 50, 22, 50, 53, 54, 26, 50, // Subfuse
  20727. 26, 54, 55, 27, 26, 27, 55, 51, 21, 27,
  20728. 55, 54, 57, 55, 54, 56, 57, 54, 56, 52, 57, 56, 52, 51, 57, 52, // Nose
  20729. 51, 55, 57, 51,
  20730. 41, 42, 39, 38, 41, 45, 46, 42, 41, 45, 48, 49, 46, 45, 48, // Cockpit
  20731. 40, 41, 38, 40, 42, 43, 39, 42, 44, 45, 41, 40, 44,
  20732. 46, 47, 43, 42, 46, 48, 45, 44, 48, 49, 47, 46, 49
  20733. }
  20734. };
  20735.  
  20736. BOOL wireFrame = FALSE;
  20737. double distance = 75, thetaDegrees = 90, phiDegrees = 60;
  20738. //************************** Static Data ***************************
  20739. LONG FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
  20740. lParam);
  20741. void DrawObject(HWND hWnd, HDC hDC, OBJECT *object );
  20742. int __cdecl compareProc(const void *elem1, const void *elem2 );
  20743. //************************** Program Begin *************************
  20744. int PASCAL WinMain(HANDLE hInst, HANDLE hPrevInst, LPSTR lpCmdLine, int
  20745. numCmdShow )
  20746. {
  20747. MSG msg;
  20748. HWND hWnd;
  20749. WNDCLASS wc;
  20750. //************************ Setup Window ************************
  20751. wc.style = (UINT) NULL;
  20752. wc.lpfnWndProc = WindowProc;
  20753. wc.cbClsExtra = 0;
  20754. wc.cbWndExtra = 0;
  20755. wc.hInstance = hInst;
  20756. wc.hIcon = LoadIcon( NULL, IDI_APPLICATION);
  20757. wc.hCursor = LoadCursor( NULL, IDC_ARROW);
  20758. wc.hbrBackground = GetStockObject(BLACK_BRUSH);
  20759. wc.lpszMenuName = (LPSTR) "Menu";
  20760. wc.lpszClassName = (LPSTR) "3DClass";
  20761.  
  20762. if (!RegisterClass(&wc))
  20763. return(FALSE);
  20764.  
  20765. hWnd = CreateWindow( "3DClass", "3D Modeling Example",
  20766. WS_OVERLAPPEDWINDOW WS_SCROLL WS_HSCROLL,
  20767. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  20768. CW_USEDEFAULT, NULL, NULL, hInst, NULL );
  20769. if (!hWnd)
  20770.  
  20771. return (FALSE);
  20772.  
  20773. SetScrollRange( hWnd, SB_HORZ, MIN_DEGREES, MAX_DEGREES, TRUE );
  20774. SetScrollRange( hWnd, SB_VERT, MIN_DEGREES, MAX_DEGREES, TRUE );
  20775. SetScrollPos( hWnd, SB_HORZ, (int) thetaDegrees, TRUE );
  20776. SetScrollPos( hWnd, SB_VERT, (int) phiDegrees, TRUE );
  20777.  
  20778. ShowWindow( hWnd, numCmdShow );
  20779.  
  20780. while (GetMessage(&msg, NULL, NULL, NULL)) /* Typical Mossage Loop */
  20781. {
  20782. TranslateMessage(&msg);
  20783. DispatchMessage(&msg);
  20784. }
  20785.  
  20786. return (msg.wParam);
  20787. }
  20788.  
  20789. LONG FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
  20790. lParam)
  20791. {
  20792. PAINTSTRUCT paint;
  20793. HDC hDC;
  20794. HMENU hMenu;
  20795. int vPos, hPos;
  20796.  
  20797. switch (message)
  20798. {
  20799. case WM_COMMAND:
  20800. hMenu = GetMenu( hWnd );
  20801. CheckMenuItem( hMenu, wParam, MF_CHECKED);
  20802.  
  20803. if (wParam == 1)
  20804. {
  20805. CheckMenuItem( hMenu, 2, MF_UNCHECKED);
  20806. wireFrame = TRUE;
  20807. }
  20808. else
  20809. {
  20810. CheckMenuItem( hMenu, 1, MF_UNCHECKED);
  20811. wireFrame = FALSE;
  20812. }
  20813. InvalidateRect( hWnd, NULL, TRUE );
  20814. break;
  20815.  
  20816. case WM_DESTROY:
  20817. PostQuitMessage( NULL );
  20818. break;
  20819.  
  20820. case WM_HSCROLL:
  20821. if (wParam == SB_THUMBTRACK)
  20822. break;
  20823.  
  20824. hPos = GetScrollPos( hWnd, SB_HORZ);
  20825.  
  20826. switch( wParam )
  20827. {
  20828. case SB_TOP:
  20829. hPos = MIN_DEGREES;
  20830. break;
  20831.  
  20832. case SB_BOTTOM:
  20833. hPos = MAX_DEGREES;
  20834. break;
  20835. case SB_LINEUP:
  20836. case SB_PAGEUP:
  20837. hPos -= ROTATE_DEGREES;
  20838. break;
  20839. case SB_PAGEDOWN:
  20840. case SB_LINEDOWN:
  20841. hPos += ROTATE_DEGREES;
  20842. break;
  20843. case SB_THUMBPOSITION:
  20844. hPos = LOWORD(lParam);
  20845. break;
  20846. }
  20847.  
  20848. if (hPos < MIN_DEGREES)
  20849. hPos = MAX_DEGREES;
  20850. else
  20851. if (hPos > MAX_DEGREES)
  20852. hPos = MIN_DEGREES;
  20853.  
  20854. SetScrollPos( hWnd, SB_HORZ, hPos, TRUE );
  20855. thetaDegrees = (double) hPos;
  20856. InvalidateRect( hWnd, NULL, TRUE );
  20857. break;
  20858.  
  20859. case WM_VSCROLL:
  20860. if (wParam == SB_THUMBTRACK)
  20861. break;
  20862.  
  20863. vPos = GetScrollPos( hWnd, SB_VERT );
  20864.  
  20865. switch( wParam )
  20866. {
  20867. case SB_TOP:
  20868. vPos = MIN_DEGREES;
  20869. break;
  20870. case SB_BOTTOM:
  20871. vPos = MAX_DEGREES;
  20872. break;
  20873. case SB_LINEUP:
  20874. case SB-PAGEUP:
  20875. vPos -= ROTATE_DEGREES;
  20876. break;
  20877. case SB_PAGEDOWN:
  20878. case SB_LINEDOWN:
  20879. vPos += ROTATE_DEGREES;
  20880. break;
  20881. case SB_THUMBPOSITION:
  20882. vPos = LOWORD(lParam);
  20883. break;
  20884. }
  20885.  
  20886. if (vPos < MIN_DEGREES)
  20887. vPos = MAX_DEGREES;
  20888. else
  20889. if (vPos > MAX_DEGREES)
  20890. vPos = MIN_DEGREES;
  20891.  
  20892.  
  20893. SetScrollPos( hWnd, SB_VERT, vPos, TRUE );
  20894. phiDegrees = (double) vPos;
  20895. InvalidateRect( hWnd, NULL, TRUE );
  20896. break;
  20897.  
  20898. case WM_SIZE:
  20899. InvalidateRect( hWnd, NULL, TRUE );
  20900. break;
  20901.  
  20902. case WM_PAINT:
  20903. hDC = BeginPaint( hWnd, &paint );
  20904. DrawObject(hWnd, hDC, &f16);
  20905. ReleaseDC( hWnd, hDC );
  20906. EndPaint( hWnd, &paint );
  20907. break;
  20908.  
  20909. default:
  20910. return (DefWindowProc(hWnd, message, wParam, lParam));
  20911. }
  20912. }
  20913.  
  20914. void DrawObject(HWND hWnd, HDC hDC, OBJECT *object)
  20915. {
  20916. double sinTheta, cosTheta, sinPhi, cosPhi;
  20917. double s1, s2, s3;
  20918. POINT3D *v1, *v2, *v3;
  20919. POINT center;
  20920. RECT rect;
  20921. POINT points[10];
  20922. HBITMAP hBitmap, hOldBitmap;
  20923. HRGN hRgn;
  20924. HDC hMemDC;
  20925. int surface, vertex, mapIndex, vertexIndex, loop;
  20926.  
  20927. GetClientRect( hWnd, &rect); // Determine Size of Client Area
  20928. center.x = (rect.right / 2); // Calculate X-Centerpoint
  20929. center.y = (rect.bottom / 2); // Calculate Y-Centerpoint
  20930.  
  20931. hRgn = CreateRectRgn( rect.left, rect.top, rect.right, rect.bottom );
  20932. hMemDC = CreateCompatibleDC( hDC );
  20933. hBitmap = CreateCompatibleBitmap( hDC, rect.right, rect.bottom);
  20934. hOldBitmap = SelectObject( hMemDC, hBitmap );
  20935. //**********************************************************************
  20936. //* Precalculate SIN(x) and COS(x) *
  20937. //**********************************************************************
  20938. cosTheta = cos( RADIANS(thetaDegrees) );
  20939. sinTheta = sin( RADIANS(thetaDegrees) );
  20940. cosPhi = cos( RADIANS(phiDegrees) );
  20941. sinPhi = sin( RADIANS(phiDegrees) );
  20942. //******************************************************************
  20943. //* Calculate Eye and Screen Coordinates *
  20944. //******************************************************************
  20945. for (loop = 1; loop < NO_OF_VERTICES; loop++)
  20946. {
  20947. object->vertex[loop].eye.x =
  20948. (-object->vertex[loop].world.x * sinTheta) +
  20949. (object->vertex[loop].world.y * cosTheta);
  20950. object->vertex[loop].eye.y =
  20951.  
  20952. (-object->vertex[loop].world.x * cosTheta * cosPhi) -
  20953. (object->vertex[loop].world.y * sinTheta * cosPhi) +
  20954. (object->vertex[loop].world.z * sinPhi);
  20955. object->vertex[loop].eye.z =
  20956. (-object->vertex[loop].world.x * sinPhi * cosTheta) -
  20957. (object->vertex[loop].world.y * sinTheta * sinPhi) -
  20958. (object->vertex[loop].world.z * cosPhi) + distance;
  20959.  
  20960. object->vertex[loop].screen.x = (int)
  20961. (VIEW_RATIO * (object->vertex[loop].eye.x / object->vertex[loop].eye.z) *
  20962. center.y + center.x);
  20963. object->vertex[loop].screen.y = (int)
  20964. (-VIEW_RATIO * (object->vertex[loop].eye.y / object->vertex[loop].eye.z) *
  20965. center.y + center.y);
  20966. }
  20967. //******************************************************************
  20968. //* Draw Object *
  20969. //******************************************************************
  20970. FillRgn( hMemDC, hRgn, GetStockObject(BLACK_BRUSH));
  20971. SelectObject( hMemDC, GetStockObject(wireFrame == TRUE ? WHITE_PEN:BLACK_PEN)
  20972. );
  20973.  
  20974. if (wireFrame == FALSE)
  20975. qsort( object->info, NO_OF_SURFACES, sizeof(SURFACE), compareProc );
  20976.  
  20977. for (surface = 0; surface < NO_OF_SURFACES; surface++)
  20978. {
  20979. mapIndex = object->info[surface].mapIndex;
  20980.  
  20981. if (wireFrame == FALSE) // No Hidden Surface Removal For Wire Frame
  20982. {
  20983. v1 = &object->vertex[ object->map[ mapIndex ] ].eye; // Setup pointers to
  20984. three
  20985. v2 = &object->vertex[ object->map[ mapIndex+1 ] ].eye; // surface vertices
  20986. v3 = &object->vertex[ object->map[ mapIndex+2 ] ].eye;
  20987.  
  20988. s1 = v1->x * (v2->y * v3->z - v3->y * v2->z); s1 = (-1) * s1;
  20989. s2 = v2->x * (v3->y * v1->z - v1->y * v3->z); // Perform dot product on
  20990. surface
  20991. s3 = v3->x * (v1->y * v2->z - v2->y * v1->z); // vectors to find normal vector
  20992. }
  20993.  
  20994. if (wireFrame == TRUE s1 - s2 - s3 <= 0.0)
  20995. {
  20996. for (vertex = 0; vertex < object->info[surface].noOfVertices; vertex++,
  20997. mapIndex++)
  20998. {
  20999. vertexIndex = object->map[mapIndex];
  21000. points[vertex].x = object->vertex[vertexIndex].screen.x;
  21001. points[vertex].y = object->vertex[vertexIndex].screen.y;
  21002. }
  21003.  
  21004. if (wireFrame == TRUE)
  21005. Polyline( hMemDC, &points[0], vertex;
  21006. else
  21007. {
  21008. SelectObject( hMemDC, GetStockObject(object->info[surface].brushColor) );
  21009. Polygon( hMemDC, &points[0], vertex);
  21010. }
  21011. }
  21012. }
  21013. BitBlt( hDC, 0, 0, rect.right, rect.bottom, hMemDC, 0, 0, SRCCOPY);
  21014.  
  21015. SelectObject( hMemDC, hOldBitmap );
  21016. DeleteObject( hBitmap );
  21017.  
  21018. DeleteObject( hRgn );
  21019. DeleteDC( hMemDC );
  21020. }
  21021.  
  21022. int _cdecl compareProc( const void *elem1, const void *elem2 )
  21023. {
  21024. if( f16.vertex[((SURFACE *) elem1)->depthIndex].eye.z >
  21025. f16.vertex[((SURFACE *) elem2)->depthIndex].eye.z)
  21026. return -1;
  21027. if( f16.vertex[((SURFACE *) elem1)->depthIndex].eye.z <
  21028. f16.vertex[((SURFACE *) elem2)->depthIndex].eye.z)
  21029. return 1;
  21030. else
  21031. return 0;
  21032. }
  21033. /* End of File*/
  21034.  
  21035.  
  21036. Listing 2 3d.def
  21037. NAME 3D
  21038.  
  21039. DESCRIPTION '3D Modeling Example'
  21040.  
  21041. EXETYPE WINDOWS
  21042. STUB 'WINSTUB.EXE'
  21043.  
  21044. CODE PRELOAD MOVEABLE
  21045. DATA PRELOAD MOVEABLE MULTIPLE
  21046.  
  21047. HEAPSIZE 2048
  21048. STACKSIZE 4096
  21049.  
  21050. EXPORTS
  21051. WindowProc @1
  21052.  
  21053. Listing 3 3d.rc
  21054. #include "windows.h"
  21055.  
  21056. Menu MENU
  21057. {
  21058. POPUP "&Draw"
  21059. {
  21060. MENUITEM "&Wire Frame", 1
  21061. MENUITEM "&Solid", 2, CHECKED
  21062. }
  21063. }
  21064.  
  21065.  
  21066.  
  21067.  
  21068.  
  21069.  
  21070.  
  21071.  
  21072.  
  21073.  
  21074.  
  21075.  
  21076.  
  21077.  
  21078.  
  21079.  
  21080.  
  21081. Pointer Power in C and C++, Part 2
  21082.  
  21083.  
  21084. Christopher Skelly
  21085.  
  21086.  
  21087. Christopher Skelly has been a teacher of C and C++ for the past ten years,
  21088. first for Plum Hall Inc., and then for his own company, Insight Resource Inc.
  21089. Insight Resource also developed the best-selling help utility, "KO-PILOT for
  21090. WordPerfect," which Brit Hume called "the best add-in ever written." Chris has
  21091. served on both the C and C++ ANSI committees, and was the Technical Chairman
  21092. for this year's "CPlusC++" and "C++ in Action" conferences, presented by
  21093. Boston University. He writes regularly for the C User's Journal and the C++
  21094. Journal, and can be reached at Insight Resource Inc., 914-631-5032, or at
  21095. 71005.771@compuserve.com.
  21096.  
  21097.  
  21098. This article extends and continues the techniques presented last month in Part
  21099. 1 of "Pointer Power in C and C++." At the end of this article I will repeat
  21100. and then solve the pointer puzzle presented in Part 1. Just in case you don't
  21101. happen to have last month's issue immediately available, here are the eight
  21102. Key Facts from Part 1:
  21103. 1. A pointer is a variable whose contents is an address.
  21104. 2. A pointer always "knows" the type of thing it addresses. It can be properly
  21105. used only to access something of the correct type.
  21106. 3. Pointer values are address/type pairs, just like pointer variables.
  21107. However, pointer values are not storable lvalues.
  21108. 4. Every pointer has three fundamental attributes. These attributes are the
  21109. location, the contents, and the indirect value of the pointer.
  21110. 5. The three attributes of a pointer represent three distinct address levels.
  21111. These address levels can also be called levels of indirection.
  21112. 6. Pointer space is organized into a series of planes or levels. Every pointer
  21113. expression can be assigned to one of these planes. The plane of a pointer
  21114. expression is a measure of how much potential for indirection there is in that
  21115. pointer expression.
  21116. 7. The name of an array usually behaves as if the array name were a pointer
  21117. value.
  21118. 8. The name of an array, in almost every context, evaluates to the address of
  21119. the array's own "zeroth" element.
  21120. Armed with these Key Facts, you are ready to learn the next set of techniques,
  21121. a game informally called Pointer Dominos.
  21122.  
  21123.  
  21124. Pointer Dominoes
  21125.  
  21126.  
  21127. The key to mastering pointers is to learn to play Pointer Dominos. The game of
  21128. pointer dominos is simply the game of using operators in expressions involving
  21129. pointers. Each of the allowable operators does something very specific, and
  21130. the operators are always to be played, or really evaluated, in a very precise
  21131. order. If you know exactly what each operator does, and if you know how to
  21132. determine the order of evaluation, you can play pointer dominos.
  21133. Key Fact #9 -- Only a small number of operations are ever performed on
  21134. pointers. If you know exactly what each operation does, and what the right
  21135. order to apply the operations is, you can understand and create any pointer
  21136. expression in C.
  21137. Each pointer has three attributes and each attribute is at a different level
  21138. of indirection. You know that certain operators actually change the level of
  21139. indirection of an expression using a pointer. Specifically, you have already
  21140. seen that & takes you up one level and * takes us down a level when applied to
  21141. a pointer in an expression. Note that in declarations the * builds in one
  21142. level of indirection, as does the []. The rules of pointer dominos apply only
  21143. to expressions, not to declarations.
  21144. int i = 0; /* i is declared at level 0 */
  21145. int *p; /* p is declared at level 1 */
  21146. p = &i; /* &i is level 1, so is p */
  21147. x = *p; /* * on p takes us down from level 1
  21148. to level 0 */
  21149. This leads to the first two rules of Pointer Dominos. When used in pointer
  21150. expressions:
  21151. & takes the type of the expression up one level of indirection.
  21152. * takes the type of the expression down one level of indirection.
  21153. Several other operators affect the overall level of indirection of an
  21154. expression. But all the operators do one of a small number of things. They
  21155. take us up, down, or sometimes even sideways on the Ladder. Fortunately, each
  21156. allowable operation has a precise, well-defined meaning. Only six moves appear
  21157. in expressions involving pointers. These moves form our next key fact.
  21158. Key Fact #10 -- The six moves of pointer dominos are:
  21159. 1. Go up one level of indirection using &
  21160. 2. Go down one level of indirection using *
  21161. 3. Go down one level of indirection using []
  21162. 4. Increase an address using + or ++
  21163. 5. Decrease an address using - or --n
  21164. 6. Change the type of the pointer's window on memory with a cast.
  21165. Structures and their members are not included here, but are easy to add to the
  21166. fundamentals of the model.
  21167. Each move corresponds to one or two C language operators. The first move to
  21168. consider is the &, the unary address-of operator. & always lifts you up one
  21169. level of indirection. Generating the address of something is the equivalent of
  21170. moving up to the next plane in pointer space. As I've said, this process is
  21171. known as referencing, and involves a relative move up to the next higher
  21172. address plane.
  21173. Two operators move you down one level of indirection. First the *, or unary
  21174. indirection operator, means go down to the plane immediately below the plane
  21175. you start on. If x lives on plane 5, *x is an expression that lives on plane
  21176. 4. Most typically, if p lives on plane 1, *p is a non-pointer living on plane
  21177. 0. This move is called dereferencing, and it is a relative move. *x is one
  21178. level below x. You can't say anything about what level this actually is, until
  21179. you know what level x itself resides on.
  21180. The [] operator is also a dereferencing operator. a[n] lives on the plane
  21181. below a, where a is any address expression. An important principle of pointer
  21182. dominos is the fact that both * and [] bring an expression down one level of
  21183. indirection from what they are applied to. The formula which relates how * and
  21184. [] are related is the important formula:
  21185. a[n] == *(a + n)
  21186. This master formula in C and C++ shows that a subscript is syntactically
  21187. equivalent to dereferencing an offset from a pointer. The a in the formula
  21188. represents any address. The address may come from a pointer, or from an array
  21189. name, or from a casted expression. It doesn't matter. The subscript can always
  21190. be applied to an address, just like the *, and the relation between the two
  21191. forms comes from this formula.
  21192. The fourth and fifth moves involve operations of addition and substraction,
  21193. including +, ++, -, and --. These operators produce no change in level of
  21194. indirection. They move you sideways on the same plane, either toward higher or
  21195. lower memory and always by a scaled offset. p + 1 is the address of one object
  21196. higher in memory than the object p points to. p - 2 is the address of an
  21197. object two objects below the object addressed by p. p, p + 1, and p - 2,
  21198. however, all exist on the same plane and have the same level of indirection.
  21199. The final pointer domino move is the cast. Casting a pointer usually produces
  21200. no change on the level of indirection. Casting a pointer to int to a pointer
  21201. to double, for example, does not change the level of the pointer. What does
  21202. change is the size and format of the "window on memory" that this pointer
  21203. accesses. A pointer to char accesses one byte of integer data. A pointer to
  21204. double accesses eight bytes of floating-point formatted data. There are
  21205. special cases, however, where a cast does affect the level of indirection of
  21206. an expression. Consider:
  21207. char *p = malloc(1000);
  21208. char **p2;
  21209. p2 = (char **)p;
  21210. Here the cast, (char **), does indeed produce a level change from level one up
  21211. to level two. The real rule is this: the level of an expression with a cast is
  21212. the level of indirection of the cast. Casts are the wild-cards of pointer
  21213. dominos. Any expression can be cast to have some new level and type. The
  21214. declaration inside the cast determines the level of the newly-casted
  21215. expression.
  21216. Summarizing the rules of pointer dominos in terms of operators, you have:
  21217. & -- move up one level
  21218.  
  21219. * -- move down one level
  21220. [n] -- move down one level, with an offset of n elements
  21221. + ++ -- add a scaled offset on the same level
  21222. - -- -- subtract a scaled offset on the same level
  21223. (type) -- change the size, format, and possible level of the expression to
  21224. that of the type in the cast
  21225. This set of six rules forms the guts of the game. All you need now is one
  21226. additional rule, which tells you which order to apply the moves when more than
  21227. one operator are present in the same expression.
  21228. For instance, in the expression:
  21229. *++p
  21230. two operations, * (indirection) and ++ (pre-increment) are being applied to
  21231. the pointer p. Which operation should you do first, the increment or the
  21232. indirection? The resulting value will be very different depending on what you
  21233. decide. If you did the * first, you would take the indirect value of p, and
  21234. then increment that indirect value. In fact, this is just the opposite of what
  21235. you are really supposed to do.
  21236. The rules of precedence state that unary operators, like both pre-increment ++
  21237. and *, group right to left. This means that the ++ binds with p before the *
  21238. is even considered. You must increment the pointer and then take the indirect
  21239. value. The point here is that the rules of precedence always determine the
  21240. proper order of evaluation. This is our last key fact.
  21241. Key Fact #11 -- If more than one operator is applied in an expression, apply
  21242. the operators in order of precedence.
  21243. A quick glance at the precedence table reveals that primary operators include
  21244. both [] and (), while the * and & are weaker-binding unary operators.
  21245. Furthermore, primary operators group left to right and unaries group right to
  21246. left. In effect, this means that you will deal with the primary [] and ()
  21247. first in left to right order, and then handle the unary *s or &s in right to
  21248. left order.
  21249. For example, the expression
  21250. *p[n]
  21251. contains two operators, one primary [] and one unary *. The [] binds first
  21252. followed by the *, so the interpretation is "locate the array element p[n],
  21253. then dereference this element."
  21254. A more complex expression may have lots of operators to consider:
  21255. *(char *)p2[n][m]
  21256. Here both subscripts bind first, in left to right order. Then comes the cast,
  21257. (char *), which changes the type of the value in p2[n][m] to be a pointer to
  21258. char. Finally, the * on the left dereferences this casted pointer.
  21259. Showing each step in order:
  21260. 1. p2--starting with p2
  21261. 2. p2[n]--access the nth element offset from p2
  21262. 3. p2[n] [m]--access the mth element offset from p2[n]
  21263. 4. (char *)p2[n][m]--cast to the type pointer to char
  21264. 5. *(char *)p2[n][m]--dereference the resulting char pointer
  21265. The rules are simple. Apply each pointer move in the proper order of
  21266. precedence. Keep track of levels as you go. Now you are thinking just like the
  21267. C compiler!
  21268.  
  21269.  
  21270. Solving the Puzzle
  21271.  
  21272.  
  21273. It's time to solve the puzzle presented at the beginning of this article.
  21274. Though the puzzle has inordinately complex expressions, the rules of pointer
  21275. dominos will make short work of the task. Listing 1 contains the puzzle again.
  21276. What kind of data structures are you working with in this puzzle? Figure 1
  21277. contains a picture of the data.
  21278. As you can see in Figure 1, ap is an array of pointers to chars, each pointer
  21279. aimed at one of five character strings. ap is a level two object. Why? First,
  21280. because ap is an array, it has intrinsically one level of indirection. But the
  21281. elements of ap are all pointers, each holding their own level one address. So
  21282. ap evaluates to the address of a pointer, hence ap is a level two expression.
  21283. app is similarly a level three expression. An array of level two pointers
  21284. evaluates to the address of a level two pointer, hence app lives on level
  21285. three. ppp is a level 3 pointer, and pppp is a level four pointer. The
  21286. relationships between the pointers are illustrated in Figure 1.
  21287. Here is the first expression to be printed:
  21288. printf("%.*s", 2, *--**pppp);
  21289. Do the easy part first. The %.*s format specifier means to fill in the * with
  21290. the first argument in the argument list following the format string. So you
  21291. are really asking for %.2s, that is, print the first two characters of the
  21292. string *--**pppp. How do you unravel *--**pppp? With the rules of pointer
  21293. dominos!
  21294. Start at the identifier, pppp, and apply the operators in order of precedence.
  21295. Both * and -- are unary operators so they group right to left, as follows:
  21296. 1. pppp--first the identifier pppp
  21297. 2. *pppp--right-most * dereference pppp
  21298. 3. **pppp--second right-most * dereference *pppp
  21299. 4. --**pppp--unary -- pre-decrement the result
  21300. 5. *--**pppp left-most * dereference again
  21301. Reading off the quoted strings gives a comprehensible formula for solving this
  21302. part of the puzzle.
  21303. To find the answer, start at the top of the diagram of the puzzle's data, at
  21304. pppp, and move down two levels, following the arrows. You should be at app[0],
  21305. the zeroth element in the app array of char ** pointers. app[0] holds the
  21306. address of ap[4], the last element in the array of char * pointers. But now,
  21307. the rules say you must apply the unary -- operator to app[0]. Instead of
  21308. pointing at ap[4], app[0] will now hold the address of ap[3]! This change, by
  21309. the way, persists, and changes the diagram slightly from that shown in Figure
  21310. 1. After decrementing app[O], you apply the final dereference or *, and arrive
  21311. at the contents of ap[3]. This is what you will print with the first
  21312. expression. Actually, the program prints only the first two characters of the
  21313. string PORTABLE. So PO appears on the output.
  21314. What does the second expression print?
  21315. printf("%.*s", 3, *(++*pppp[0] - 4));
  21316. This complex expression again uses the %.*s mechanism to pick up the 3 as the
  21317. number of chars to be printed. In effect, you will print three chars from the
  21318. address given by the complex expression *(++*pppp [0] - 4)).
  21319. This one breaks down as follows:
  21320. 1. pppp--start at pppp
  21321. 2. pppp[0]--dereference to access [0] th element
  21322. 3. *pppp[0]--dereference pppp[0]
  21323. 4. ++*pppp[0]--pre-increment the result
  21324. 5. ++*pppp[0] - 4--subtract 4
  21325. 6. *(++*pppp[0] - 4)--dereference again
  21326. Handling each operator one step at a time gives you the solution.
  21327. The only new trick here is the translation between [] and *. Remember the
  21328. all-important a[n] == *(a + n) formula and you'll zip through the steps.
  21329. Accessing the zeroth element of pppp is the same as dereferencing pppp. a[0]
  21330. is always the same object as *a. So the subscript [0] and the right-most *
  21331. bring you down two levels, just as before. Only now you are told to increment
  21332. the result, namely app[0]. So app[0] now pops back right back to where it
  21333. started in the first place, namely to point to ap[4], the string TOWER!.
  21334. Now what? Now, you have to subtract 4 from this pointer. This is where it can
  21335. get tough. But remember, app is an array of char ** pointers, so app[0] acts
  21336. like a pointer to a pointer to a char. Decrementing means subtracting the
  21337. space for four char * pointers. So app[0] - 4 points to ap[0], the very first
  21338. string in the puzzle, INTEGER. You have to print three characters from this
  21339. string. So INT appears on the display right after the PO. The screen says
  21340. POINT.
  21341.  
  21342. The third expression prints the whole of the expression:
  21343. ++*--*++pppp[0] + 5
  21344. This whopper breaks down according to the precedence rules as follows:
  21345. 1. pppp
  21346. 2. pppp[0]
  21347. 3. ++pppp[0]
  21348. 4. *++pppp[0]
  21349. 5. -- *++pppp[0]
  21350. 6. *--*++pppp[0]
  21351. 7. ++*--*++pppp[0]
  21352. 8. ++*--*++pppp[0] + 5
  21353. Here's the explanation.
  21354. Again [0] means move down one level, to ppp. Now increment ppp, so ppp points
  21355. to ppp[1], not ppp[0] anymore. Dereference with * means move down to app[1].
  21356. Now decrement app[1], so app[1] points to ap[2] from now on, rather than
  21357. ap[3]. Dereferencing with * brings you down to ap[2]. Now increment ap[2], so
  21358. the pointer to char, ap[2], points to the E rather than the D of DEBUGGER.
  21359. Finally, add 5. Since you are now down at level one, the contents of ap[2],
  21360. adding 5 means add the size of five chars to the pointer. Hence you are
  21361. finally left pointing at the second E in DEBUGGER. Printing the resulting
  21362. string, along with a space as the puzzle requires, gives us ER.
  21363. The screen now says POINTER.
  21364. Only two more. By the time you're done you'll never forget how to do this! The
  21365. fourth printf statement looks like this:
  21366. printf("%.*s", 2, *++pppp[0][3] + 3);
  21367. You are asked to print two characters from the expression:
  21368. *++pppp[0][3] + 3
  21369. Here's the break down of the steps.
  21370. 1. pppp
  21371. 2. pppp[0]
  21372. 3. pppp[0][3]
  21373. 4. ++pppp[0][3]
  21374. 5. *++pppp[0][3]
  21375. 6. *++pppp[0][3] + 3
  21376. Now just read through this break down, supplying the interpretation for each
  21377. step.
  21378. [0] again moves down to ppp. Adding a [3] to ppp means two things; go down to
  21379. the next level, and offset by three elements. So while pppp[0][0] is app[1]
  21380. (remember, you incremented ppp in the last step), pppp[0][3] is app[4]! Now
  21381. increment this result, app[4], so app[4] points at ap[1], rather than ap[0].
  21382. Dereferencing with the * brings you down to the contents of ap[1]. Adding 3,
  21383. skips the first three characters of PROPORTION so you print the PO substring
  21384. from the middle of PROPORTION.
  21385. Now the screen reads POINTER PO.
  21386. Time for the last one! See if you can get this one without an explanation. If
  21387. you get to WER!, you're right. Remember to consider the changes that -- and ++
  21388. created in the earlier expressions. Here's how.
  21389. printf("%s\n", (*pppp + 2)[-2][2] + 2);
  21390. 1. pppp
  21391. 2. *pppp
  21392. 3. *pppp + 2
  21393. 4. (*pppp + 2)[-2]
  21394. 5. (*pppp + 2)[-2][2]
  21395. 6. (*pppp + 2)[-2][2] + 2
  21396. Dereference pppp to ppp, now still pointing at app[1]. Adding 2 creates a
  21397. pointer value pointing at app[3]. What does the [-2] subscript do? First it
  21398. moves you down to the level of app[3], but the -2 means two objects lower in
  21399. memory, so you move down to app[1], not app[3]. app[1] is still pointing at
  21400. ap[2], where the -- in the third expression left app[1]. Applying the [2]
  21401. subscript to this value of app[1] moves us down and over to ap[4], two
  21402. pointers offset from ap[2]. The last + 2 skips over the first two chars in
  21403. TOWER!, so you see the last part of the puzzle, WER! on the display.
  21404. The screen says POINTER POWER! and you had better believe it!
  21405. You may want to work the steps of this puzzle over several times, perhaps
  21406. drawing some intermediate diagrams as the pointers change. If you can work
  21407. this puzzle correctly, you have indeed acquired pointer power as a long-term
  21408. resource for your future C and C++ programs!
  21409. Remembering to carefully distinguish the Three Attributes, following the
  21410. levels on the Ladder of Indirection, and applying the rules of Pointer Dominos
  21411. will get you to the solution every time. Furthermore, understanding complex
  21412. pointer behavior will give you the confidence to create your own sophisticated
  21413. data-handling mechanisms, when and where appropriate. The examples in the
  21414. puzzle, of course, are not designed to be "good code." They are designed to
  21415. show that this most powerful part of C is indeed governed by straight-forward
  21416. rules which can always be applied to understand and work with complex pointer
  21417. declarations and expressions in C.
  21418. Now it's on to C++, where void isn't quite so void anymore, where references
  21419. are not pointers, but sometimes act like them, and where pointers to members
  21420. are not even addresses in the standard C sense at all!
  21421. Figure 1
  21422.  
  21423. Listing 1 What does this program print?
  21424. #include <stdio.h>
  21425.  
  21426. char *ap[] = {
  21427. "INTEGER",
  21428. "PROPORTION",
  21429. "DEBUGGER",
  21430. "PORTABLE",
  21431. "TOWER!"
  21432. };
  21433.  
  21434. char **app[] = { ap + 4, ap + 3, ap + 2, ap + 1, ap };
  21435.  
  21436. char ***ppp = app;
  21437.  
  21438. char ****pppp = &ppp;
  21439.  
  21440.  
  21441. void main()
  21442. {
  21443. printf("%.*s", 2, *--**pppp);
  21444.  
  21445. printf("%.*s", 3, *(++*pppp[0] - 4));
  21446.  
  21447. printf("%s " , ++*--*++pppp[0] + 5);
  21448.  
  21449. printf("%.*s" , 2, *++pppp[0][3] + 3);
  21450.  
  21451. printf("%s\n", (*pppp + 2) [-2][2] + 2);
  21452. }
  21453.  
  21454. /* End of File */
  21455.  
  21456.  
  21457.  
  21458.  
  21459.  
  21460.  
  21461.  
  21462.  
  21463.  
  21464.  
  21465.  
  21466.  
  21467.  
  21468.  
  21469.  
  21470.  
  21471.  
  21472.  
  21473.  
  21474.  
  21475.  
  21476.  
  21477.  
  21478.  
  21479.  
  21480.  
  21481.  
  21482.  
  21483.  
  21484.  
  21485.  
  21486.  
  21487.  
  21488.  
  21489.  
  21490.  
  21491.  
  21492.  
  21493.  
  21494.  
  21495.  
  21496.  
  21497.  
  21498.  
  21499.  
  21500.  
  21501.  
  21502.  
  21503. C++, Coroutines, and Simulation
  21504.  
  21505.  
  21506. Trond Akerbaek
  21507.  
  21508.  
  21509. Trond Akerbaek is teaching at a college in Halden, Norway. Current interests
  21510. include computer communications, neural networks, and fuzzy systems. He can be
  21511. contacted at Ostfold DH; N-1757 Halden; Norway; e-mail:tronda@dhhalden.no.
  21512.  
  21513.  
  21514.  
  21515.  
  21516. Introduction
  21517.  
  21518.  
  21519. Object-oriented programming is not a new concept. The Simula67 Language,
  21520. developed in the late sixties, contains all the basic concepts found in modern
  21521. object-oriented languages such as C++. An important part of Simula not found
  21522. in C++ is the support for quasi-parallel processes and simulation. Very few
  21523. programmers ever use parallel functions or coroutines in their programs, even
  21524. if it will lead to more elegant designs. There are several reasons: few
  21525. languages have ample support, coroutines may otherwise be cumbersome to
  21526. implement, and it is possible to solve most problems without.
  21527. C programmers may use setjmp and longjmp and functions based on these to
  21528. implement quasi-parallell routines. With C++ and some assembly instructions it
  21529. is possible to create a set of functions which makes coroutines easier to
  21530. comprehend and use. Using coroutines, implementing simple tools for simulation
  21531. modelled after the Simula67 tools, is a fairly easy matter.
  21532. In this article I will give a brief introduction to coroutines and describe an
  21533. implementation in Borland C++ 2.0, with a single assembly function in Turbo
  21534. Assembler. Based on the coroutine concept, the article will discuss simulation
  21535. and how to implement it in C++.
  21536.  
  21537.  
  21538. Quasi-Parallel Functions and Coroutines
  21539.  
  21540.  
  21541. Due to the single-processor constraint, computer programming has traditionally
  21542. been sequentially-oriented with a single thread of operations. Multitasking
  21543. operating systems give us the opportunity to create programs consisting of
  21544. several sequential processes operating in parallel on a timesharing basis. The
  21545. operating system controls the task switching, with little or no influence from
  21546. the programmer. 
  21547. The object-oriented approach is still basically sequential, even if it makes
  21548. it easier to describe real processes which are interacting and operating in
  21549. parallel. Parallel processes cannot directly be modelled in a sequential
  21550. language. The process descriptions are running on a single processor, and must
  21551. switch back and forth, imitating real parallel behavior, hence the expression
  21552. quasi-parallel.
  21553. It would be another situation if the model were running on a multiprocessor
  21554. machine, with one processor for each process. In that case the model would be
  21555. described in a language designed for such environments.
  21556.  
  21557.  
  21558. The Coroutine Extensions
  21559.  
  21560.  
  21561. In order to support the coroutine concept I have created a few extensions to
  21562. C++:
  21563. A base-class coroutine for all classes describing coroutines.
  21564. A virtual function main in coroutine subclasses containing the actions of the
  21565. coroutine.
  21566. The primitive resume(coroutine*) which freezes the current coroutines actions,
  21567. and transfers control to the indicated coroutine, resuming its actions at the
  21568. freesing point.
  21569. A new coroutine instance is initially frozen at the start of its main
  21570. function.
  21571. A primitive detach is available, which freezes the current coroutine actions
  21572. and transfers control to the main program.
  21573. A coroutine is terminated when its main function is terminated normally (not
  21574. frozen by resume or detach). Resuming a terminated object leads to a runtime
  21575. error.
  21576. Automatic variables declared in a class method and alllocated on the stack are
  21577. not availablee outside the couroutine instance. A variable declared in the
  21578. main function of the program, is not available within the other coroutines.
  21579. The main program is in fact a coroutine with the main function defining its
  21580. actions.
  21581. There must be no register variables, because registers will not be preserved
  21582. changing coroutines. 
  21583.  
  21584.  
  21585. Implementation
  21586.  
  21587.  
  21588. The implementation consists of the coroutine class, the two primitives resume
  21589. and detach, and a few functions for internal use. Each coroutine, as the main
  21590. program, has its own stack. Switching between coroutines is done by switching
  21591. stacks. Whenever a coroutine is activated, the activating coroutine's stack is
  21592. saved in memory, and the activated coroutine's previously stored stack is
  21593. copied to the stack segment.
  21594. Listing 1 contains the definitions. The variables of the coroutine class are
  21595. used for keeping track of the passive stack. stkSegment and stkOffset are the
  21596. segment and offset address of the area where the stack is stored, while
  21597. stkSize holds the size of the used part of the stack. 
  21598. Listing 2 shows the implementation. There are three internal pointers in the
  21599. implementation file: PREVIOUS referencing the coroutine to switch from,
  21600. CURRENT referencing the new and active coroutine, and MAINC referencing the
  21601. main program. 
  21602. When a new process is created as a coroutine subclass instance, the stack of
  21603. the new process is initialized by coroutine::coroutine(). Space for the
  21604. initial stack is allocated on the heap, and the addresses and the size are
  21605. stored in the base class variables. This stack is initialized, so that on
  21606. activation, the stack is copied from the heap, and control is passed to the
  21607. superMain function.
  21608. The trick is to store the address of the starting function (startProcess) on
  21609. the stack as a return address. When the stack-switching function terminates,
  21610. control returns to this address as if it was calling the function. This
  21611. function in turn starts the superMain function, which calls the main function
  21612. of the subclass. The superMain function also takes care of necessary
  21613. terminating actions. A coroutine may be deleted. In that case the
  21614. coroutine::~coroutine() method releases the stack area.
  21615. The resume primitive switches control from the active coroutine to the new
  21616. current specified as parameter to the primitve. At first, the size of the
  21617. current stack is computed, and space for storage is allocated on the heap. The
  21618. PREVIOUS and CURRENT variables are set, pointing to the two coroutines
  21619. involved. The assembler function ctxtswc is callled to do the actual stack
  21620. chnge. On return, the new coroutine is CURRENT and active, and the area for
  21621. stack storoage is released. The detach primitive resumes MAINC, the main
  21622. program.
  21623. You find ctxtswc in Listing 3. The global variables CURRENT and PREVIOUS are
  21624. imported by ctxtswc. Through these pointers, the stack storage area of the two
  21625. coroutines involved, are accessible.
  21626. ctxtswc works by copying the current system stack to the area referenced in
  21627. the PREVIOUS coroutine instance. This copy of the stack now contains the
  21628. return address from the ctxtswc call. Then the stack of the new CURRENT
  21629. coroutine is copied from the storage area to the system stack area, and the SP
  21630. register is reset. This stack now contains the return address from the ctxtswc
  21631. call, last time it was called by the coroutine. Therefore, when ctxtswc
  21632. returns, program execution continues where the resumed coroutine was stopped.
  21633. This version is written for the compact memory model. Minor changes in ctxtswc
  21634. and some of the C++ functions are necessary for other memory models.
  21635.  
  21636.  
  21637. Simulation
  21638.  
  21639.  
  21640.  
  21641. With the tools described in the previous sections it is possible to model
  21642. interacting real-world processes in a quasi-parallel program. In such a model
  21643. you can experiment. By changing the number of processes, the behavior of each,
  21644. and the input data you can test the model to gain insight into how the real
  21645. system would perform under different circumstances.
  21646. Simulating real processes in a computer model is often cheaper than doing real
  21647. life experiments. In some cases it is not even possible to experiment with the
  21648. real system. Consider the case of a nuclear reactor. In order to study the
  21649. behavior of the reactor operators in stress situations, e.g. when serious
  21650. problems occur, it would be necessary to induce problems in the reactor. We
  21651. cannot do that, for obvious reasons. Instead a control room connected to a
  21652. computer model, simulating the reactor, is used in experiments and training.
  21653. Real systems are often very complex. When creating a model, it is necessary to
  21654. include those features that are crucial to the model's operation, and avoid
  21655. insignificant details. Otherwise the simulation results may be of no value.
  21656. The time aspect of simulation, which is very important in most real systems,
  21657. is not handled within the coroutine concept. In the next sections, I will
  21658. introduce a set of tools for modelling processes in simulated time with C++.
  21659.  
  21660.  
  21661. An Example
  21662.  
  21663.  
  21664. This is a hypothetical system consisting of a number of physicians working
  21665. together in a clinic receiving patients. The system is simplified in order to
  21666. keep the example reasonably small in size. 
  21667. The physicians receive patients from 8 A.M. until 4 P.M. Then the physicians
  21668. work until no more patients are waiting. The patients arrive one at a time. On
  21669. arrival the patient goes to the waiting room. If there is a physician in the
  21670. lunchroom, he is summoned at once. Otherwise the patient waits for his turn.
  21671. When a patient leaves the clinic, the physician will see the next patient. If
  21672. nobody is waiting, he will go to the lunchroom for a coffee break.
  21673. The physicians, observing that most of the day the waiting room is full, want
  21674. to know if another physician should be invited to join the group. They are
  21675. losing business because the patients must wait too long, but they are not sure
  21676. if this might lead to an increasing number of coffee breaks.
  21677. The model of this system consists of a clinic object, a number of physician
  21678. objects, and the patients. For each of these objects, there is a set of
  21679. actions controlling the objects and their interactions. As the objects operate
  21680. in simulated time, their classes are derived from the class process, a
  21681. subclass of the class coroutine, which contains the information necessary to
  21682. support the time concept. Instead of the resume and detach primitives, a small
  21683. set of more advanced primitives will be used. These are based on resume and
  21684. detach, but are more suitable for simulations in time.
  21685. Most simulation models depend on some kind of input data that specifies when
  21686. the significant events occur. In this case, the important events are the
  21687. arrivals of patients, and the duration of a consultation. It is possible to
  21688. collect real data and use the information directly as input to the simulation
  21689. model. Another solution is to analyze the input data and find the events'
  21690. probability distributions. Input data may then be generated using random
  21691. generators. The physicians have observed the arrival pattern of patients and
  21692. found that patients arrive according to a Poisson distribution, and they have
  21693. found the expected number of patients each minute. They have also found that
  21694. the duration of each consultation is uniformly distributed over an interval.
  21695. A simulation model should always be validated by comparing the results of a
  21696. simulation with real input data to real system results. If there are
  21697. discrepancies, the model must be modified.
  21698. The implementation of the clinic system is found in Listing 4. The
  21699. implementation consist of three kinds of processes, defined by the classes
  21700. clinic, physician, and patient derived from the process base class. Each class
  21701. has a constructor, and a main function describing the process actions. There
  21702. is a main program which creates the process instances, starts the simulation
  21703. and presents the results. This example just finds the average waiting time of
  21704. the patients. Two functions are used to generate input to the model. The
  21705. treatmentPeriod function returns a randomly-chosen value indicating the
  21706. duration of a consultation, while periodBeforeNextArrival returns the
  21707. randomly-chosen period between two arrivals.
  21708. The clinic class references two FIFO queues, chain lunchRoom and waitingRoom,
  21709. where the physicians and patients are kept while waiting. In addition, there
  21710. are variables used for collecting statistical data. The physician and patient
  21711. classes have a pointer to the clinic, and variables for statistical data.
  21712. The physician and patient constructors set the clinic pointer. The clinic
  21713. constructor creates the physician processes and schedules them to run at once.
  21714. The constructors also initialize statistical variables.
  21715. The clinic main function describes the clinic actions. The main task is
  21716. generating new patients. Its main loop will run until simulated time
  21717. (currentTime) reaches the clinic's closing time. In the loop a new patient is
  21718. created, sent to the waiting room, and activated. If there is a physician in
  21719. the lunchRoom she is summoned and activated. Then the clinic process
  21720. reschedules itself and waits until the next patient arrives, with the hold
  21721. primitive.
  21722. The physician process is an eternal loop. Each time the physician process
  21723. checks if the waitingRoom is empty. If so, she goes to the lunchRoom and has a
  21724. break by calling the passivate primitive. This primitive will suspend the
  21725. process until some other process activates it. If there are patients in the
  21726. waitingRoom, the physician gets the next patient, activates him and suspends
  21727. herself with the hold primitive while helping the patient. When the
  21728. consultation ends, she will see the patient out, activating him, and repeat
  21729. the loop. 
  21730. The patient process is simple. It is activated three times, first by the
  21731. clinic process when the patient is sent to the waitingRoom, then by the
  21732. physician on leaving the waitingRoom, and last by the physician when the
  21733. consultation is over. Each time, the patient process records the time of the
  21734. event for statistical purposes before passivating itself. (When the patient
  21735. main function terminates, there is an implicit passivate call.) 
  21736. The main program of the system first initializes the random generator
  21737. functions and the process library by calling initProcesses. Then it suspends
  21738. itself until opening hours when a clinic process is created and activated. The
  21739. main program again suspends itself for a long period, until the clinic is
  21740. closed and all patients have left. At last statistical data are processed and
  21741. displayed.
  21742. In this model, each process acts on its own, performing actions. Between
  21743. events, the processes are suspended waiting to be restarted, either
  21744. automatically as with the hold primitive, or by another process with the
  21745. activate primitive. It is much easier to understand the sequence of events for
  21746. each process and to describe each process separately as in this example, than
  21747. to describe the whole system in one piece. Besides, you can modify the model
  21748. to include other objects such as secretaries, surgeons, several patient types,
  21749. and other action sequences.
  21750. After validating the model, you can simulate and investigate different
  21751. scenarios by changing the number of physicians, the arrival pattern of
  21752. patients, consultation time etc. It is easy to include statements to collect
  21753. other types of statistical data, such as the number of minutes spent in the
  21754. lunchroom, and the distribution of patient waiting times.
  21755.  
  21756.  
  21757. The Process Extensions
  21758.  
  21759.  
  21760. I created the following extensions supporting processes:
  21761. Each process is defined as a subclass of the process base class.
  21762. A function initProcesses initializes the process library.
  21763. A virtual function main in descendants of the process class contains the
  21764. actions of the process.
  21765. activate(process*,float) schedules the indicated process to run at the
  21766. specified point of time. If it is already scheduled to run, it is rescheduled.
  21767. If the current process is activated, it is suspended and rescheduled.
  21768. hold(float) suspends and reschedules the current process to run after the
  21769. indicated period of time.
  21770. passivate suspends the current process without rescheduling.
  21771. currentTime returns current simulated time.
  21772. Whenever the current process is suspended, simulated time is increased to the
  21773. time of the next scheduled process, and this process is restarted. If no
  21774. process is scheduled to run, a runtime error occurs. In this way simulated
  21775. time grows from 0 in varying steps as controls are switched from process to
  21776. process.
  21777. In addition, a few primitives not used in this example, are available
  21778. cancel(process *) deschedules the process indicated. Cancelling the current
  21779. process is the same as passivate.
  21780. currentProcess returns a pointer to the current process object. 
  21781. mainProcess returns a pointer to the main program process object. The main
  21782. program in itself is a process, and can be treated as such. 
  21783. resume(coroutine*) and detach may be used with processes as the process class
  21784. is derived from the coroutine class. Avoid this, it might lead to unexpected
  21785. activation sequences.
  21786.  
  21787.  
  21788. Implementation
  21789.  
  21790.  
  21791. In Listing 5 you find the process class definition. The process class is a
  21792. coroutine subclass with a single time variable which contains scheduled time
  21793. to run. It is initially set by the process constructor. 
  21794. Listing 6 contains the implementation of the process library. The data
  21795. structure of the process module is a priority queue SOS (The Sequence Set)
  21796. where the scheduled processes are kept sorted in ascending order on the time
  21797. variable. The current executing process is always the first object in the SOS.
  21798. There is a process* MAINP which is used for referencing the main program as a
  21799. process. CURRENT and MAINC are imported from the coroutine module. The
  21800. initProcesses function initializes the data structure. A process object
  21801. referencing the main program is created and inserted at the head of the SQS.
  21802. MAINC and CURRENT used for referencing the main program and the current
  21803. coroutine, are set to reference this main program process. 
  21804. The four scheduling primitives activate, hold, passivate, and cancel are
  21805. implemented as functions manipulating the SQS. activate inserts a process in
  21806. the priority queue, hold removes and reinserts the current process, cancel
  21807. removes any process, and passivate removes the current process. Whenever the
  21808. current process is suspended, control is transferred to the next. 
  21809. The three information primitives mainProcess, currentProcess, and currentTime
  21810. just return state information: which is the main program process, which is
  21811. currently executing, and what is the current simulated time. 
  21812. The internal superMain function is called from startProcess to manage the
  21813. user-defined main function. On termination, the process is removed from the
  21814. SQS and cannot be activated again. 
  21815.  
  21816.  
  21817. Some Comments on the Code listings
  21818.  
  21819.  
  21820.  
  21821. As shown, the implementations of the process primitives are very simple, once
  21822. the coroutine primitives are in place. If the need arise, you can create other
  21823. scheduling primitives of your own design. 
  21824. The code shown in the listings has been compiled and tested using Borland's
  21825. Turbo C++ 2.0 and Turbo Assembler 2.5, compact memory model. It is easy to
  21826. modify the system for other memory models. I have removed some include
  21827. statements referring to system functions and the description of the priority
  21828. type class chain. You may implement this yourself, otherwise a complete system
  21829. is available from the author on request. 
  21830. References
  21831. Birtwistle, Dahl, Myhrhaug, Nygaard: "SIMULA BEGIN," Auerbach, Phil., 1973.
  21832. (contains an introduction to the SIMULA programming language)
  21833.  
  21834. Listing 1 The base class coroutine definition
  21835. // COR.HPP
  21836. typedef unsigned int word;
  21837.  
  21838. class coroutine {
  21839. friend void startProcess(void);
  21840. friend void resume(coroutine*);
  21841. word stkSegment,stkOffset,stkSize;
  21842. virtual void main() {}
  21843. virtual void superMain() {}
  21844. public:
  21845. coroutine(void);
  21846. ~coroutine(void);
  21847. };
  21848. void resume(coroutine*);
  21849. void detach(void);
  21850.  
  21851. // End of File
  21852.  
  21853.  
  21854. Listing 2 The base-class coroutine implementation
  21855. // COR.CPP
  21856. #include "cor.hpp"
  21857. #include ...
  21858.  
  21859. extern void ctxtswc(void);
  21860. coroutine *MAINC=new coroutine();
  21861. coroutine *PREVIOUS,*CURRENT=MAINC;
  21862.  
  21863. coroutine::coroutine() {
  21864. word *sp,*stkBase;
  21865. stkSize= 2 * sizeof(word);
  21866. stkBase=(word *) farmalloc(stkSize);
  21867. stkSegment=FP_SEG(stkBase);
  21868. stkOffset=FP_OFF(stkBase);
  21869. sp=stkBase + 2;
  21870. *--sp=(word) startProcess;}
  21871.  
  21872. coroutine::~coroutine() {
  21873. delete(MK_FP(stkSegment,stkOffset));}
  21874.  
  21875. void coroutine::superMain(void) {
  21876. main();
  21877. resume(MAINC);
  21878. FATAL("terminated coroutine resumed");}
  21879.  
  21880. void startProcess(void) {
  21881. CURRENT->superMain();}
  21882.  
  21883. void resume(coroutine* rc)
  21884. { word sp,*stkBase;
  21885. if ((CURRENT != NULL) && (rc != CURRENT) &&
  21886. (rc != NULL)) {
  21887. sp=_SP;
  21888.  
  21889. CURRENT->stkSize= _stklen - sp + 4;
  21890. stkBase=
  21891. (word*)farmalloc(CURRENT->stkSize);
  21892. CURRENT->stkSegment=FP_SEG(stkBase);
  21893. CURRENT->stkOffset=FP_OFF(stkBase);
  21894. PREVIOUS=CURRENT;
  21895. CURRENT=rc;
  21896. ctxtswc();
  21897. delete(MK_FP(CURRENT->stkSegment,
  21898. CURRENT->stkOffset));
  21899. }
  21900. }
  21901.  
  21902. void detach(void) {
  21903. resume(MAINC);}
  21904.  
  21905. // End of File
  21906.  
  21907.  
  21908. Listing 3 The assembler routine for changing stacks
  21909. ; ctxtswc will do a contextswitch by changing stacks.
  21910. ; the stack in use will be stored on the heap, and the
  21911. ; new stack which is previously stored on the heap,
  21912. ; will be loaded.
  21913. .MODEL COMPACT
  21914. .CODE
  21915. EXTRN _stklen:word
  21916. EXTRN _PREVIOUS:dword
  21917. EXTRN _CURRENT:dword
  21918. PUBLIC @ctxtswc$qv
  21919. @ctxtswc$qv PROC NEAR
  21920. push bp ; save bp
  21921. mov cx,_stklen ; put nmbr of bytes
  21922. sub cx,sp ; in cx
  21923. les bx,dword ptr DGROUP:_PREVIOUS
  21924. mov word ptr es:[bx+4],cx ; save sz of usedstk
  21925. mov di,word ptr es:[bx+2] ; set adr for stk
  21926. mov es,word ptr es:[bx] ; copy in di and es
  21927. mov ax,ds ; save ds
  21928. mov bx,ss ; set adr of stk in
  21929. mov ds,bx ; ds
  21930. mov si,sp ; and si
  21931. rep movsb ; do copy
  21932. mov ds,ax ; reset ds
  21933. les bx,dword ptr DGROUP:_CURRENT
  21934. mov cx,word ptr es:[bx+4] ; get sz of stk copy
  21935. mov ax,ds ; save ds
  21936. mov sp,_stklen
  21937. sub sp,cx ; set stkPtr
  21938. mov si,word ptr es:[bx+2] ; set adr of stored
  21939. mov ds,word ptr es:[bx] ; stk in si and ds
  21940. mov di,sp ; set dest for copy
  21941. mov dx,ss ; di=stkPtr
  21942. mov es,dx ; and es=stkSeg
  21943. rep movsb ; do copy
  21944. mov ds,ax ; reset ds
  21945. pop bp ; reset bp
  21946. ret
  21947. @ctxtswc$qv ENDP
  21948.  
  21949. END
  21950. ; End of File
  21951.  
  21952.  
  21953. Listing 4 Simulation of a medical clinic
  21954. #include "sim.hpp"
  21955. #include ...
  21956. #define nmbrOfPhysicians 4
  21957. #define openingHours 480.0
  21958. #define openPeriod 480.0
  21959. #define consultationLow 5.0
  21960. #define consultationHigh 20.0
  21961. #define arrivalsExpected 0.25
  21962.  
  21963. float treatmentPeriod(void)
  21964. { return(fUniform(consultationLow,consultationHigh));
  21965. }
  21966.  
  21967. float periodBeforeNextArrival(void)
  21968. { return(fNegexp(arrivalsExpected)); }
  21969.  
  21970. class clinic : public process
  21971. {public:chain lunchRoom,waitingRoom;
  21972. float closingTime,totalWaitingTime;
  21973. int totalNmbrOfPatients;
  21974. clinic(int nrOfPhysicians,float Period);
  21975. void main(void);
  21976. };
  21977.  
  21978. class physician : public process
  21979. { clinic* theClinic;
  21980. public: physician(clinic* cl) {theClinic=cl;}
  21981. void main(void);
  21982. };
  21983.  
  21984. class patient : public process
  21985. { clinic *theClinic;
  21986. public: float startWaitingAt,startTreatmentAt,
  21987. finishedAt;
  21988. patient(clinic* cl) {theClinic=cl;}
  21989. void main(void);
  21990. };
  21991.  
  21992. clinic::clinic(int nrOfPhysicians, float Period)
  21993. { int i;
  21994. totalWaitingTime=0.0;
  21995. totalNmbrOfPatients=0;
  21996. closingTime=currentTime() + Period;
  21997. for (i=0;i<nrOfPhysicians;i++)
  21998. activate(new physician(this),currentTime());
  21999. }
  22000.  
  22001. void clinic::main(void)
  22002. { patient* ptnt;
  22003. physician* phsn;
  22004. hold(periodBeforeNextArrival());
  22005. while (currentTime() < closingTime)
  22006. { ptnt=new patient(this);
  22007. totalNmbrOfPatients++;
  22008.  
  22009. waitingRoom.append(ptnt);
  22010. activate(ptnt,currentTime());
  22011. if (!lunchRoom.empty())
  22012. { phsn=(physician*)lunchRoom.
  22013. getfirst();
  22014. activate(phsn,currentTime());
  22015. }
  22016. hold(periodBeforeNextArrival());
  22017. }
  22018. }
  22019.  
  22020. void physician::main(void)
  22021. { patient *ptnt;
  22022. while(1)
  22023. { if (theClinic->waitingRoom.empty())
  22024. { theClinic->lunchRoom.append(this);
  22025. passivate();
  22026. }
  22027. else
  22028. { ptnt=(patient*)theClinic->waitingRoom.
  22029. getfirst();
  22030. activate(ptnt,currentTime());
  22031. hold(treatmentPeriod());
  22032. activate(ptnt,currentTime());
  22033. }
  22034. }
  22035. }
  22036.  
  22037. void patient::main(void)
  22038. {
  22039. startWaitingAt=currentTime();
  22040. passivate();
  22041. startTreatmentAt=currentTime();
  22042. passivate();
  22043. finishedAt=currentTime();
  22044. theClinic->totalWaitingTime+=
  22045. (startTreatmentAt-startWaitingAt);
  22046. }
  22047.  
  22048. void main(void)
  22049. { clinic *clnc;
  22050. initRandom();
  22051. initProcesses();
  22052. hold(openingHours - currentTime());
  22053. activate(clnc=new clinic(
  22054. nmbrOfPhysicians,openPeriod),
  22055. currentTime());
  22056. hold(10000.0);
  22057. cout << "Total number of patients ="
  22058. << clnc->totalNmbrOfPatients << end1;
  22059. cout << "Average waiting time ="
  22060. << clnc->totalWaitingTime/
  22061. clnc->totalNmbrOfPatients << end1;
  22062. }
  22063.  
  22064. // End of File
  22065.  
  22066.  
  22067. Listing 5 The class process definition
  22068.  
  22069. // SIM.HPP
  22070. #include "cor.hpp"
  22071.  
  22072. class process : private coroutine
  22073. {
  22074. friend void startProcess(void);
  22075. friend float currentTime(void);
  22076. friend void hold(float interval);
  22077. friend void passivate(void);
  22078. friend void activate(process *p,float time);
  22079. friend void cancel (process *p);
  22080. float time;
  22081. virtual void superMain(void);
  22082. virtual void main(void) {}
  22083. public: process (void) {time=0.0;}
  22084. ~process (void) {}
  22085. };
  22086.  
  22087. void initProcesses(void);
  22088. void activate(process *p,float time);
  22089. void hold(float interval);
  22090. void passivate(void);
  22091. void cancel (process *p);
  22092. process* mainProcess(void);
  22093. process* currentProcess (void);
  22094. float currentTime(void);
  22095.  
  22096. // End of File
  22097.  
  22098.  
  22099. Listing 6 A library implementation for the clinic simulation
  22100. // SIM.CPP
  22101. #include "sim.hpp"
  22102. #include ...
  22103.  
  22104. extern coroutine* CURRENT;
  22105. extern coroutine* MAINC;
  22106. process* MAINP=NULL;
  22107. chain SQS;
  22108.  
  22109. void initProcesses(void)
  22110. { if (MAINP == NULL)
  22111. { MAINP=newprocess();
  22112. SQS.put(MAINP,0);
  22113. delete MAINC;
  22114. MAINC=CURRENT=(coroutine*)MAINP;
  22115. }
  22116. }
  22117.  
  22118. process* mainProcess(void)
  22119. { return(MAINP); }
  22120.  
  22121. process* currentProcess(void)
  22122. { return((process *) CURRENT); }
  22123.  
  22124. float currentTime(void)
  22125. { return( ((process*)CURRENT)->time); }
  22126.  
  22127. void hold(float interval)
  22128.  
  22129. { if (interval > 0)
  22130. { SQS.get(CURRENT);
  22131. ((process *)CURRENT)->time+=interval;
  22132. SQS.put(CURRENT,
  22133. ((process *)CURRENT)->time);
  22134. resume((coroutine*)SQS.first());
  22135. }
  22136. }
  22137.  
  22138. void passivate(void)
  22139. { SQS.get(CURRENT);
  22140. if (SQS.first() == NULL)
  22141. FATAL("Sequence Set is empty");
  22142. resume((coroutine *)SQS.first());
  22143. }
  22144.  
  22145. void activate(process *p,float time)
  22146. { if (p == CURRENT)
  22147. hold(time - currentTime());
  22148. else
  22149. { SQS.get(p);
  22150. p->time= ((time > currentTime())?
  22151. time : currentTime());
  22152. SQS.put(p,p->time);
  22153. }
  22154. }
  22155.  
  22156. void cancel(process *p)
  22157. { SQS.get(p);
  22158. if (p == CURRENT)
  22159. { if (SQS.first() == NULL)
  22160. FATAL("Sequence Set is empty");
  22161. resume((coroutine *)SQS.first());
  22162. }
  22163. }
  22164.  
  22165. void process::superMain(void)
  22166. { main();
  22167. passivate();
  22168. FATAL("terminated process activated");}
  22169.  
  22170. // End of File
  22171.  
  22172.  
  22173.  
  22174.  
  22175.  
  22176.  
  22177.  
  22178.  
  22179.  
  22180.  
  22181.  
  22182.  
  22183.  
  22184.  
  22185.  
  22186.  
  22187.  
  22188.  
  22189.  
  22190.  
  22191.  
  22192. Standard C
  22193.  
  22194.  
  22195. Time Formatting Functions
  22196.  
  22197.  
  22198.  
  22199.  
  22200. P.J. Plauger
  22201.  
  22202.  
  22203. P.J. Plauger is senior editor of The C Users Journal. He is convenor of the
  22204. ISO C standards committee, WG14, and active on the C++ committee, WG21. His
  22205. latest books are The Standard C Library, published by Prentice-Hall, and ANSI
  22206. and ISO Standard C (with Jim Brodie), published by Microsoft Press. You can
  22207. reach him at pjp@plauger.com.
  22208.  
  22209.  
  22210. This is the last of three installments on the functions declared in <time.h>.
  22211. (See "The Header <time.h>," CUJ January 1993 and "Time Conversion Functions ,"
  22212. CUJ February 1993.) I conclude by describing the functions that convert
  22213. encoded times to human-readable text strings.
  22214. Standard C provides an extraordinary ability to capture times and dates. It
  22215. also lets you convert freely between scalar and component forms. That lets you
  22216. do arithmetic on components such as days or months, leaving to the library the
  22217. hard work of correcting for calendar irregularities. The Standard C library
  22218. even lets you switch between local and universal (UTC or GMT) time zones,
  22219. correcting as needed for Daylight Savings Time. (All that assumes that the
  22220. library has some way of learning time zone information from the environment,
  22221. of course.)
  22222. The icing on the cake is the ability to convert times to human-readable form.
  22223. C has long had the functions ctime and asctime to perform this useful service.
  22224. Committee X3J11 added the more general function strftime. It lets you pick the
  22225. time and date components you wish to include. It also adapts to the current
  22226. locale. Thus Standard C now encourages programs that print time information in
  22227. the most useful form for each culture. (Again, that assumes that the library
  22228. has a nontrivial implementation of the locale machinery.)
  22229. The topic for this month is the remaining functions declared in
  22230. <time.h>--those that convert times to text strings. They are not as hairy as
  22231. the conversion functions I described last month, but they have their own
  22232. complexities.
  22233.  
  22234.  
  22235. What the C Standard Says
  22236.  
  22237.  
  22238.  
  22239.  
  22240. 7.12.3 Time conversion functions
  22241.  
  22242.  
  22243. Except for the strftime function, these functions return values in one of two
  22244. static objects: a broken-down time structure and an array of char. Execution
  22245. of any of the functions may overwrite the information returned in either of
  22246. these objects by any of the other functions. The implementation shall behave
  22247. as if no other library functions call these functions.
  22248.  
  22249.  
  22250. 7.12.3.1 The asctime function
  22251.  
  22252.  
  22253.  
  22254.  
  22255. Synopsis
  22256.  
  22257.  
  22258. #include <time.h>
  22259. char *asctime
  22260. (const struct tm *timeptr);
  22261.  
  22262.  
  22263. Description
  22264.  
  22265.  
  22266. The asctime function converts the broken-down time in the structure pointed to
  22267. by timeptr into a string in the form
  22268. Sun Sep 16 01:03:52 1973\n\0
  22269. using the equivalent of the following algorithm.
  22270. char *asctime
  22271. (const struct tm *timeptr)
  22272. {
  22273. static const char wday_name[7][3] = {
  22274. "Sun", "Mon", "Tue", "Wed",
  22275. "Thu", "Fri", "Sat"
  22276. };
  22277.  
  22278. static const char mon_name[12][3] = {
  22279. "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  22280. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  22281. };
  22282. static char result[26];
  22283.  
  22284. sprintf(result,
  22285. "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
  22286. wday_name[timeptr->tm_wday],
  22287. mon_name[timeptr->tm_mon],
  22288. timeptr->tm_mday, timeptr->tm_hour,
  22289. timeptr->tm_min, timeptr->tm_sec,
  22290. 1900 + timeptr->tm_year);
  22291. return result;
  22292. }
  22293.  
  22294.  
  22295. Returns
  22296.  
  22297.  
  22298. The asctime function returns a pointer to the string.
  22299.  
  22300.  
  22301. 7.12.3.2 The ctime function
  22302.  
  22303.  
  22304.  
  22305.  
  22306. Synopsis
  22307.  
  22308.  
  22309. #include <time.h>
  22310. char *ctime(const time_t *timer);
  22311.  
  22312.  
  22313. Description
  22314.  
  22315.  
  22316. The ctime function converts the calendar time pointed to by timer to local
  22317. time in the form of a string. It is equivalent to
  22318. asctime (localtime(timer))
  22319.  
  22320.  
  22321. Returns
  22322.  
  22323.  
  22324. The ctime function returns the pointer returned by the asctime function with
  22325. that broken-down time as argument. Forward references: the localtime function
  22326. (7.12.3.4).
  22327. ....
  22328.  
  22329.  
  22330. 7.12.3.5 The strftime function
  22331.  
  22332.  
  22333.  
  22334.  
  22335. Synopsis
  22336.  
  22337.  
  22338. #include <time.h>
  22339. size_t strftime(char *s,
  22340.  
  22341. size_t maxsize,
  22342. const char *format,
  22343. const struct tm *timeptr);
  22344.  
  22345.  
  22346. Description
  22347.  
  22348.  
  22349. The strftime function places characters into the array pointed to by s as
  22350. controlled by the string pointed to by format. The format shall be a multibyte
  22351. character sequence, beginning and ending in its initial shift state. The
  22352. format consists of zero or more conversion specifiers and ordinary multibyte
  22353. characters. A conversion specifier consists of a % character followed by a
  22354. character that determines the behavior of the conversion specifier. All
  22355. ordinary multibyte characters (including the terminating null character) are
  22356. copied unchanged into the array. If copying takes place between objects that
  22357. overlap, the behavior is undefined. No more than maxsize characters are placed
  22358. into the array. Each conversion specifier is replaced by appropriate
  22359. characters as described in the following list. The appropriate characters are
  22360. determined by the LC_TIME category of the current locale and by the values
  22361. contained in the structure pointed to by timeptr.
  22362. "%a" is replaced by the locale's abbreviated weekday name.
  22363. "%A" is replaced by the locale's full weekday name.
  22364. "%b" is replaced by the locale's abbreviated month name.
  22365. "%B" is replaced by the locale's full month name.
  22366. "%c" is replaced by the locale's appropriate date and time representation.
  22367. "%d" is replaced by the day of the month as a decimal number (01-31).
  22368. "%H" is replaced by the hour (24-hour clock) as a decimal number (00-23).
  22369. "%I" is replaced by the hour (12-hour clock) as a decimal number (01-12).
  22370. "%j" is replaced by the day of the year as a decimal number (001-366).
  22371. "%m is replaced by the month as a decimal number (01-12).
  22372. "%M" is replaced by the minute as a decimal number (00-59).
  22373. "%p" is replaced by the locale's equivalent of the AM/PM designations
  22374. associated with a 12-hour clock.
  22375. "%S" is replaced by the second as a decimal number (00-61).
  22376. "%U" is replaced by the week number of the year (the first Sunday as the first
  22377. day of week 1) as a decimal number (00-53).
  22378. "%w" is replaced by the weekday as a decimal number (0-6), where Sunday is 0.
  22379. "%W" is replaced by the week number of the year (the first Monday as the first
  22380. day of week 1) as a decimal number (00-53).
  22381. "%x" is replaced by the locale's appropriate date representation.
  22382. "%X" is replaced by the locale's appropriate time representation.
  22383. "%y" is replaced by the year without century as a decimal number (00-99).
  22384. "%Y" is replaced by the year with century as a decimal number.
  22385. "%Z" is replaced by the time zone name or abbreviation, or by no characters if
  22386. no time zone is determinable.
  22387. "%%" is replaced by %.
  22388. If a conversion specifier is not one of the above, the behavior is undefined.
  22389.  
  22390.  
  22391. Returns
  22392.  
  22393.  
  22394. If the total number of resulting characters including the terminating null
  22395. character is not more than maxsize, the strftime function returns the number
  22396. of characters placed into the array pointed to by s not including the
  22397. terminating null character. Otherwise, zero is returned and the contents of
  22398. the array are indeterminate.
  22399.  
  22400.  
  22401. The Function strftime
  22402.  
  22403.  
  22404. The one complicated function declared in <time.h> (from the outside, at least)
  22405. is strftime. You use it to generate a text representation of a time and date
  22406. from a struct tm under control of a format string. In this sense, it is
  22407. modeled after the print functions declared in <stdio. h>. It differs in two
  22408. important ways:
  22409. strftime does not accept a variable argument list. It obtains all time and
  22410. date information from one argument.
  22411. The behavior of strftime can vary considerably among locales. The locale
  22412. category LC_TIME can, for example, specify that the text form of all dates
  22413. follow the conventions of the French culture.
  22414. For example, the code fragment:
  22415. char buf[100];
  22416.  
  22417. strftime(buf, sizeof buf,
  22418. "%A, %x", localtime(&t0));
  22419. might store in buf any of:
  22420. Sunday, 02 Dec 1979
  22421. dimanche, le 2 decembre 1979
  22422. Weekday 0, 02/12/79
  22423. If your goal is to display times and dates in accordance with local custom,
  22424. then strftime gives you just the flexibility you need. You can even write
  22425. multi-byte-character sequences between the conversion specifiers. That lets
  22426. you convert dates to Kanji and other large character sets.
  22427. Here are the conversion specifiers defined for strftime. I follow each with an
  22428. example of the text it produces. The examples are all from P.J. Plauger and
  22429. Jim Brodie, ANSI and ISO Standard C, Microsoft Press, 1992. All assume the "C"
  22430. locale and the date and time Sunday, 2 December 1979 at 06:55:15 AM EST:
  22431. %a -- the abbreviated weekday name (Sun)
  22432. %A -- the full weekday name (Sunday)
  22433.  
  22434. %b -- the abbreviated month name (Dec)
  22435. %B -- the full month name (December)
  22436. %c -- the date and time (Dec 2 06:55:15 1979)
  22437. %d -- the day of the month (02)
  22438. %H -- the hour of the 24-hour day (06)
  22439. %I -- the hour of the 12-hour day (06)
  22440. %j -- the day of the year, from 001 (335)
  22441. %m -- the month of the year, from 01 (12)
  22442. %M -- the minutes after the hour (55)
  22443. %p -- the AM/PM indicator (AM)
  22444. %S -- the seconds after the minute (15)
  22445. %U -- the Sunday week of the year, from 00 (48)
  22446. %w -- the day of the week, from 0 for Sunday (0)
  22447. %W -- the Monday week of the year, from 00 (47)
  22448. %x -- the date (Dec 2 1979)
  22449. %X -- the time (06:55:15)
  22450. %y -- the year of the century, from 00 (79)
  22451. %Y -- the year (1979)
  22452. %Z -- the time zone name, if any (EST)
  22453. %% -- the per cent character (%)
  22454.  
  22455.  
  22456. Using the Formatting Functions
  22457.  
  22458.  
  22459. Note that the two functions that return a value of type pointer to char return
  22460. a pointer to a static data object. Thus, a call to one of these functions can
  22461. alter the value stored on behalf of an earlier call to another (or the same)
  22462. function. Be careful to copy the value stored in one of these shared data
  22463. objects if you need the value beyond a conflicting function call.
  22464. asctime--(The asc comes from ASCII, which is now a misnomer.) Use this
  22465. function to generate the text form of the date represented by the argument
  22466. (which points to a broken-down time). The function returns a pointer to a
  22467. null-terminated string that looks like "Sun Dec 2 06:55:15 1979\n". This is
  22468. equivalent to calling strftime with the format "%a %c\n" in the "C" locale.
  22469. Call asctime if you want the English-language form regardless of the current
  22470. locale. Call strftime if you want a form that changes with locale. See the
  22471. warning about shared data objects, above.
  22472. ctime--ctime(pt) is equivalent to the expression asctime(localtime(pt)). You
  22473. use it to convert a calendar time directly to a text form that is independent
  22474. of the current locale. See the warning about shared data objects, above.
  22475. strftime--This function generates a null-terminated text string containing the
  22476. time and date information that you specify. You write a format string argument
  22477. to specify a mixture of literal text and converted time and date information.
  22478. You specify a broken-down time to supply the encoded time and date
  22479. information. The category LC_TIME in the current locale determines the
  22480. behavior of each conversion.
  22481.  
  22482.  
  22483. Implementing the Conversion Functions
  22484.  
  22485.  
  22486. These functions convert encoded times to text strings in various ways. All
  22487. depend, in the end, on the internal function _Strftime to do the actual
  22488. conversion. What varies is the choice of locale. The function asctime (and, by
  22489. extension, the function ctime) convert times by a fixed format, following the
  22490. conventions of the "C" locale regardless of the current state of the locale
  22491. category LC_TIME. The function strftime, on the other hand, lets you specify a
  22492. format that directs the conversion of a broken-down time. It follows the
  22493. conventions of the current locale. Thus, one of the arguments to _Strftime
  22494. specifies the locale-specific time information (of type_Tinfo) to use.
  22495. Listing 1 shows the file asctime.c. It defines the function asctime that
  22496. formats a broken-down time the same way irrespective of the current locale.
  22497. The file also defines the data object _Times that specifies the
  22498. locale-specific time information. And it defines the internal data object
  22499. ctinfo, which replicates the time information for the "C" locale.
  22500. Listing 2 shows the file ctime.c. The function ctime simply calls localtime,
  22501. then asctime, to convert its time_t argument. Thus, it always follows the
  22502. conventions of the "C" locale.
  22503. Listing 3 shows the file strftime.c. The function strftime calls _Strftime,
  22504. using the locale-specific time information stored in_Times. Thus, its behavior
  22505. changes with locale.
  22506. Listing 4 shows the file xstrftim.c. It defines the internal function
  22507. _Strftime that does all the work of formatting time information. _Strftime
  22508. uses the macro PUT, defined at the top of the file xstrftim.c, to deliver
  22509. characters. The macro encapsulates the logic needed to copy generated
  22510. characters, count them, and limit the number delivered.
  22511. The internal function _Mbtowc, declared in <stdlib.h>, parses the format as a
  22512. multibyte string using state memory of type _Mbstate that you provide on each
  22513. call.
  22514. Listing 5 shows the file xgentime.c It defines the function _Gentime that
  22515. performs the actual conversions for _Strftime. The function _Gentime consists
  22516. primarily of a large switch statement that processes each conversion
  22517. separately.
  22518. Each conversion determines a pointer p to a sequence of characters that gives
  22519. the result of the conversion. It also stores a signed integer count at *pn. A
  22520. positive count instructs _Strftime to generate the designated sequence of
  22521. characters.
  22522. One source of generated characters is the function _Get_time, which selects a
  22523. field from one of the strings in the locale-specific time information. Another
  22524. is the internal function getval, also defined in the file xgentime.c, which
  22525. generates decimal integers. getval stores characters in the accumulator
  22526. provided by _Strftime.
  22527. Note that _Gentime includes a nonstandard addition. The conversion specifier
  22528. %D converts the day of the month with a leading space in place of a leading 0.
  22529. That's what asctime insists on.
  22530. _Gentime returns a negative count to instruct _Strftime to "push down" a
  22531. format string for a locale-specific conversion. Three conversions change with
  22532. locale: %c, %x, and %X. (The conversion %x, for example, becomes the format
  22533. string "%b %d %Y" in the "C" locale.) You express these conversions as format
  22534. strings that invoke the other conversions. Note that the function_Strftime
  22535. supports only one level of format stacking.
  22536. The other internal function in the file xgentime.c is wkyr. It counts weeks
  22537. from the start of the year for a given day of the year. The week can begin on
  22538. Sunday (wstart is 0) or Monday (wstart is 1). The peculiar logic avoids
  22539. negative arguments for the modulus and divide operators.
  22540.  
  22541.  
  22542. Conclusion
  22543.  
  22544.  
  22545. For over two and a half years, I have used this column for a single
  22546. purpose--to provide a guided tour of the Standard C library. Along the way, I
  22547. got out a book of the same name. About half these installments have been
  22548. excerpts from the completed book. With this installment, I bring to a close
  22549. that guided tour.
  22550. Starting next month, I'll pick up on other standards activities in the C
  22551. community. That's more than you might think. When I started this column, I was
  22552. a member of one ANSI standards committee. Now I find myself attending meetings
  22553. for two ANSI committees and three ISO committees. Don't think I do it because
  22554. it's unmitigated fun. I just think standards activities are too important
  22555. these days to ignore. I'll do my best to keep you informed too.
  22556. This article is excerpted in part from P.J. Plauger, The Standard C Library,
  22557. (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
  22558.  
  22559. Listing 1 asctime.c
  22560. /* asctime function */
  22561. #include "xtime.h"
  22562.  
  22563.  
  22564. /* static data */
  22565. static const char ampm[] = {":AM:PM"};
  22566. static const char days[] = {
  22567. ":Sun:Sunday:Mon:Monday:Tue:Tuesday:Wed:Wednesday"
  22568. ":Thu:Thursday:Fri:Friday:Sat:Saturday"};
  22569. static const char fmts[] = {
  22570. "%b %D %H:%M:%S %Y%b %D %Y%H:%M:%S"};
  22571. static const char isdst[] = {""};
  22572. static const char mons[] = {
  22573. ":Jan:January:Feb:February:Mar:March"
  22574. ":Apr:April:May:May:Jun:June"
  22575. ":Jul:July:Aug:August:Sep:September"
  22576. ":Oct:October:Nov:November:Dec:December"};
  22577. static const char zone[] =
  22578. {""}; /* adapt by default */
  22579. static_Tinfo ctinfo = {ampm, days, fmts, isdst, mons, zone};
  22580. _Tinfo_Times = {ampm, days, fmts, isdst, mons, zone};
  22581.  
  22582. char *(asctime)(const struct tm *t)
  22583. { /* format time as
  22584. "Day Mon dd hh:mm:ss yyyy\n" */
  22585. static char tbuf[] =
  22586. "Day Mon dd hh:mm:ss yyyy\n";
  22587.  
  22588. _Strftime(tbuf, sizeof (tbuf), "%a %c\n",
  22589. t, &ctinfo);
  22590. return (tbuf);
  22591. }
  22592.  
  22593. /* End of File */
  22594.  
  22595.  
  22596. Listing 2 ctime.c
  22597. /* ctime function */
  22598. #include <time.h>
  22599.  
  22600. char *(ctime)(const time_t *tod)
  22601. { /* convert calendar time to local text */
  22602. return (asctime(localtime(tod)));
  22603. }
  22604.  
  22605. /* End of File */
  22606.  
  22607.  
  22608. Listing 3 strftime.c
  22609. /* strftime function */
  22610. #include "xtime.h"
  22611.  
  22612. size_t (strftime)(char *s, size_t n,
  22613. const char *fmt, const struct tm *t)
  22614. { /* format time to string */
  22615. return (_Strftime(s, n, fmt, t, &_Times));
  22616. }
  22617.  
  22618. /* End of File */
  22619.  
  22620.  
  22621. Listing 4 xstrftim.c
  22622. /* _Strftime function */
  22623.  
  22624. #include <stdlib.h>
  22625. #include <string.h>
  22626. #include "xtime.h"
  22627.  
  22628. /* macros */
  22629. #define PUT(s, na) (void)(nput = (na), \
  22630. 0 < nput && (nchar += nput) <= bufsize ? \
  22631. (memcpy(buf, s, nput), buf += nput) : 0)
  22632.  
  22633. size_t_Strftime(char *buf, size_t bufsize, const char *fmt,
  22634. const struct tm *t, _Tinfo *tin)
  22635. { /* format time information */
  22636. const char *fmtsav, *s;
  22637. size_t len, lensav, nput;
  22638. size_t nchar = 0;
  22639.  
  22640. for (s = fmt, len = strlen(fmt), fmtsav = NULL; ; fmt = s)
  22641. { /* parse format string */
  22642. int n;
  22643. wchar_t wc;
  22644. _Mbsave state = {0};
  22645.  
  22646. while (0 < (n = _Mbtowc(&wc, s, len, &state)))
  22647. { /* scan for '%' or '\0' */
  22648. s += n, len -= n;
  22649. if (wc == '%')
  22650. break;
  22651. }
  22652. if (fmt < s) /* copy any literal text */
  22653. PUT(fmt, s - fmt - (0 < n ? 1 : 0));
  22654. if (0 < n)
  22655. { /* do the conversion */
  22656. char ac[20];
  22657. int m;
  22658. const char *p = _Gentime(t, tin, s++, &m, ac);
  22659.  
  22660. --len;
  22661. if (0 <= m)
  22662. PUT(p, m);
  22663. else if (fmtsav == NULL)
  22664. fmtsav = s, s = p, lensav = len,
  22665. len = -m;
  22666. }
  22667. if (0 == len && fmtsav == NULL n < 0)
  22668. { /* format end or bad multibyte char */
  22669. PUT("", 1); /* null termination */
  22670. return (nchar <= bufsize ? nchar - 1 : 0);
  22671. }
  22672. else if (0 == len)
  22673. s = fmtsav, fmtsav = NULL, len = lensav;
  22674. }
  22675. }
  22676.  
  22677. /* End of File */
  22678.  
  22679.  
  22680. Listing 5 xgentime.c
  22681. /* _Gentime function */
  22682. #include "xtime.h"
  22683.  
  22684.  
  22685. /* macros */
  22686. #define SUNDAY 0 /* codes for tm_wday */
  22687. #define MONDAY 1
  22688.  
  22689. static char *getval(char *s, int val, int n)
  22690. { /* convert a decimal value */
  22691. if (val < 0)
  22692. val = 0;
  22693. for (s += n, *s = '\0'; 0 <= --n; val /= 10)
  22694. *--s = val % 10 + '0';
  22695. return (s);
  22696. }
  22697.  
  22698. static int wkyr(int wstart, int wday, int yday)
  22699. { /* find week of year */
  22700. wday = (wday + 7 - wstart) % 7;
  22701. return (yday - wday + 12) / 7 - 1;
  22702. }
  22703.  
  22704. const char *_Gentime(const struct tm *t,
  22705. _Tinfo *tin, const char *s, int *pn, char *ac)
  22706. { /* format a time field */
  22707. const char *p;
  22708.  
  22709. switch (*s++)
  22710. { /* switch on conversion specifier */
  22711. case 'a': /* put short weekday name */
  22712. p = _Gettime(tin->_Days, t->tm_wday << 1, pn);
  22713. break;
  22714. case 'A': /* put full weekday name */
  22715. p = _Gettime(tin->_Days, (t->tm_wday << 1) + 1, pn);
  22716. break;
  22717. case 'b': /* put short month name */
  22718. p = _Gettime(tin->_Months, t->tm_mon << 1, pn);
  22719. break;
  22720. case 'B': /* put full month name */
  22721. p = _Gettime(tin->_Months, (t->tm_mon << 1) + 1, pn);
  22722. break;
  22723. case 'c': /* put date and time */
  22724. p = _Gettime(tin->_Formats, 0, pn),
  22725. *pn = -*pn;
  22726. break;
  22727. case 'd': /* put day of month, from 01 */
  22728. p = getval(ac, t->tm_mday, *pn = 2);
  22729. break;
  22730. case 'D': /* put day of month, from 1 */
  22731. p = getval(ac, t->tm_mday, *pn = 2);
  22732. if (ac[0] == '0')
  22733. ac [0] = ' ';
  22734. break;
  22735. case 'H': /* put hour of 24-hour day */
  22736. p = getval(ac, t->tm_hour, *pn = 2);
  22737. break;
  22738. case 'I': /* put hour of 12-hour day */
  22739. p = getval(ac, t->tm_hour % 12, *pn = 2);
  22740. break;
  22741. case 'j': /* put day of year, from 001 */
  22742. p = getval(ac, t->tm_yday + 1, *pn = 3);
  22743.  
  22744. break;
  22745. case 'm': /* put month of year, from 01 */
  22746. p = getval(ac, t->tm_mon + 1, *pn = 2);
  22747. break;
  22748. case 'M': /* put minutes after the hour */
  22749. p = getval(ac, t->tm_min, *pn = 2);
  22750. break;
  22751. case 'p': /* put AM/PM */
  22752. p = _Gettime(tin->_Ampm, 12 <= t->tm_hour, pn);
  22753. break;
  22754. case 'S': /* put seconds after the minute */
  22755. p = getval(ac, t->tm_sec, *pn = 2);
  22756. break;
  22757. case 'U': /* put Sunday week of the year */
  22758. p = getval(ac,
  22759. wkyr(SUNDAY, t->tm_wday, t->tm_yday),
  22760. *pn = 2);
  22761. break;
  22762. case 'w': /* put day of week, from Sunday */
  22763. p = getval(ac, t->tm_wday, *pn = 1);
  22764. break;
  22765. case 'W': /* put Monday week of the year */
  22766. p = getval(ac,
  22767. wkyr(MONDAY, t->tm_wday, t->tm_yday),
  22768. *pn = 2);
  22769. break;
  22770. case 'x': /* put date */
  22771. p =_Gettime(tin->_Formats, 1, pn),
  22772. *pn = -*pn;
  22773. break;
  22774. case 'X': /* put time */
  22775. p = _Gettime(tin->_Formats, 2, pn),
  22776. *pn = -*pn;
  22777. break;
  22778. case 'y': /* put year of the century */
  22779. p = getval(ac, t->tm_year % 100, *pn = 2);
  22780. break;
  22781. case 'Y': /* put year */
  22782. p = getval(ac, t->tm_year + 1900, *pn = 4);
  22783. break;
  22784. case 'Z': /* put time zone name */
  22785. if (tin->_Tzone[0] == '\0')
  22786. tin->_Tzone = _Getzone(); /* adapt zone */
  22787. p = _Gettime(tin->_Tzone, 0 < t->tm_isdst, pn);
  22788. break;
  22789. case '%': /* put "%" */
  22790. p = "%", *pn = 1;
  22791. break;
  22792. default: /* unknown field, print it */
  22793. p = s - 1, *pn = 2;
  22794. }
  22795. return (p);
  22796. }
  22797.  
  22798. /* End of File */
  22799.  
  22800.  
  22801.  
  22802.  
  22803.  
  22804.  
  22805.  
  22806.  
  22807.  
  22808.  
  22809.  
  22810.  
  22811.  
  22812.  
  22813.  
  22814.  
  22815.  
  22816.  
  22817.  
  22818.  
  22819.  
  22820.  
  22821.  
  22822.  
  22823.  
  22824.  
  22825.  
  22826.  
  22827.  
  22828.  
  22829.  
  22830.  
  22831.  
  22832.  
  22833.  
  22834.  
  22835.  
  22836.  
  22837.  
  22838.  
  22839.  
  22840.  
  22841.  
  22842.  
  22843.  
  22844.  
  22845.  
  22846.  
  22847.  
  22848.  
  22849.  
  22850.  
  22851.  
  22852.  
  22853.  
  22854.  
  22855.  
  22856.  
  22857.  
  22858.  
  22859.  
  22860.  
  22861.  
  22862.  
  22863.  
  22864.  
  22865.  
  22866.  
  22867. Questions & Answers
  22868.  
  22869.  
  22870. Function Return Values
  22871.  
  22872.  
  22873.  
  22874.  
  22875. Kenneth Pugh
  22876.  
  22877.  
  22878. Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C+ +
  22879. language courses for corporations. He is the author of C Language for
  22880. Programmers and All On C, and was a member on the ANSI C committee. He also
  22881. does custom C programming for communications, graphics, image databases, and
  22882. hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707.
  22883. You may fax questions for Ken to (919) 489-5239. Ken also receives email at
  22884. kpugh@dukemvs.ac.duke.edu (Internet) and on Compuserve 70125,1142.
  22885.  
  22886.  
  22887. Q
  22888. First of all I want to thank you for a wonderful magazine and informative
  22889. answers to a lot of good programming questions. I have two easy ones for you.
  22890. Why do most of the Standard C library string functions (strcpy, strcat) return
  22891. a char * when they already modify the string through formal parameters? I have
  22892. seen:
  22893. s2 = strcpy(s2, s1);
  22894. a lot less than I have seen:
  22895. strcpy(s2, s1);
  22896. I remember your saying something about return values screwing up the stack if
  22897. they are not captured, and this seems like a major offender.
  22898. The second question is geared towards C++. I have never seen a constructor or
  22899. destructor declared with a return value. Are we assuming they return void or
  22900. int, either of which would be better style to specifically declare?
  22901. Thanks in advance for your time and useful information, and keep up the great
  22902. column.
  22903. Andrew Tucker
  22904. Seattle Pacific University
  22905. A
  22906. Let's take your questions in order. I can't ever remember saying anything
  22907. about not using the return value. It usually gets passed back in a register
  22908. and if you do not use it, the value simply disappears when the register is
  22909. reused.
  22910. One reason for the char * return value is to allow the functions to be nested.
  22911. You could specify a series of operations as:
  22912. strcpy(a,"");
  22913. strcat(a, b);
  22914. strcat(a, c);
  22915. or as:
  22916. strcat(strcat(strcpy (a,""),
  22917. b), c);
  22918. I cannot say I particularly prefer the latter. Of course, if you were using
  22919. C++ and a String class, the point becomes moot. String classes overload the
  22920. operator + to mean concatenation. The assignment operator is also usually
  22921. overloaded to mean string copying. So the set of operations can be expressed
  22922. as:
  22923. a = "" + b + c;
  22924. or
  22925. a = b + c;
  22926. Functions such as fgets also return a character pointer, probably for the same
  22927. reason. Although this is consistent with the str__ functions, it is
  22928. inconsistent with the return type of most of the other file functions.
  22929. As for your second question, constructors and destructors do not return any
  22930. value. They are implicitly called in declarations, so there is no opportunity
  22931. for them to return a value. You might call this a void return value, but that
  22932. implies to me that the function actually is called normally.
  22933. For example, suppose you coded:
  22934. #include "string.hpp"
  22935. a_function()
  22936. {
  22937. String a_string;
  22938. ...
  22939. }
  22940. The constructor String(void) is implicitly called when a_string is declared.
  22941. At the terminating brace (when a_string goes out of scope), the destructor
  22942. ~String(void) is implicitly called.
  22943. The lack of return value relates to the problem of what to do if there is an
  22944. error in the constructor or destruct. For example, the initialization values
  22945. might be out of range. You cannot return an error code. You have several
  22946. choices. They include aborting the program, issuing a message alternative to
  22947. the standard error output, or simply substituting in valid values. Just to be
  22948. complete, let me note that a constructor can be called explicitly as a normal
  22949. function. For example, if you had a string = string ("abc"), the constructor
  22950. string (char*) is called and creates an object of class string. However, there
  22951. is still no way to return an error code. A destructor can also be called
  22952. explicitly, but the use for that is fiarly obscure. (KP)
  22953.  
  22954.  
  22955. Check Digits
  22956.  
  22957.  
  22958. This is in partial response to your answer to a question by Brendan O'Haire in
  22959. the November 1992 issue of The C Users Journal. I came across this in reading
  22960. sometime early in my career, 1958-1964 perhaps. If you would like proofs of
  22961. the mathematical assertions, I will supply them on request. I remember the
  22962. source where I read this claimed it was a technique widely-used in the
  22963. business world. I cannot argue for or against that nor can I cite the original
  22964. source.
  22965. This procedure inserts a check "digit" into a number in such a way that all
  22966. single transpositions of adjacent digits can be detected. It can actually
  22967. detect single transposition at odd distance, but the adjacent transposition is
  22968. the more probable keying error. The mathematical algorithm used to compute the
  22969. check digit is actually an algorithm for computing the remainder upon division
  22970. by 11.
  22971. I'll use an example. Suppose the number to be encoded is 34781 and you wish to
  22972. make the check digit the third digit from the right. Let Y represent the check
  22973. digit. The new number will have the form 347Y81. Form a sum alternately adding
  22974. and subtracting the digits in the number:
  22975.  
  22976. 1 - 8 + Y - 7 + 4 - 3 = -13 + Y
  22977. If the numeric part of this, -13 in this case, is not between -10 and 10
  22978. repeat the process:
  22979. -13 + Y -> -(3 - 1) + Y = -2 + Y
  22980. At this point pick a single digit for Y so the sum is either zero or eleven. Y
  22981. is 2 in this case. It is possible Y might be 10 in some cases. The Roman
  22982. numeral X is used as the "digit" in this case. The original number with the
  22983. check digit inserted is now 347281. When the alternate addition and
  22984. subtraction is performed:
  22985. 1 - 8 + 2 - 7 + 4 - 3 = -11 -> -(1 - 1) = 0
  22986. then the ultimate value should be zero. If two digits are transposed, 342781
  22987. for example, the algorithm produces -1 for the example. A non-zero value
  22988. indicates the number is not correct.
  22989. The mathematical argument depends on the fact that any integer, in decimal
  22990. notation, can be represented as a sum of two terms, A+B, where A is the
  22991. alternating sum and difference of the digits and B is an exact multiple of 11.
  22992. This latter fact is derived from two assertions which can be proved by
  22993. mathematical induction.
  22994. (10 ** (2k + 1)) + 1 and (10**2k) - 1, k = 0, 1, 2,...
  22995. are each divisible by 11. All the terms in B have a factor of one or the other
  22996. of these forms.
  22997. Walter Beck
  22998. I read with some interest the letter from Brendan O'Haire in your column in
  22999. the November CUJ regarding check digits. I'm no mathematician nor a theorist,
  23000. so I don't know the reasons why these things are done the way they are.
  23001. However, I have recently run into a couple of algorithms for calculation of
  23002. check digits in real-world programs, which differ significantly from the
  23003. simple algorithm you gave in your response.
  23004. The first one I tripped over was in doing some database conversion for a
  23005. client. Part of the data were bank routing numbers, used for Electronic Funds
  23006. Transfers (these numbers are, apparently, assigned by the Federal Reserve
  23007. Bank). They are eight-digit numbers with a ninth digit appended, which is the
  23008. check digit. The algorithm I was given to use for calculating these numbers is
  23009. shown in the code in Listing 1.
  23010. Also, I am currently involved in attempting to understand the specifications
  23011. from HL-7. (HL-7 is a standard for data messages to be used in Health Care
  23012. settings.) Some of the data types specified in the standard are short text
  23013. strings with a check digit calculated either as mod 10 or mod 11. After some
  23014. digging I was given the following description of the algorithm:
  23015. Assume you have an identifier equal to 12345. Take the odd digit positions,
  23016. counting from the right, i.e., 531, multiply this number by 2 to get 1062.
  23017. Take the even digit positions, starting from the right, i.e., 42, append these
  23018. to the 1062 to get 421062. Add all of these six digits together to get 15.
  23019. Subtract this number from the next highest multiple of 10, i.e., 20 - 15 to
  23020. get 5. The Mod10 check digit is 5. The Mod10 check digit for 401 is 0, for
  23021. 9999, it's 4, for 9999999, it's 7.
  23022. Listing 2 is my (possibly simplistic, and certainly not terribly efficient)
  23023. implementation of this algorithm.
  23024. I always enjoy reading your column. Keep up the good work!
  23025. Fred Smith
  23026. Thank you both for your contribution. I had an encoding course about twenty
  23027. years ago, but the algorithms I recall were mainly for correction of binary
  23028. values. They help in detecting and/or correcting binary digit errors (1 for 0
  23029. or 0 for 1). The parity bit on memory bytes is an example of a single error
  23030. detection code. It could not detect a transposition error (switching two
  23031. binary digits). Cyclic redundancy codes (CRC) can be used to correct multiple
  23032. errors, such as might be found on magnetic disks.
  23033. A single check digit for a decimal number can be used to detect single
  23034. transposition errors or a single digit error. Since the digit is usually
  23035. included as part of the number, it would be interesting to see if any of these
  23036. algorithms fail if the check digit is transposed with a normal digit. When I
  23037. used the check digit, it was set off as a separate character with a hyphen, so
  23038. that mistake was less likely. Too bad telephone numbers don't include a check
  23039. digit. The old rotary switches couldn't handle them, but computerized switches
  23040. should be able to. I wonder if the saving in the amount of time spent dialing
  23041. wrong numbers would compensate for the time spent dialing an extra digit. (KP)
  23042.  
  23043.  
  23044. gotos
  23045.  
  23046.  
  23047. In response to the letter from Raymond Lutz in the November CUJ I would like
  23048. to point out the following useful feature of goto. I use gotos to consolidate
  23049. the error-handling code near the bottom of the routine. This can be
  23050. particularly useful when the normal path also includes most (or all) of the
  23051. error-handling code as well (as in the case of cleanup code). See Listing 3
  23052. for an example.
  23053. If I had used return instead of goto I would have had three copies of the free
  23054. code for buf1, two copies of the free code for buf2, and four different exit
  23055. points in the routine. This way I only have one copy of the free code and one
  23056. exit point which makes maintaining the code easier.
  23057. In brief, I believe that gotos, just like handguns, can be misused/abused but
  23058. that is not a reason to avoid them altogether. You just have to make sure you
  23059. use the right tool for the right job.
  23060. James Brown
  23061. Orem, UT
  23062. I agree with your sentiments regarding gotos, as I have stated before in this
  23063. column. Your particular example of memory allocations is of particular
  23064. interest. A major program that I am helping out with has rather large dynamic
  23065. memory requirements. It also has the need for exiting gracefully if memory
  23066. runs out. The compiler vendor's allocation algorithms have created some
  23067. interesting problems in this application. Memory was getting fragmented more
  23068. than expected, so some tracing capabilities were needed.
  23069. Instead of using malloc and free, I made up two wrapper functions called
  23070. memory_allocate(unsigned int size, char * name) and memory_free(void *address,
  23071. char * name). The name parameter is a string that describes the variable that
  23072. is being allocated or freed.
  23073. It is used for debugging and error reporting purposes. I can't give you the
  23074. exact code, as it is for a proprietary project, but the pseudo-code looks
  23075. something like Figure 1. (KP)
  23076. Figure 1 Pseudocode for memory-tracing capability used for debugging and error
  23077. reporting
  23078. static char *names_allocated[MAX_ALLOCATIONS];
  23079. static unsigned int sizes_allocated[MAX_ALLOCATIONS];
  23080. static unsigned long addresses_allocated[MAX_ALLOCATIONS];
  23081.  
  23082. void *memory_allocate(unsigned int size, char *name)
  23083.  {
  23084.  // Call malloc with size
  23085.  // if okay, then store name, size, address in arrays
  23086.  // return address
  23087.  // else, print error message with name
  23088.  // optionally, print all names, sizes, addresses
  23089.  }
  23090.  
  23091. void memory_free(void *address, char *name)
  23092.  {
  23093.  // If address == NULL
  23094.  // print error message with name
  23095.  // else find address in array
  23096.  // if not found
  23097.  // print error message with name
  23098.  // else
  23099.  // clear array elements
  23100.  }
  23101.  
  23102. Listing 1 Algorithm for calculating bank routing numbers
  23103. #define NUMVAL(x) (x - '0')
  23104.  
  23105. #define TONUM(x) NUMVAL(x)
  23106. #define TODIGIT(x) (x + '0')
  23107. static char trconst[] = {'3', '7', '1', '3',
  23108. '7', '1', '3', '7'};
  23109. char calc_check_digit (char * trnum)
  23110. {
  23111. int sum, val;
  23112. int i;
  23113. for ( sum = i = 0 ; i < 8 ; i++)
  23114. {
  23115. sum += TONUM (trnum[i]) * TONUM (trconst[i]);
  23116. }
  23117. val = 10 - (sum % 10);
  23118. if (val == 10)
  23119. val = 0;
  23120. return (TODIGIT (val));
  23121. }
  23122.  
  23123. /* End of File */
  23124.  
  23125.  
  23126. Listing 2 Algorithm for calculating HL-7 standard check digits
  23127. /*
  23128. * This implementation of the above algorithm has some distinct
  23129. * limitations, although I believe they are not a serious problem.
  23130. * This is intended for processing short strings of digits (10 or so),
  23131. * not arbitrarily long digit strings. Each of the substrings that are
  23132. * broken out by the code below are assumed to be a value that can
  23133. * be assigned to a long when converted to an integral type. This
  23134. * limitation requires that the input string not be more than about 20
  23135. * characters in length, which is more than adequate for the intended
  23136. * application. I'm assuming that a long is at least 32 bits.
  23137. * Further, it assumes a character set where the digits' collating
  23138. * sequence mimics that of ASCII, so that things like:
  23139. * x = val - '0';
  23140. * will evaluate to an integer value of 3 in the case where val == '3'.
  23141. */
  23142.  
  23143. #include <stdio.h>
  23144. #define MOD10 10
  23145. #define MOD11 11
  23146. /*
  23147. * -------------- - check_digit() -------- - *
  23148. * Calculates a check digit according to the algorithm given above.
  23149. * NOTE
  23150. * that there are no checks for array bounds overflow. Beware.
  23151. */
  23152. check_digit (string, mod)
  23153. char * string;
  23154. int mod;
  23155. {
  23156. char tmpbuf[20];
  23157. int i, j, sum;
  23158. unsigned long odd, even;
  23159. /* extract the odd digits */
  23160. j = strlen (string);
  23161. for ( j-, i = 0 ; j >= 0 ; j -= 2, i++ )
  23162. {
  23163. tmpbuf[i] = string[j];
  23164.  
  23165. }
  23166. tmpbuf[i] = '\0';
  23167. odd = atol (tmpbuf);
  23168. /* extract the even digits */
  23169. j = strlen (string);
  23170. for ( j -= 2, i = 0 ; j >= 0 ; j -= 2, i++ )
  23171. {
  23172. tmpbuf[i] = string[j];
  23173. }
  23174. tmpbuf[i] = '\0';
  23175. even = atol (tmpbuf);
  23176. /* now make the composite string & sum the digits */
  23177. sprintf (tmpbuf, "%lu%lu", even, 2L * odd);
  23178. i = strlen (tmpbuf);
  23179. for ( sum = j = 0 ; j < i ; j++)
  23180. {
  23181. sum += tmpbuf[j] - '0';
  23182. }
  23183. /* now do the mod10 or mod11 operation */
  23184. i = (mod - (sum % mod));
  23185. if (i == mod)
  23186. i = 0;
  23187. return (i);
  23188. }
  23189. #ifdef TEST
  23190. main ()
  23191. {
  23192. char buf[30];
  23193. while (1)
  23194. {
  23195. gets (buf);
  23196. if (strlen (buf) == 0)
  23197. break;
  23198. printf ("string: %s, mod10 checkdigit: %d,"
  23199. "mod11 checkdigit %d:n",
  23200. buf, check_digit (buf, MOD10),
  23201. check_digit (buf, MOD11));
  23202. }
  23203. }
  23204. #endif /* TEST */
  23205.  
  23206. /* End of File */
  23207.  
  23208.  
  23209. Listing 3 Code using goto to consolidate error-handling code
  23210. int SomeFunc(void)
  23211. {
  23212. char *buf1, *buf2, *buf3;
  23213. int ccode;
  23214. if ((buf1 = malloc(1024)) == NULL)
  23215. {
  23216. ccode = -1;
  23217. goto End1;
  23218. }
  23219. if ((buf2 = malloc(1024)) == NULL)
  23220. {
  23221. ccode = -1;
  23222. goto End2;
  23223. }
  23224.  
  23225. if ((buf3 = malloc(1024)) == NULL)
  23226. {
  23227. ccode = -1;
  23228. goto End3;
  23229. }
  23230. // More code here
  23231. End3:
  23232. free(buf3);
  23233. End2:
  23234. free(buf2);
  23235. End1:
  23236. free(buf1);
  23237. return(ccode);
  23238. }
  23239.  
  23240. /* End of File */
  23241.  
  23242.  
  23243.  
  23244.  
  23245.  
  23246.  
  23247.  
  23248.  
  23249.  
  23250.  
  23251.  
  23252.  
  23253.  
  23254.  
  23255.  
  23256.  
  23257.  
  23258.  
  23259.  
  23260.  
  23261.  
  23262.  
  23263.  
  23264.  
  23265.  
  23266.  
  23267.  
  23268.  
  23269.  
  23270.  
  23271.  
  23272.  
  23273.  
  23274.  
  23275.  
  23276.  
  23277.  
  23278.  
  23279.  
  23280.  
  23281.  
  23282.  
  23283.  
  23284.  
  23285.  
  23286.  
  23287.  
  23288. Code Capsule
  23289.  
  23290.  
  23291. A C++ Date Class, Part 2
  23292.  
  23293.  
  23294.  
  23295.  
  23296. Chuck Allison
  23297.  
  23298.  
  23299. Chuck Allison is a software architect for the Family History Department of
  23300. Jesus Christ of Latter Day Saints Church Headquarters in Salt Lake City. He
  23301. has a B.S. and M.S. in mathematics, has been programming since 1975, and has
  23302. been teaching and developing in C since 1984. His current interest
  23303. object-oriented technology and education. He is a member of X3J16, the ANSI
  23304. C++ Standards Committee. Chuck can be reached on the Internet at
  23305. allison@decus.org, or at (801) 240-4510.
  23306.  
  23307.  
  23308. In last month's capsule I presented the beginnings of a simple date class. In
  23309. addition to providing a member function to calculate the interval between two
  23310. dates, this class illustrated the following features of C++:
  23311. inline functions
  23312. references
  23313. constructors
  23314. controlled access to private data members
  23315. In this month's installment I will add relational operators, input/output
  23316. operations, and the capability to get the current date, while demonstrating
  23317. these features:
  23318. operator overloading
  23319. streams
  23320. friend functions
  23321. static members
  23322. When using dates you often need to determine if one precedes another. I will
  23323. add the member function
  23324. int compare(const Date& d2)
  23325. const;
  23326. to the date class (see Listing 1).
  23327. Date::compare behaves like strcmp--it returns a negative integer if the
  23328. current object (*this) precedes d2, 0 if both represent the same date, and a
  23329. positive integer otherwise (see Listing 2 for the implementation and Listing 3
  23330. for a sample program). For those of you familiar with qsort from the Standard
  23331. C library, you can use Date::compare to sort dates just like you use strcmp to
  23332. sort strings. Here is a compare function to pass to qsort:
  23333. #include "date.h"
  23334. int datecmp(const void *p1, const void *p2)
  23335. {
  23336. const Date
  23337. *d1p = (const Date *) p1,
  23338. *d2p = (const Date *) p2;
  23339. return d1p->compare(*d2p);
  23340. }
  23341. (Next month's CODE CAPSULE will cover qsort).
  23342.  
  23343.  
  23344. Operator Overloading
  23345.  
  23346.  
  23347. Most of the time, it is more convenient to have relational operators, for
  23348. example
  23349. if (d1 < d2)
  23350. // do something appropriate..
  23351. Adding a less-than operator is trivial using Date::compare--just insert the
  23352. following in-line member function into the class definition:
  23353. int operator<(const Date& d2)
  23354. const
  23355. {return compare(d2) < 0);
  23356. The compiler translates each occurrence of an expression such as
  23357. d1 < d2
  23358. into the function call
  23359. d1.operator<(d2)
  23360. Listing 4 has the class definition with all six relational operators and the
  23361. updated sample program is in Listing 5.
  23362. Since the functionality of Date::interval is like a subtraction (it gives us
  23363. the difference between two dates), it would seem natural to rename it as
  23364. Date::operator-. Before doing this, take a closer look at the semantics of the
  23365. statement
  23366. a = b - c;
  23367.  
  23368. No matter the type of the variables, the following should hold:
  23369. a is a distinct object created by the subtraction, and b - c == - (c - b)
  23370. I will use the convention that a "positive" Date object has all positive data
  23371. members, and conversely for a "negative" date (mixed signs are not allowed).
  23372. In Listing 7, I have replaced Date::interval with Date::operator- (const
  23373. Date&), which affixes the proper signs to the data members and returns a
  23374. newly-constructed Date object. The new class definition in Listing 6 also
  23375. contains a unary minus operator, which also has the name Date::operator-, but
  23376. takes no arguments. The compiler will transform the statements
  23377. d1 - d2;
  23378. -d1;
  23379. respectively into
  23380. d1.operator-(d2); // Calls Date::operator-(const Date&)
  23381. d1.operator-(); // Calls Date::operator-()
  23382. A sample program using these new member functions is in Listing 8.
  23383.  
  23384.  
  23385. Stream I/O
  23386.  
  23387.  
  23388. One thing remains before I can say that a Date object has the look and feel of
  23389. a built-in type--input/output support. C++ supplies stream objects that handle
  23390. I/O for standard types. For example, the output from the program
  23391. #include <iostream.h>
  23392.  
  23393. main()
  23394. {
  23395. int i;
  23396. cout << "Enter an integer: ";
  23397. cin >> i;
  23398. cout << "You typed " << i << endl;
  23399. return 0;
  23400. }
  23401. will be something like
  23402. Enter an integer: 5
  23403. You typed 5
  23404. cout is an output stream (class ostreom) supplied by the C++ streams library
  23405. and cin is an input stream (class istream), which are associated by default
  23406. with standard output and standard input, respectively. When the compiler sees
  23407. the expression
  23408. cout << "Enter an integer: "
  23409. it replaces it with
  23410. cout.operator<<("Enter an integer: ")
  23411. which calls the member function ostream::operator<<(const char *). Likewise,
  23412. the expression
  23413. cout << i
  23414. where i is an integer calls the function
  23415. ostream::-operator<<(int).
  23416. endl is a special stream directive (called a manipulator) which outputs a
  23417. newline and flushes the output buffer. Output items can be chained together:
  23418. cout << "You typed " << i
  23419. because ostream::operator<< returns a reference to the stream itself. The
  23420. above statement becomes
  23421. (cout.operator<<("You typed ")).operator<<(i)
  23422. To accommodate output of Date objects, then, you will need a global function
  23423. that sends the printed representation to a given output stream, and then
  23424. returns a reference to that stream:
  23425. ostream& operator<<(ostream& os, const Date& d)
  23426. {
  23427. os << d.get_month() << '/'
  23428. << d.get_day() << '/'
  23429. << d.get_year();
  23430. return os;
  23431. }
  23432. This of course can't be a member function, since the stream (not the object
  23433. being output) always appears to the left of the stream insertion operator.
  23434.  
  23435.  
  23436. Friends
  23437.  
  23438.  
  23439. For efficiency, it is customary to give operator<< access to the private data
  23440. members of an object. (Most implementations of a class provide the associated
  23441. I/O operators too, so it seems safe to break the encapsulation boundary in
  23442. this case.) To bypass the restriction on access to private members, you need
  23443. to declare operator<< to be a friend of the Date class by adding the following
  23444. statement to the class definition:
  23445. friend ostream& operator<<(ostream&, const Date&);
  23446. This declaration appears in the new class definition in Listing 9, along with
  23447. the corresponding friend declaration for the input function, operator>>. The
  23448. implementation for these functions is in Listing 10, and Listing 11 has a
  23449. sample program.
  23450.  
  23451.  
  23452.  
  23453. Static Members
  23454.  
  23455.  
  23456. A class in C++ defines a scope. This is why the function Date::compare would
  23457. not conflict with a global function named compare (even if the arguments and
  23458. return value were of the same type). Now consider the array dtab[] in the
  23459. implementation file. dtab's static storage class makes it private to the file,
  23460. but it really belongs to the class, not to the file. If I chose to spread the
  23461. Date member functions across multiple files, I would be forced to group those
  23462. that needed access to dtab together in the same file.
  23463. A better solution is to make dtab a static member of the Date class. Static
  23464. members belong to the whole class, not to a single object. This means that
  23465. only one copy of dtab exists, and is shared by all objects of the class.
  23466. Making the function isleap static allows you to call it without an associated
  23467. object, i.e., you just need to say
  23468. isleap(y);
  23469. instead of
  23470. d.isleap(y);
  23471. To make isleap available to any caller, place it in the public section of the
  23472. class definition, and call it like this:
  23473. Date::isleap(y);
  23474. As a finishing touch, I will redefine the default constructor to initialize
  23475. its object with the current date. The final class definition, implementation
  23476. and sample program are in Listing 12 - Listing 14 respectively.
  23477.  
  23478.  
  23479. Summary
  23480.  
  23481.  
  23482. In these last two installments I've tried to illustrate how C++ supports data
  23483. abstraction--the creation of user-defined types. Constructors cause objects to
  23484. be initialized automatically when you declare them. You can protect class
  23485. members from inadvertent access by making them private. Overloading common
  23486. operators makes your objects look and feel like built-in types--a plus for
  23487. readability and maintenance.
  23488.  
  23489. Listing 1 Introduces a function to compare dates
  23490. // date4.h
  23491.  
  23492. class Date
  23493. {
  23494. int month;
  23495. int day;
  23496. int year;
  23497.  
  23498. public:
  23499. // Constructors
  23500. Date()
  23501. {month = day = year= 0;}
  23502. Date(int m, int d, int y)
  23503. {month = m; day = d; year = y;}
  23504.  
  23505. // Accessor Functions
  23506. int get_month() const
  23507. {return month;}
  23508. int get_day() const
  23509. {return day;}
  23510. int get_year() const
  23511. {return year;}
  23512.  
  23513. Date * interval(const Date&) const;
  23514. int compare(const Date&) const;
  23515. };
  23516.  
  23517. // End of File
  23518.  
  23519.  
  23520. Listing 2 Implements the interval and compare member functions
  23521. // date4.cpp
  23522.  
  23523. #include "date4.h"
  23524.  
  23525. inline int isleap(int y)
  23526. {return y%4 == 0 && y%100 != 0 y%400 == 0;}
  23527.  
  23528. static int dtab[2][13] =
  23529. {
  23530.  
  23531. {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  23532. {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  23533. };
  23534.  
  23535. Date * Date::interval(const Date& d2) const
  23536. {
  23537. static Date result;
  23538. int months, days, years, prev_month;
  23539.  
  23540. // Compute the interval - assume d1 precedes d2
  23541. years = d2.year - year;
  23542. months = d2.month - month;
  23543. days = d2.day - day;
  23544.  
  23545. // Do obvious corrections (days before months!)
  23546. //
  23547. // This is a loop in case the previous month is
  23548. // February, and days < -28.
  23549. prev_month = d2.month - 1;
  23550. while (days < 0)
  23551. {
  23552. // Borrow from the previous month
  23553. if (prev_month == 0)
  23554. prev_month = 12;
  23555. -months;
  23556. days += dtab[isleap(d2.year)][prev_month-];
  23557. }
  23558.  
  23559. if (months < 0)
  23560. {
  23561. // Borrow from the previous year
  23562. -years;
  23563. months += 12;
  23564. }
  23565.  
  23566. // Prepare output
  23567. result.month = months;
  23568. result.day = days;
  23569. result.year = years;
  23570. return &result;
  23571. }
  23572.  
  23573. int Date::compare(const Date& d2) const
  23574. {
  23575. int months, days, years, order;
  23576.  
  23577. years = year - d2.year;
  23578. months = month - d2.month;
  23579. days = day - d2.day;
  23580.  
  23581. // return <0, 0, or >0, like strcmp()
  23582. if (years == 0 && months == 0 && days == 0)
  23583. return 0;
  23584. else if (years == 0 && months == 0)
  23585. return days;
  23586. else if (years == 0)
  23587. return months;
  23588. else
  23589. return years;
  23590.  
  23591. }
  23592. // End of File
  23593.  
  23594.  
  23595. Listing 3 Tests the compare member function
  23596. // tdate4.cpp
  23597.  
  23598. #include <stdio.h>
  23599. #include "date4.h"
  23600.  
  23601. void compare_dates(const Date& d1, const Date& d2)
  23602. {
  23603. int compval = d1.compare(d2);
  23604. char *compstr - (compval < 0) ? "precedes" :
  23605. ((compval > 0) ? "follows" : "equals"};
  23606.  
  23607. printf("%d/%d/%d %s %d/%d/%d\n",
  23608. d1.get_month(),d1.get_day(0),d1.get_year(),
  23609. compstr,
  23610. d2.get_month(),d2.get_day(),d2.get_year());
  23611. }
  23612. main()
  23613. {
  23614. Date d1(1,1,1970);
  23615. compare dates(d1,Date(10,1,1951));
  23616. compare_dates{d1,Date(1,1,1970));
  23617. compare_dates(d1,Date(12,31,1992));
  23618. return 0;
  23619. }
  23620.  
  23621. /* OUTPUT
  23622.  
  23623. 1/1/1970 follows 10/1/1951
  23624. 1/1/1970 equals 1/1/1970
  23625. 1/1/1970 precedes 12/31/1992
  23626. */
  23627.  
  23628. // End of File
  23629.  
  23630.  
  23631. Listing 4 Defines relational operators for the Date class
  23632. // date5.h
  23633.  
  23634. class Date
  23635. {
  23636. int month;
  23637. int day;
  23638. int year;
  23639.  
  23640. public:
  23641. // Constructors
  23642. Date()
  23643. {month = day = year = 0;}
  23644. Date(int m, int d, int y)
  23645. {month = m; day = d; year = y;}
  23646.  
  23647. // Accessor Functions
  23648. int get_month() const
  23649. {return month;}
  23650.  
  23651. int get_day() const
  23652. {return day;}
  23653. int get_year() const
  23654. {return year;}
  23655.  
  23656. Date * interval(const Date&) const;
  23657. int compare(const Date&) const;
  23658.  
  23659. // Relational operators
  23660. int operator<(const Date& d2) const
  23661. {return compare(d2) < 0;}
  23662. int operator<=(const Date& d2) const
  23663. {return compare(d2) <= 0;}
  23664. int operator>(const Date& d2) const
  23665. {return compare(d2) > 0;}
  23666. int operator>=(const Date& d2) const
  23667. {return compare(d2) >= 0;}
  23668. int operator!=(const Date& d2) const
  23669. {return compare(d2) != 0;}
  23670. int operator!=(const Date& d2) const
  23671. {return compare(d2) !=0;}
  23672. };
  23673.  
  23674. // End of File
  23675.  
  23676.  
  23677. Listing 5 Uses the Date relational operators
  23678. // tdate5.cpp
  23679.  
  23680. #include <stdio.h>
  23681. #include <stdlib.h>
  23682. #include "date5.h"
  23683.  
  23684. void compare_dates(const Date& d1, const Date& d2)
  23685. {
  23686. char *compstr = (d1 < d2) ? "precedes" :
  23687. ((d1 > d2) ? "follows" : "equals");
  23688.  
  23689. printf("%d/%d/%d %s %d/%d/%d\n",
  23690. d1.get_month(),d1.get_day(),d1.get_year(),
  23691. compstr,
  23692. d2.get_month(),d2.get_day(),d2.get_year());
  23693. }
  23694.  
  23695. main()
  23696. {
  23697. Date d1(1,1,1970);
  23698. compare_dates(d1,Date(10,1,1951));
  23699. compare_dates(d1,Date(1,1,1970));
  23700. compare_dates(d1,Date(12,31,1992));
  23701. return 0;
  23702. }
  23703.  
  23704. /* OUTPUT
  23705.  
  23706. 1/1/1970 follows 10/1/1951
  23707. 1/1/1970 equals 1/1/1970
  23708. 1/1/1970 precedes 12/31/1992
  23709. */
  23710.  
  23711.  
  23712. // End of File
  23713.  
  23714.  
  23715. Listing 6 Adds binary and unary minus to the Date class
  23716. // date6.h
  23717.  
  23718. class Date
  23719. {
  23720. int month;
  23721. int day;
  23722. int year;
  23723.  
  23724. public:
  23725. // Constructors
  23726. Date()
  23727. {month = day = year = 0;}
  23728. Date(int m, int d, int y)
  23729. {month = m; day = d; year = y;}
  23730.  
  23731. // Accessor Functions
  23732. int get_month() const
  23733. {return month;}
  23734. int get_day() const
  23735. {return day;}
  23736. int get_year() const
  23737. {return year;}
  23738.  
  23739. Date operator-(const Date& d2) const;
  23740. Date& operator-()
  23741. {month = -month; day = -day; year = -year;
  23742. return *this;}
  23743.  
  23744. int compare(const Date&) const;
  23745.  
  23746. // Relational operators
  23747. int operator<(const Date& d2) const
  23748. {return compare(d2) < 0;}
  23749. int operator<=(const Date& d2) const
  23750. {return compare(d2) <= 0;}
  23751. int operator>(const Date& d2) const
  23752. {return compare(d2) > 0;}
  23753. int operator>=(const Date& d2) const
  23754. {return compare(d2) >= 0;}
  23755. int operator==(const Date& d2) const
  23756. {return compare(d2) == 0;}
  23757. int operator!=(const Date& d2) const
  23758. {return compare(d2) != 0;}
  23759. };
  23760.  
  23761. // End of File
  23762.  
  23763.  
  23764. Listing 7 Implements the binary minus operator
  23765. // date6.cpp
  23766. #include <assert.h>
  23767. #include "date6.h"
  23768.  
  23769. inline int isleap(int y)
  23770.  
  23771. {return y%4 == 0 && y%100 != 0 y%400 == 0;}
  23772.  
  23773. static int dtab[2][13] =
  23774. {
  23775. {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  23776. {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  23777. };
  23778.  
  23779. Date Date::operator-(const Date& d2) const
  23780. {
  23781. int months, days, years, prev_month, order;
  23782. const Date * first, * last;
  23783.  
  23784. // Must know which date is first
  23785. if (compare(d2) <= 0)
  23786. {
  23787. // this <= d2
  23788. order = -1;
  23789. first = this;
  23790. last = &d2;
  23791. }
  23792. else
  23793. {
  23794. order = 1;
  23795. first = &d2;
  23796. last = this;
  23797. }
  23798.  
  23799. // Compute the interval; first <= last
  23800. years = last->year - first->year;
  23801. months = last->month - first->month;
  23802. days = last->day - first->day;
  23803. assert(years >= 0 && months >= 0 && days >= 0);
  23804.  
  23805. // Do obvious corrections (days before months!)
  23806. // This is a loop in case the previous month is
  23807. // February, and days < -28.
  23808. prev_month = last->month - 1;
  23809. while (days < 0)
  23810. {
  23811. // Borrow from the previous month
  23812. if (prev_month == 0)
  23813. prev_month = 12;
  23814. --months;
  23815. days += dtab[isleap(last->year)][prey_month--];
  23816. }
  23817.  
  23818. if {months < 0)
  23819. {
  23820. // Borrow from the previous year
  23821. --years;
  23822. months += 12;
  23823. }
  23824.  
  23825. // Return a date object with the interval
  23826. if (order == -1)
  23827. return Date(-months,-days,-years);
  23828. else
  23829. return Date(months, days, years);
  23830.  
  23831. }
  23832.  
  23833. int Date::compare(const Date& d2) const
  23834. {
  23835. // same as in Listing 2
  23836. }
  23837. // End of File
  23838.  
  23839.  
  23840. Listing 8 Subtracts two dates
  23841. // tdate6.cpp:
  23842.  
  23843. #include <stdio.h>
  23844. #include "date6.h"
  23845.  
  23846. main()
  23847. {
  23848. Date d1(1,1,1970), d2(12,8,1992);
  23849. Date result = d1 - d2;
  23850. printf("years: %d, months: %d, days: %d\n",
  23851. result.get_year(),
  23852. result.get_month(),
  23853. result.get_day());
  23854. result = d2 - d1;
  23855. printf("years: %d, months: %d, days: %d\n",
  23856. result.get_year(),
  23857. result.get_month(),
  23858. result.get_day());
  23859. int test = d1 - d2 == -(d2 - d1);
  23860. printf("d1 - d2 == -(d2 - d1)? %s\n",
  23861. test ? "yes" : "no");
  23862. return 0;
  23863. }
  23864.  
  23865. /* OUTPUT
  23866.  
  23867. years: -22, months: -11, days: -7
  23868. years: 22, months: 11, days: 7
  23869. d1 - d2 == -(d2 - d1)? yes
  23870. */
  23871.  
  23872. // End of File
  23873.  
  23874.  
  23875. Listing 9 Adds stream I/O to the Date class
  23876. // date7.h
  23877.  
  23878. class ostream;
  23879.  
  23880. class Date
  23881. {
  23882. int month;
  23883. int day;
  23884. int year;
  23885.  
  23886. public:
  23887. // Constructors
  23888. Date()
  23889. {month = day = year = 0;}
  23890.  
  23891. Date(int m, int d, int y)
  23892. {month = m; day = d; year = y;}
  23893.  
  23894. // Accessor Functions
  23895. int get_month() const
  23896. {return month;}
  23897. int get_day() const
  23898. {return day;}
  23899. int get_year() const
  23900. {return year;}
  23901.  
  23902. Date operator-(const Date& d2) const;
  23903. Date& operator-()
  23904. {month= -month; day = -day; year = -year;
  23905. return *this;}
  23906.  
  23907. int compare(const Date&) const;
  23908.  
  23909. // Relational operators
  23910. int operator<(const Date& d2) const
  23911. {return compare(d2) < 0;}
  23912. int operator<=(const Date& d2) const
  23913. {return compare(d2) <= 0;)
  23914. int operator>(const Date& d2) const
  23915. {return compare(d2) > 0;}
  23916. int operator>=(const Date& d2) const
  23917. {return compare(d2) >= 0;}
  23918. int operator==(const Date& d2) const
  23919. {return compare(d2) == 0;}
  23920. int operator!=(const Date& d2) const
  23921. {return compare(d2) != 0)
  23922.  
  23923. // I/O operators
  23924. friend ostream& operator<<(ostream&, const Date&);
  23925. friend istream& operator>>(istream&, Date&);
  23926. };
  23927.  
  23928. // End of File
  23929.  
  23930.  
  23931. Listing 10 Implements Date stream I/O functions
  23932. #include <iostream.h>
  23933. #include "date7.h"
  23934.  
  23935. ostream& operator<<(ostream& os, const Date& d)
  23936. {
  23937. os << d.month << '/' << d.day << '/' << d.year;
  23938. return os;
  23939. }
  23940.  
  23941. istream& operator>>(istream& is, Date& d)
  23942. {
  23943. char slash;
  23944. is >> d.month >> slash >> d.day >> slash >> d.year;
  23945. return is;
  23946. }
  23947.  
  23948. // End of File
  23949.  
  23950.  
  23951.  
  23952. Listing 11 Illustrates stream I/O of Date objects
  23953. // tdate7.cpp:
  23954.  
  23955. #include <iostream.h>
  23956. #include "date7.h"
  23957.  
  23958. main()
  23959. {
  23960. Date d1, d2;
  23961. cout << "Enter a date: ";
  23962. cin >> d1;
  23963. cout << "Enter another date: ";
  23964. cin >> d2;
  23965. cout << "d1 - d2 = "<< d1 - d2 << endl;
  23966. cout << "d2 - d1 = "<< d2 - d1 << endl;
  23967. return 0;
  23968. }
  23969.  
  23970. /* OUTPUT
  23971.  
  23972. Enter a date: 10/1/1951
  23973. Enter another date: 5/1/1954
  23974. d1 - d2 = -7/0/-2
  23975. d2 - d1 = 7/0/2
  23976. */
  23977.  
  23978. // End of File
  23979.  
  23980.  
  23981. Listing 12 Defines static members
  23982. // date8.h
  23983.  
  23984. // Forward declarations
  23985. class istream;
  23986. class ostream;
  23987.  
  23988. class Date
  23989. {
  23990. int month;
  23991. int day;
  23992. int year;
  23993.  
  23994. static int dtab[2][13];
  23995.  
  23996. public:
  23997. // Constructors
  23998. Date(); // Get today's date (see .cpp file)
  23999. Date(int m, int d, int y)
  24000. {month = m; day = d; year = y;}
  24001.  
  24002. // Accessor Functions
  24003. int get_month() const
  24004. {return month;}
  24005. int get_day() const
  24006. {return day;}
  24007. int get_year() const
  24008. {return year;}
  24009.  
  24010.  
  24011. Date operator-(const Date& d2) const;
  24012. Date& operator-()
  24013. {month = -month; day = -day; year = -year;
  24014. return *this;}
  24015.  
  24016. int compare(const Date&) const;
  24017.  
  24018. // Relational operators
  24019. int operator<(const Date& d2) const
  24020. {return compare{d2) < 0;}
  24021. int operator<=(const Date& d2) const
  24022. {return compare(d2) <= 0;}
  24023. int operator>(const Date& d2) const
  24024. {return compare(d2) > 0;}
  24025. int operator>=(const Date& d2) const
  24026. {return compare(d2) >= 0;}
  24027. int operator==(const Date& d2) const
  24028. {return compare(d2) == 0;}
  24029. int operator!=(const Date& d2) const
  24030. {return compare(d2) != 0;}
  24031.  
  24032. // Stream I/O operators
  24033. friend ostream& operator<<(ostream&, const Date&);
  24034. friend istream& operator>>(istream&, Date&);
  24035.  
  24036. static int isleap(int y)
  24037. {return y%4 == 0 && y%100 != 0 y%400 == 0;}
  24038. };
  24039.  
  24040. // End of File
  24041.  
  24042.  
  24043. Listing 13 Final implementation of the Date class
  24044. // date8.cpp
  24045.  
  24046. #include <iostream.h>
  24047. #include <time.h>
  24048. #include <assert.h>
  24049. #include "date8.h"
  24050.  
  24051. // Must initialize statics outside the class definition
  24052. int Date::dtab[2][13] =
  24053. {
  24054. {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  24055. {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  24056. };
  24057.  
  24058. Date Date::operator-(const Date& d2) const
  24059. {
  24060. int months, days, years, prev_month, order;
  24061. const Date * first, * last;
  24062.  
  24063. // Must know which date is first
  24064. if (compare(d2) <= 0)
  24065. {
  24066. // this <= d2
  24067. order= -1;
  24068. first = this;
  24069. last = &d2;
  24070.  
  24071. }
  24072. else
  24073. {
  24074. order = 1;
  24075. first = &d2;
  24076. last = this;
  24077. }
  24078.  
  24079. // Compute the interval; first <= last
  24080. years = last->year - first->year;
  24081. months = last->month - first->month;
  24082. days = last->day - first->day;
  24083. assert(years >= 0 && months >= 0 && days >= 0);
  24084.  
  24085. // Do obvious corrections (days before months!)
  24086. //
  24087. // This is a loop in case the previous month is
  24088. // February, and days < -28.
  24089. prev_month = last->month - 1;
  24090. while (days < 0)
  24091. {
  24092. // Borrow from the previous month
  24093. if (prev_month == 0)
  24094. prev_month = 12;
  24095. -months;
  24096. days += dtab[isleap(last->year)][prev_month-];
  24097. }
  24098.  
  24099. if (months < 0)
  24100. {
  24101. // Borrow from the previous year
  24102. -years;
  24103. months += 12;
  24104. }
  24105.  
  24106. // Return a date object with the interval
  24107. if (order == 1)
  24108. return Date(-months,-days,-years);
  24109. else
  24110. return Date(months,days,years);
  24111. }
  24112.  
  24113. int Date::compare(const Date& d2) const
  24114. {
  24115. int months, days, years, order;
  24116.  
  24117. years = year - d2.year;
  24118. months = month - d2.month;
  24119. days = day - d2.day;
  24120.  
  24121. // return <0, 0, or >0, like strcmp()
  24122. if (years == 0 && months == 0 && days == 0)
  24123. return 0;
  24124. else if (years == 0 && months == 0)
  24125. return days;
  24126. else if (years == 0)
  24127. return months;
  24128. else
  24129. return years;
  24130.  
  24131. }
  24132.  
  24133. ostream& operator<<(ostream& os, const Date& d)
  24134. {
  24135. os << d.month << '/' << d.day << '/' << d.year;
  24136. return os;
  24137. }
  24138.  
  24139. istream& operator>>(istream& is, Date& d)
  24140. {
  24141. char slash;
  24142. is >> d.month >> slash >> d.day >> slash >> d.year;
  24143. return is;
  24144. }
  24145.  
  24146. Date::Date()
  24147. (
  24148. // Get today's date
  24149. time_t tval = time(0);
  24150. struct tm *tmp= localtime(&tval);
  24151.  
  24152. month = tmp->tm_mon+1;
  24153. day = tmp->tm_mday;
  24154. year = tmp->tm_year + 1900;
  24155. }
  24156. // End of File
  24157.  
  24158.  
  24159. Listing 14 Gets today's date
  24160. // tdate8.cpp:
  24161.  
  24162. #include <iostream.h>
  24163. #include "date8.h"
  24164.  
  24165. main()
  24166. {
  24167. Date today, d2;
  24168. cout << "Today's date is "<< today << endl;
  24169. cout << "Enter another date: ";
  24170. cin >> d2;
  24171. cout << "today - d2 = "<< today - d2 << endl;
  24172. cout << "d2 - today = "<< d2 - today << endl;
  24173. return 0;
  24174. }
  24175.  
  24176. /* OUTPUT
  24177. Today's date is 12/12/1992
  24178. Enter another date: 1/1/1970
  24179. today - d2 = 11/11/22
  24180. d2 - today = -11/-11/-22
  24181. */
  24182. // End of File
  24183.  
  24184.  
  24185.  
  24186.  
  24187.  
  24188.  
  24189.  
  24190.  
  24191.  
  24192.  
  24193.  
  24194.  
  24195.  
  24196.  
  24197.  
  24198.  
  24199.  
  24200.  
  24201.  
  24202.  
  24203.  
  24204.  
  24205.  
  24206.  
  24207.  
  24208.  
  24209.  
  24210.  
  24211.  
  24212.  
  24213.  
  24214.  
  24215.  
  24216.  
  24217.  
  24218.  
  24219.  
  24220.  
  24221.  
  24222.  
  24223.  
  24224.  
  24225.  
  24226.  
  24227.  
  24228.  
  24229.  
  24230.  
  24231.  
  24232.  
  24233.  
  24234.  
  24235.  
  24236.  
  24237.  
  24238.  
  24239.  
  24240.  
  24241.  
  24242.  
  24243.  
  24244.  
  24245.  
  24246.  
  24247.  
  24248.  
  24249.  
  24250.  
  24251.  
  24252.  
  24253.  
  24254. Stepping Up To C++
  24255.  
  24256.  
  24257. Inheritance, Part 1
  24258.  
  24259.  
  24260.  
  24261.  
  24262. Dan Saks
  24263.  
  24264.  
  24265. Dan Saks is the founder and principal of Saks & Associates, which offers
  24266. consulting and training in C++ and C. He is secretary of the ANSI and ISO C++
  24267. committees, and contributing editor for the Windows/DOS Developer's Journal.
  24268. Dan is coauthor of C++ Programming Guidelines, and codeveloper of the Plum
  24269. Hall Validation Suite for C++ (both with Thomas Plum). You can reach him at
  24270. 393 Leander Dr., Springfield OH, 45504,4906, by phone at (513)324-3601, or
  24271. electronically at dsaks@wittenberg.edu.
  24272.  
  24273.  
  24274. Although they don't always agree on the exact meaning of the terms, most
  24275. people who know something about object-oriented programming agree that it
  24276. employs at least three techniques:
  24277. Data abstraction
  24278. Inheritance
  24279. Polymorphism
  24280. Thus far in my column, I've only covered C++ features that support data
  24281. abstraction--namely, classes, access specifiers, constructors and destructors,
  24282. and features such as references and operator overloading that help you build
  24283. more intuitive abstractions. In this article, I'll introduce inheritance.
  24284. Some of you may be wondering why I've waited so long to deal with inheritance.
  24285. I think inheritance is useful, but not nearly as useful as classes and access
  24286. specifiers. Many programmers overrate its value and consequently misuse it. I
  24287. use inheritance sparingly in my own work.
  24288. The other reason I've postponed discussing inheritance is that inheritance is
  24289. a technique for defining new classes from existing classes. As such,
  24290. understanding inheritance requires an understanding of all of those features I
  24291. listed above. Now that I've covered them, I'll take on inheritance.
  24292.  
  24293.  
  24294. The Basics
  24295.  
  24296.  
  24297. Inheritance is a technique for creating a new class, called the derived class,
  24298. from an existing class, called the base class. The derived class definition
  24299. looks like any other base class definition, except for the presence of a base
  24300. class specifier appearing immediately after the derived class name. For
  24301. example, the definition
  24302. class D : public B
  24303. {
  24304. //...
  24305. };
  24306. includes the base class specifier : public B, so that class D is derived from
  24307. (a previously defined) base class B. The keyword public after the colon (:)
  24308. indicates that B is a public base class of D, or conversely, that D is
  24309. publicly-derived from B. Base classes can also be private or protected. For
  24310. the moment, I will only consider public base classes.
  24311. The derived class inherits nearly all the members (data and functions) of the
  24312. base class even though the derived class definition doesn't even mention the
  24313. inherited members. You add more members to a derived class by simply declaring
  24314. them inside the class body. For example, if you define class B as
  24315. class B
  24316. {
  24317. public:
  24318. int f();
  24319. void g(int);
  24320. private:
  24321. int i, j;
  24322. };
  24323. then
  24324. class D : public B
  24325. {
  24326. public:
  24327. double h(double);
  24328. private:
  24329. double x;
  24330. };
  24331. defines class D inheriting the members of B aing a public member function
  24332. h(double) and a private data member x. The inherited public members--functions
  24333. f() and g(int)--become public members of D. The inherited private
  24334. members--data members i and j--become part of class D, but remain private to
  24335. the B part of D. A D member function, like h(double), can only access i and j
  24336. via public members (and friends, if any) of B. You may be surprised by this
  24337. restriction, but if derived classes could directly access private base class
  24338. members, then anyone could violate the encapsulation of a class by simply
  24339. deriving another class from it.
  24340. A derived class can be a base class for further derived classes. For example,
  24341. class F : public D
  24342. {
  24343. public:
  24344. char *p;
  24345. };
  24346. derives class F from D, adding a public data member p to F.
  24347.  
  24348.  
  24349.  
  24350. Is-A Relationships
  24351.  
  24352.  
  24353. Inheritance provides a simple, explicit notation for creating specialized
  24354. versions of broader classes. Inheritance defines an Is-A relationship (some
  24355. people prefer Is-A-Kind-Of for added clarity): an object of a derived class is
  24356. an object of the base class. A derived class object has everything that a base
  24357. class object has, and usually more. It never has less.
  24358. For example, Stroustrup (1991) introduces inheritance by sketching an example
  24359. dealing with employees and their managers. Class employee defines the
  24360. representation for each employee, with data such as name, age, and salary.
  24361. Since a manager is an employee (with additional powers and responsibilities),
  24362. Stroustrup derives class manager from class employee:
  24363. class manager : public employee
  24364. {
  24365. // additional members that
  24366. // distinguish managers from
  24367. // other employees
  24368. };
  24369. Since a manager is an employee, any function f with a formal parameter of type
  24370. employee or employee & will accept an actual argument of type manager. For
  24371. example, given
  24372. void f(employee &e);
  24373. manager m;
  24374. the call f(m) binds the formal parameter e to actual argument m. Inside f, e
  24375. appears to refer to an employee, which it does. It just happens that in this
  24376. particular call, e refers to an employee that is also a manager. But f can't
  24377. tell that e actually refers to a manager; it can only access the employee part
  24378. of the object.
  24379. As a general rule, whenever class B is a public base of class D, you can:
  24380. 1. Convert a D * to a B * (a pointer to a derived object to a pointer to a
  24381. base object)
  24382. 2. Convert a D & to a B & (a reference to a derived object to a reference to a
  24383. base object)
  24384. 3. Initialize a B & to refer to a D
  24385. These conversions are transitive. That is, if class D is a public base of
  24386. class F, then you can convert an F * to either a D *or a B *, or an F & to
  24387. either a D & or a B &. For example, given
  24388. class B {...};
  24389. class D : public B {...};
  24390. class F : public D {...};
  24391. B b;
  24392. D d;
  24393. F f;
  24394. then all of the following operations are valid:
  24395. B *pb = &d; // ok, a D* is a B*
  24396. D *pd = &f; // ok, an F* is a D*
  24397. pb = &f; // ok, an F* is a D*,
  24398. // which is a B*
  24399. D &rd = f; // ok, an F is a D
  24400. B &rb1 = rd; // ok, a D is a B
  24401. B &rb2 = f; // ok, an F is a D,
  24402. // which is a B
  24403. Although an object of a derived class is an object of its base class, the
  24404. opposite is not true. Thus, you cannot convert a pointer (or reference) to a
  24405. base object to a pointer (or reference) to a derived object, unless you use a
  24406. cast. For example, given the immediately preceding declarations, then
  24407. pd = pb; // error, a B* is
  24408. // not a D*
  24409. pd = (D *)pb; // ok, but suspect
  24410. F &f = b; // error, a B is
  24411. // not an F
  24412. In general, C++ also lets you initialize a base class object with a derived
  24413. class object, as in
  24414. D d;
  24415. //...
  24416. B b(d);
  24417. or simply assign a derived class object to a base class object, as in
  24418. b = (d);
  24419. (I detailed the differences between initialization and assignment in
  24420. "Initialization vs. Assignment," CUJ, September 1992.) Both the initialization
  24421. and the assignment copy only the inherited B members from d to b.
  24422. The ARM (Ellis & Stroustrup 1990) doesn't specifically permit conversion from
  24423. a derived class object to a base class object, but it falls out from the
  24424. reference conversions. When you write
  24425. B b(d);
  24426. C++ translates this to a call to B's copy constructor, typically declared as
  24427. B::B(const B &br);
  24428. The constructor call binds formal parameter br (a reference to a base object)
  24429. to actual argument d (a derived class object), as permitted by in rule 3
  24430. previously mentioned. Similarly, when you write the assignment
  24431. b = d;
  24432. C++ translates this to a call to B's assignment operator:
  24433. B &B::operator=(const B &br);
  24434. Again, calling this assignment operator binds br to d, employing the same
  24435. reference conversion.
  24436.  
  24437.  
  24438.  
  24439. How It Works
  24440.  
  24441.  
  24442. Some insight into how C++ implements inheritance may help you understand and
  24443. remember the conversion rules a bit better. Although C++ imposes some
  24444. restrictions on the storage layout for derived class objects, it doesn't
  24445. require that an implementation use any particular layout strategy.
  24446. Figure 1 shows a simple base class B and a typical storage layout for an
  24447. object of class B. Notice that only the data fields occupy storage in the
  24448. object. C++ resolves B's member function calls at translation time, so it need
  24449. not store any information about the member functions in the object.
  24450. Figure 2 shows a typical layout for a class D derived from class B in Figure
  24451. 1. The B sub-object (the B part) occupies the beginning (the lowest addresses)
  24452. of a D object. Thus, converting a pointer (or reference) to a D into a pointer
  24453. (or reference) to a B doesn't require any generated code; it's strictly a
  24454. compile-time transformation.
  24455. Calling a function,
  24456. void f(B &br);
  24457. with an actual argument d of type D binds br to the B part of d. Typical
  24458. generated code simply assigns the address of d to the pointer that implements
  24459. br. No pointer arithmetic or indirection is needed.
  24460. The body of the function can't tell whether br is bound to a B or to an object
  24461. of a class derived from B. But, since all classes derived from B have at least
  24462. everything that B has, f can safely access all members of br. That there might
  24463. be more members beyond the fringes of B is not a concern.
  24464. This model also illustrates why you generally can't convert in the opposite
  24465. direction, that is, from pointer (or reference) to base to pointer (or
  24466. reference) to derived. For example, calling a function,
  24467. void g(D &dr);
  24468. with an actual argument b of type B attempts to bind dr to b. But, referring
  24469. to Figure 1 and Figure 2, a B object doesn't have an x member, so accessing
  24470. dr.x inside g would reach beyond the end of b into uncharted territory. Thus,
  24471. converting from base to derived violates the type safety rules in C++. You
  24472. can't make the call without casting the actual argument b to type D.
  24473.  
  24474.  
  24475. Overriding
  24476.  
  24477.  
  24478. A derived class can redefine a function inherited from a base case, as shown
  24479. in Listing 1. Here, the base class B defines two functions, f and g. The
  24480. derived class D inherits both functions from B, but then replaces g with a
  24481. definition of its own. This replacement is called overriding.
  24482. Figure 3 shows the output from the program in Listing 1. Calling d.f calls the
  24483. f inherited from B. The translator need not generate any new code for D's f;
  24484. it can simply invoke the code already generated for B's f, passing the address
  24485. of d's B sub-object as the value of this. However, since D's g overrides the g
  24486. inherited from B, calling d.g calls a different function than calling b.g.
  24487. When a derived class overrides an inherited public function, that function is
  24488. hidden, but not completely inaccessible. The derived class can still access
  24489. the hidden member by using the scope resolution operator, ::, and explicitly
  24490. qualifying the member name with the base class name, as shown in Listing 2. In
  24491. this example, derived class D overrides inherited functions and g and h. The
  24492. call to B::g inside D's g calls B's g, as shown in the program output in
  24493. Figure 4. Without the qualifier B::, a call to g inside D's g would be a
  24494. recursive call to D's g.
  24495. The body of D::h in Listing 2 shows another technique for calling an
  24496. overridden member function using a cast. Remember that, inside the body of a
  24497. member function, a call to a member function like h is actually a call to
  24498. this->h. In a D member function, the type of is D const *. By casting this to
  24499. B * (which is a valid conversion from pointer to derived to pointer to base)
  24500. inside D::h, I forced the translator to look for h in the scope of B, and
  24501. bypass looking in the scope of B. It works, but I recommend avoiding the cast
  24502. and using the scope resolution operator as I did in D::g.
  24503. A derived class can override inherited data members as well as function
  24504. members. For example, consider
  24505. class B
  24506. {
  24507. public:
  24508. int n;
  24509. // ...
  24510. };
  24511.  
  24512. class D : public B
  24513. {
  24514. public:
  24515. long n;
  24516. void f();
  24517. // ...
  24518. };
  24519. An object d of class D has storage for both an int n and a long n. Inside
  24520. D::f, an unqualified reference to n refers to D::n (the long); B::n refers to
  24521. the inherited int n.
  24522. A derived class cannot delete inherited members.
  24523.  
  24524.  
  24525. An Example
  24526.  
  24527.  
  24528. In my last two articles I described and implemented several versions of class
  24529. float_array, an array of float for which you can set the number of elements at
  24530. runtime. (See "Dynamic Arrays," CUJ, November 1992, and "The Function
  24531. operator[]", CUJ, January 1993.) Listing 3 shows the class definition for the
  24532. climactic version, which grows automatically to keep subscript references in
  24533. bounds.
  24534. float_arrays, like all other arrays in C and C++, have the lowest subscript
  24535. fixed at zero. This is less than ideal for some applications. Programming
  24536. languages like Pascal, and its descendents Modula-2 and Ada, let you declare
  24537. arrays with low bounds other than zero.
  24538. In C++, you can fill the need by creating a class that I'll call float_vector,
  24539. for which you specify not the number of elements but the low and high bounds
  24540. of the subscript. For example,
  24541. float_vector fv(1, 10);
  24542. declares fv as an array of float whose subscript range is 1 to 10, inclusive.
  24543. float_array already embodies much of the functionality for float_vector, so
  24544. let's consider implementing float_vector by deriving it from float_array.
  24545. Listing 4 shows the class definition for float_vector. float_vector adds a new
  24546. private data member, _low, that records the vector's low bound. A float_vector
  24547. need not store the high bound as a data member because it can determine the
  24548. high bound from the low bound and length (inherited from float_array).
  24549. float_vector defines a constructor, float_vector(int lo, int hi), that builds
  24550. a vector with subscripts from lo to hi, inclusive. It also adds two query
  24551. functions, low and high, that return the values of the current low and high
  24552. subscripts, respectively.
  24553. The float_vector constructor is extremely terse, so I defined it as an inline
  24554. function in the header, as
  24555. inline float_vector::float_vector(int lo, int hi)
  24556. : _low(lo), float_array(hi - lo + 1)
  24557. { }
  24558. The constructor's member-initializers do all the work. The first initializer,
  24559. _low(lo), fills in the private data member _low. The second initializer,
  24560. float_array(hi - lo + 1), invokes the base class constructor to initialize the
  24561. inherited data members array and len.hi - lo + 1 is the number of elements in
  24562. a float_vector whose subscript range is from lo to hi.
  24563. Remember, inherited private members are not directly accessible in the derived
  24564. class. The derived class must use the public interface provided by the base
  24565. class, which in this case, is a public constructor. Unlike member initializers
  24566. that I've used in the past, the leading identifier in the initializer
  24567. float_array(hi - lo + 1) is the name of a type, not the name of a data member.
  24568. There's no named member for the float_array sub-object in the float_vector, so
  24569. you must refer to it using its type name.
  24570. float_vector overrides both inherited operator[] functions with new
  24571. implementations that work when the low subscript bound is nonzero. Notice that
  24572. the formal parameters for both the const and non-const
  24573. float_vector::operator[] are int, and not size_t, as they are in class
  24574. float_array, because a float_vector's low bound may be negative.
  24575.  
  24576. Listing 5 contains the non-inline float_vector member functions, namely, both
  24577. forms of operator[]. The function bodies are identical; they both rely on the
  24578. inherited (overridden) versions of operator[] to do most of the work. The
  24579. expression i - low shifts the subscript i into a subscript range whose low
  24580. bound is zero. The statement
  24581. return float_array::operator[] (i - low);
  24582. calls the inherited operator[] to select the desired element from the
  24583. inherited float_array sub-object, and extend the array if necessary.
  24584. Listing 6 shows a test program for float_vectors. Notice that the display
  24585. function (brought over from my previous two articles) still accepts a second
  24586. argument of type const float_array &. I did not change it to const
  24587. float_vector &. I also left a float_array in the test program to show that the
  24588. display function accepts arguments of both the base and derived types.
  24589. A sample output from the program appears in Figure 5. The abnormal termination
  24590. is intentional. I planted a subscripting error just to show that the
  24591. assertions work.
  24592.  
  24593.  
  24594. Food for Thought
  24595.  
  24596.  
  24597. I was forced to make a simplifying assumption in my float_vector class,
  24598. namely, that you can only extend the high bound of a vector. The low bound
  24599. must remain fixed. That's why both float_vector::operator[] functions include
  24600. the assertion
  24601. assert(i >= low());
  24602. The problem is that my design for float_arrays did not anticipate that I might
  24603. want to extend the arrays in both directions. I'm not sure that I want to
  24604. extend the low bound, but given this design, it's out of the question.
  24605. Inheritance is often advertised as a wonderful technique for reusing existing
  24606. code. But the reuse doesn't always work out the way you want. The reality is
  24607. that you must decide for each class that you build whether you intend to use
  24608. it as a base class for further derivation, and if so, how you or others might
  24609. wish to use it.
  24610. My design also raises another question. I said earlier that public inheritance
  24611. defines Is-A relationships. You might reasonably ask if float_vector really is
  24612. a float_array, or if I just used a convenient implementation trick.
  24613. I'll ponder this and other questions in the next part of this series.
  24614. References
  24615. Ellis, Margaret A. and Bjarne Stroustrup. 1990. The Annotated C++ Reference
  24616. Manual. Reading, MA: Addison-Wesley.
  24617. Stroustrup, Bjarne. 1991. The C++ Programming Language, 2nd. ed. Reading, MA:
  24618. Addison-Wesley.
  24619. Figure 1 Base class B and typical storage layout for derived class objects
  24620. Figure 2 Typical layout for a class D drived from B
  24621. Figure 3 Output from Listing 1
  24622. B::f()
  24623. B::g()
  24624. B::f()
  24625. D::g()
  24626. Figure 4 Output from Listing 2
  24627. B::f()
  24628. B::g()
  24629. B::h()
  24630. B::f()
  24631. B::g()
  24632. ... called from D::g()
  24633. B::h()
  24634. ... called from D::h()
  24635. Figure 5 Sample output from the program in Listing 6
  24636. low? 3
  24637. high? 6
  24638. fa = { 3 4 5 6 }
  24639. fb = { 3 4 5 6 }
  24640. fb = { 9 4 5 6 }
  24641. fb = { 9 16 5 6 }
  24642. fb = { 9 16 25 6 }
  24643. fb = { 9 16 25 36 }
  24644. fb = { 9 16 25 36 49 }
  24645. fb = { 9 16 25 36 49 64 }
  24646. fb = { 9 16 25 36 49 64 81 }
  24647. fb = { 9 16 25 36 49 64 81 100 }
  24648. fb.low() = 3
  24649. fb.high() = 10
  24650. fc = { 3 4 5 6 }
  24651. fc = { 3 4 5 123 }
  24652. fc = Assertion failed: i >= low, file fv1.cpp, line 19
  24653. Abnormal program termination
  24654.  
  24655. Listing 1 Overriding an inherited function
  24656. #include <iostream.h>
  24657.  
  24658. class B
  24659.  
  24660. {
  24661. public:
  24662. void f();
  24663. void g();
  24664. };
  24665.  
  24666. void B::f() { cout << "B::f()\n"; }
  24667.  
  24668. void B::g() { cout << "B::g()\n"; }
  24669.  
  24670. class D : public B
  24671. {
  24672. public:
  24673. void g();
  24674. };
  24675.  
  24676. void D::g() { cout << "D::g()\n"; }
  24677.  
  24678. int main()
  24679. {
  24680. B b;
  24681. b.f();
  24682. b.g();
  24683. D d;
  24684. d.f();
  24685. d.g();
  24686. return 0;
  24687. }
  24688.  
  24689. // End of File
  24690.  
  24691.  
  24692. Listing 2 Calling an overridden inherited function
  24693. #include <iostream.h>
  24694.  
  24695. class B
  24696. {
  24697. public:
  24698. void f();
  24699. void g();
  24700. void h();
  24701. };
  24702.  
  24703. void B::f() { cout << "B::f()\n"; }
  24704.  
  24705. void B::g() { cout << "B::g()\n"; }
  24706.  
  24707. void B::h() { cout << "B::h()\n"; }
  24708.  
  24709. class D : public B
  24710. {
  24711. public:
  24712. void g();
  24713. void h();
  24714. };
  24715.  
  24716. void D::g()
  24717. {
  24718. B::g();
  24719.  
  24720. cout << "... called from D::g()\n";
  24721. }
  24722.  
  24723. void D::h()
  24724. {
  24725. ((B *)(this))->h();
  24726. cout << "... called from D::h()\n";
  24727. }
  24728.  
  24729. int main()
  24730. {
  24731. B b;
  24732. b.f();
  24733. b.g();
  24734. b.h();
  24735. D d;
  24736. d.f();
  24737. d.g();
  24738. d.h();
  24739. return 0;
  24740. }
  24741.  
  24742. // End of File
  24743.  
  24744.  
  24745. Listing 3 Class definition for float_array
  24746. // fa1.h - a dynamic array of float using a subscripting
  24747. // object
  24748.  
  24749. #include <iostream.h>
  24750.  
  24751. class fa_index
  24752. {
  24753. friend class float_array;
  24754. public:
  24755. fa_index &operator=(float f);
  24756. operator float();
  24757. private:
  24758. fa_index(float_array *f, size_t i);
  24759. float_array *fa;
  24760. size_t ix;
  24761. };
  24762.  
  24763. class float_array
  24764. {
  24765. friend class fa_index;
  24766. public:
  24767. float_array(size_t n = 0);
  24768. float_array(const float_array &fa);
  24769. -float_array();
  24770. float_array &operator=(const float_array &fa);
  24771. float operator[](size_t i) const;
  24772. fa_index operator[](size_t i);
  24773. inline size_t length() const;
  24774. private:
  24775. void extend(size_t i);
  24776. float *array;
  24777. size_t len;
  24778. };
  24779.  
  24780.  
  24781. ostream &operator<<(ostream &os, const float_array &fa);
  24782.  
  24783. inline size_t float_array::length() const
  24784. {
  24785. return len;
  24786. }
  24787.  
  24788. // End of File
  24789.  
  24790.  
  24791. Listing 4 Class definition for float_vector
  24792. // fv1.h - a dynamic vector of float (with a possibly
  24793. // non-zero low-bound) using a subscripting object
  24794.  
  24795. #include <iostream.h>
  24796. #include "fa1.h"
  24797.  
  24798. class float_vector : public float_array
  24799. {
  24800. public:
  24801. float_vector(int lo = 0, int hi = 0);
  24802. float operator[](int i) const;
  24803. fa_index operator[](int i);
  24804. int low() const;
  24805. int high() const;
  24806. private:
  24807. int _low;
  24808. };
  24809. inline float_vector::float_vector(int lo, int hi)
  24810. : low(lo), float_array(hi - lo + 1)
  24811. { }
  24812.  
  24813. inline float_vector::low() const
  24814. {
  24815. return_low;
  24816. }
  24817.  
  24818. inline float_vector::high() const
  24819. {
  24820. return_low + length() - 1;
  24821. }
  24822.  
  24823. // End of File
  24824.  
  24825.  
  24826. Listing 5 Non-inline float_vector member functions.
  24827. // fv1.cp- a dynamic vector of float (with a possibly
  24828. // non-zero low-bound) using a subscripting object
  24829.  
  24830. #include "fv1.h"
  24831. #include <assert. h>
  24832.  
  24833. float float_vector::operator[](int i) const
  24834. {
  24835. assert(i >= low());
  24836. return float_array::operator[](i - low());
  24837. }
  24838.  
  24839.  
  24840. fa_index float_vector::operator[] (int i)
  24841. {
  24842. assert(i >= low());
  24843. return float_array::operator[](i - low());
  24844. }
  24845.  
  24846. // End of File
  24847.  
  24848.  
  24849. Listing 6 Test program for float_vector
  24850. // tv1.cpp - a test program for float_vectors and
  24851. // float_arrays
  24852.  
  24853. #include <iostream.h>
  24854. #include "fv1.h"
  24855.  
  24856. void display(const char *s, const float_array &fa)
  24857. {
  24858. cout << s << " = " << fa << endl;
  24859. }
  24860. int main()
  24861. {
  24862. int i, low, high;
  24863. cout << "low? ";
  24864. cin >> low;
  24865. cout << "high? ";
  24866. cin >> high;
  24867.  
  24868. float_vector fa(low, high);
  24869. for (i = fa.low(); i <= fa.high(); ++i)
  24870. fa[i] = i;
  24871. display("fa", fa);
  24872. float_vector fb = fa;
  24873. display("fb", fb);
  24874. for (i = low; i < low + 2 * fa.length(); ++i)
  24875. {
  24876. fb[i] = i * i;
  24877. display("fb", fb);
  24878. }
  24879. cout << "fb.low() = " << fb.low() << '\n';
  24880. cout << "fb.high() = " << fb.high() << '\n';
  24881. float_array fc = fa;
  24882. display("fc", fc);
  24883. i = fc.length();
  24884. fc[i - 1] = 123;
  24885. display("fc", fc);
  24886. cout << "fa[" << low - 1 << "] = ";
  24887. cout << fa[low - 1] << '\n';
  24888. return 0;
  24889. }
  24890.  
  24891. // End of File
  24892.  
  24893.  
  24894.  
  24895.  
  24896.  
  24897.  
  24898.  
  24899.  
  24900.  
  24901.  
  24902.  
  24903.  
  24904.  
  24905.  
  24906.  
  24907.  
  24908.  
  24909.  
  24910.  
  24911.  
  24912.  
  24913.  
  24914.  
  24915.  
  24916.  
  24917.  
  24918.  
  24919.  
  24920.  
  24921.  
  24922.  
  24923.  
  24924.  
  24925.  
  24926.  
  24927.  
  24928.  
  24929.  
  24930.  
  24931.  
  24932.  
  24933.  
  24934.  
  24935.  
  24936.  
  24937.  
  24938.  
  24939.  
  24940.  
  24941.  
  24942.  
  24943.  
  24944.  
  24945.  
  24946.  
  24947.  
  24948.  
  24949.  
  24950.  
  24951.  
  24952.  
  24953.  
  24954.  
  24955.  
  24956.  
  24957.  
  24958.  
  24959.  
  24960.  
  24961.  
  24962.  
  24963. C-Clearly
  24964.  
  24965.  
  24966. William Smith
  24967.  
  24968.  
  24969. William Smith is the engineering manager at Montana Software, a sorftware
  24970. development company specializing in custom applications for MS-DOS and
  24971. Windows. You may contact him by mail at P.O. Box 663, Bozeman, MT 59771-0663.
  24972.  
  24973.  
  24974. C is a free format language. It opens the door to an infinite variation of
  24975. appearances based on the programmers personal preference. Nearly every
  24976. programmer has opinions, sometimes very strong, of how C code should be
  24977. formatted. Arguments over curly brace placement, indentation, white space, and
  24978. comment placement abound. Over time, programmers first become comfortable with
  24979. a certain style, then attached and eventually religiously committed.
  24980. Style, especially the appearance aspect of style is a matter of personal
  24981. taste. You can radically change the appearance of C without changing what the
  24982. code does. Depending on the placement of indentation and white space you can
  24983. make the code very readable or nearly unreadable. Obviously the goal is to use
  24984. appearance to enhance the visibility of programming structures such as
  24985. functions, blocks, loops, etc. Unfortunately what is readable and visible to
  24986. one programmer is obfuscated to another.
  24987. C-Clearly by V Communications is a C & C++ source code formatting utility that
  24988. claims to be able to give you complete control over code appearance. It is a
  24989. "you can have it any way you like it" utility. C-Clearly attempts to be the
  24990. ultimate code beautifier/formatter.
  24991. If true, it ends the argument on what appearance is appropriate. Every
  24992. programmer can have it his or her own way. In a work group situation,
  24993. programmers can use the style they are comfortable with and later reformat
  24994. their code to the standards specified by the group. Alternatively, a
  24995. programmer can take someone else's code and reformat it into the style that he
  24996. or she is most comfortable with and efficient at reading. Sounds good in
  24997. theory, but there are limitations.
  24998. First of all, style is not just where the curly braces go. Style consists of
  24999. both structure and appearance. Structure involves programming constructs,
  25000. variable naming, and the type of techniques to execute a certain process.
  25001. C-Clearly cannot do anything about this aspect of coding style. What it can do
  25002. is alter the appearance of code. And even this has its limits.
  25003.  
  25004.  
  25005. What C-Clearly Does
  25006.  
  25007.  
  25008. There are infinite combinations of appearance options for C and C++ code.
  25009. Instead of a long list of command-line options or an input screen containing a
  25010. myriad of check boxes and radio buttons, C-Clearly's user interface for
  25011. selecting a specific combination of appearance options is a template file. The
  25012. template file is a recipe of pseudo-C commands that set the formatting
  25013. definitions.
  25014. C-Clearly comes with six template files, three each for C and C++. Listing 1,
  25015. knr. ccl, is the template file for K&R-style C code. You can create your own
  25016. template file by starting with one of the delivered template files and editing
  25017. it. You have to be careful when editing a template file. You can only modify
  25018. the white space in the template file.
  25019. In addition to the this restriction, the fact that some constructs are defined
  25020. in more than one place creates another restriction. The constructs that are
  25021. defined in more than one place must be consistent or C-Clearly will warn you
  25022. when you have conflicting appearance constraints.
  25023. C-Clearly supports both a command-line and an interactive user interface. The
  25024. command-line interface is a subset of the interactive user interface. You
  25025. cannot specify any style formatting options on the command line. You must use
  25026. the interactive user interface to specify formatting options and then save
  25027. them as the default.
  25028. C-Clearly stores the default settings directly in the executable file. This
  25029. means it writes to and modifies the executable file. Most Anti-Virus software
  25030. will detect this. So you will have to tell your Anti-Virus program to ignore
  25031. this situation.
  25032. Besides the template file there are four categories of additional appearance
  25033. choices. The first category involves margins, tabs, and nesting lines. Nesting
  25034. lines involve adding lines to show the connection between the beginning and
  25035. end of logical blocks.
  25036. The second category involves comments. You can select how to align and group
  25037. comments. C-Clearly will make attempts to beautify comments, but I have never
  25038. found what it does to my liking. I usually turn most of the comment formatting
  25039. options off.
  25040. The third category applies to white space. You can tell C-Clearly to retrain
  25041. existing blank lines depending upon their placement.
  25042. The fourth category is for hardcopy output only. You can send the output
  25043. directly to a printer or to a file that is ready to print. When generating
  25044. hardcopy output, C-Clearly can add line numbers, printer escape codes, and
  25045. additional information such as headers, footers, and code metrics information.
  25046. By inserting printer escape codes around different code constructs you can
  25047. select printing styles or fonts. For example, you can print all comments in
  25048. italics. There are no printer drivers so you will have to determine the escape
  25049. codes for the printer you are using.
  25050. The combination of code templates, an interactive user interface for
  25051. additional style selection, and a terse commandline interface works well for
  25052. C-Clearly. I find it quite easy to use.
  25053. The user manual is perfect bound and about 55 pages in length. Chapter 2
  25054. covers template files. It is only a couple of pages in length, but if you are
  25055. going to create your own template file it is must reading.
  25056. This all sounds good until you have to start formatting some code. Even though
  25057. C-Clearly has a lot going for it, it has some severe limitations. As soon as
  25058. you are faced with the chore of formatting files, you will most certainly bump
  25059. up against C-Clearly's unfortunate limitations.
  25060.  
  25061.  
  25062. What C-Clearly Doesn't Do
  25063.  
  25064.  
  25065. C-Clearly requires syntactically correct source code. This in itself is not
  25066. that big a drawback. If your code complies it is probably syntactically
  25067. correct. Well, not always, according to C-Clearly. C-Clearly does not like
  25068. some C constructs that are rigorously correct in syntax and compile just fine.
  25069. The ability of C-Clearly to have so much control and flexibility in formatting
  25070. code means that it has to parse and understand code nearly to the same level
  25071. as a compiler. Unfortunately, it chokes on many complex but entirely valid C
  25072. constructs. When C-Clearly finds a syntax it does not like, it quits and makes
  25073. no further attempt to format the file.
  25074. This problem is a serious flaw in C-Clearly. It truly cripples an otherwise
  25075. fine product. From experience, an important area of weakness that I have
  25076. identified is in handling types and macros that deal with types.
  25077. C-Clearly has trouble dealing with typedefs, structures, defines, and
  25078. variables that happen to have the same name. This may not be a good idea to
  25079. have the same name for all these different things, but it is valid. C-Clearly
  25080. also has problems with macros that expand to structure member references. For
  25081. example macros similar to the following have caused me trouble with C-Clearly.
  25082. #define MEMBER StructInstance->Member
  25083. The token pasting preprocessor operator, ##, can also cause C-Clearly to fail
  25084. on perfectly correct code.
  25085. Sometimes you can coax C-Clearly to still format a file by turning off
  25086. formatting or syntax checking for the sections it can not handle. This has the
  25087. drawback of requiring you to insert C-Clearly instructions in the form of
  25088. specialized comments into your code. It may still not get some files to work.
  25089. C-Clearly ignores the code that you have flagged and then expects the
  25090. unflagged code to still be syntactically correct.
  25091. Since C-Clearly has the bad habit of not liking certain code structures and
  25092. flagging them as syntax errors, I have found it useful to monitor the
  25093. program's return code from within a batch file. Although not documented in the
  25094. users manual, C-Clearly returns 1 if it fails. You can access this in a batch
  25095. file as the MS-DOS errorlevel. I use this technique to conditionally execute
  25096. further processing on the source file after C-Clearly.
  25097. I have not had success getting C-Clearly to consistently output tabs for
  25098. indentation. Consequently, I process the file after C-Clearly to convert
  25099. groups of leading spaces to tabs. The following is an MS-DOS batch file
  25100. listing.
  25101. CCL %1 OUTPUT.CCL
  25102. IF ERRORLEVEL 1 GOTO :EXITERROR
  25103. ENTAB OUTPUT.OUT 8
  25104. COPY OUTPUT.OUT %1
  25105. :EXITERROR
  25106. ERASE OUTPUT.CCL
  25107. I also add the comment, /* End of File */ to the end of every source file.
  25108. Although C-Clearly can add comments to closing braces, it does not add
  25109. comments (footers) to file output. You can add footers to hard-copy
  25110. (printer-ready) output.
  25111. I have an additional major complaint. Even with all of its flexibility, I
  25112. cannot get C-Clearly to format code exactly the way I want. The problem is
  25113. with long expressions that must be continued on multiple lines. With very long
  25114. source lines, I like to find a convenient place to make a new line and
  25115. continue the expression on the next line indented two levels from the indent
  25116. level of the expression. Try as I might, I have not been able to get C-Clearly
  25117. to do this exactly the way I want. I cannot even get it to leave continued
  25118. lines alone. Since C-Clearly comes so close to giving you what you want and
  25119. your expectations are high, this is especially annoying.
  25120. The other complaints stem from the limitations of memory imposed by MS-DOS. I
  25121. have started to run out of memory with long listings, especially code for
  25122. Microsoft Windows that include WINDOWS.H. Since WINDOWS.H is more than 5,000
  25123. lines in length, this is not surprising. As file length goes up, C-Clearly
  25124. gets significantly slower. A protected-mode version may remedy these problems.
  25125.  
  25126.  
  25127. User Support -- Upgrade Policy
  25128.  
  25129.  
  25130.  
  25131. I started using C-Clearly when it first came out about three years ago. I
  25132. expected a lot from the product. I was not shy in calling V Communications and
  25133. expressing my desires for C-Clearly to do something that it presently could
  25134. not do or pointing out limitations and bugs in the product. V Communications
  25135. responded with a closely-spaced series of upgrades. Many of these upgrades
  25136. came free of charge.
  25137. This experience and the ease of access to the technical people involved with
  25138. the product impressed me. V Communications seemed committed to a quality
  25139. product and committed to user support.
  25140. C-Clearly has evolved to a state that works for me. I have found work arounds
  25141. for most of the limitations and rarely contact the company anymore. The last
  25142. upgrade cost me $45.00.
  25143.  
  25144.  
  25145. Conclusions
  25146.  
  25147.  
  25148. C-Clearly ambitiously sets out to be the ultimate C and C++ source code
  25149. reformatting utility. It comes close. It fails in two critical areas. The
  25150. first is that I cannot get C-Clearly to format code exactly the way I want it.
  25151. The other serious flaw is that it will not format some perfectly good code. It
  25152. stops when it thinks it has found a syntax error or runs low on memory.
  25153. If you are willing to live with these limitations, C-Clearly is a powerful
  25154. source code maintenance tool. I have found it useful and use it frequently. I
  25155. have also found it frustrating and nearly always do additional processing to
  25156. files after C-Clearly is done with them.
  25157. When I have to read someone else's code that is in a style I am not
  25158. comfortable with, C-Clearly has come to my rescue many times. C-Clearly is not
  25159. a replacement for good coding style rules such as choice of variable names,
  25160. consistency, and commenting, but it sure helps put to rest some of the code
  25161. appearance arguments.
  25162.  
  25163.  
  25164. Note from the editor:
  25165.  
  25166.  
  25167. V Communications has released version 2.1 of C-Clearly which addresses some of
  25168. the limitations outlined in this review. For specific differences please
  25169. contact V Communications.
  25170. C-Clearly, Version 2.0
  25171. V Communications, Inc.
  25172. 4320 Stevens Creek Blvd., Suite 275
  25173. San Jose, CA 95129
  25174. Phone: 800-648-8266
  25175. Price: $129.95
  25176. Hardware Requirements: IBM PC Compatible, MS-DOS, and 512K RAM
  25177.  
  25178. Listing 1 C-Clearly template file for K&R code for C code
  25179. #include "lib.h"
  25180. #define Macro( Param1, Param2 ) Param1 + Param2
  25181.  
  25182. static int i, (far *pfi)();
  25183.  
  25184. int v = 0;
  25185. int ArrayName[XSize][YSize] = {{Init1, Init2}, {Init3}};
  25186.  
  25187. enum EnumName {Enum=1, Enum2};
  25188.  
  25189. struct StructName {
  25190. int Field, Field;
  25191. int Field, Width;
  25192. };
  25193.  
  25194. void ANSIProtoFunc1 ();
  25195. void ANSIProtoFunc2 ();
  25196.  
  25197. static int ANSIFunction (char Param[], int *PtrParam)
  25198. {
  25199. long int Variable;
  25200. int Variable;
  25201.  
  25202. if (Expression) {
  25203. Statement;
  25204. }
  25205. else if (Expression) {
  25206. Statement;
  25207. }
  25208. else {
  25209. Statement;
  25210.  
  25211. }
  25212. if (Expression) {
  25213. int LocalVariable;
  25214. int LocalVariable;
  25215.  
  25216. Statement;
  25217. Statement;
  25218. }
  25219. else if (Expression) {
  25220. int LocalVariable;
  25221. int LocalVariable;
  25222.  
  25223. Statement;
  25224. Statement;
  25225. }
  25226. else {
  25227. int LocalVariable;
  25228. int LocalVariable;
  25229.  
  25230. Statement;
  25231. Statement;
  25232. }
  25233. Array [Index].Field = (int *)PostOp++ * ++PreOp;
  25234. Struct->Field = -UnaryOp * sizeof Variable;
  25235. if ((Value = FunctionName ()) == sizeof (Type))
  25236. Statement;
  25237. else if (Expression)
  25238. Statement;
  25239. else
  25240. Statement;
  25241. {
  25242. Statement;
  25243. }
  25244. {
  25245. int LocalVariable;
  25246. int LocalVariable;
  25247.  
  25248. Statement;
  25249. Statement;
  25250. }
  25251. while (Expression)
  25252. Statement;
  25253. while (Expression) {
  25254. Statement;
  25255. }
  25256. while (Expression) {
  25257. int LocalVariable;
  25258. int LocalVariable;
  25259.  
  25260. Statement;
  25261. Statement;
  25262. }
  25263. switch (Expression) {
  25264. case Value: {
  25265. Statement;
  25266. break;
  25267. }
  25268. default:
  25269. Statement;
  25270.  
  25271. break;
  25272. }
  25273. return Expression;
  25274. }
  25275.  
  25276. void KnRFunction (Param1, Param2)
  25277. int Param1;
  25278. int Param2;
  25279. {
  25280. FunctionCall (Param, TestExpr ? ThenExpr : ElseExpr);
  25281. goto Label;
  25282. for (Expression; Expression; Expression, CommaExpression)
  25283. Statement;
  25284. for (Expression; Expression; Expression, CommaExpression) {
  25285. Statement;
  25286. }
  25287. for (Expression; Expression; Expression, CommaExpression) {
  25288. int LocalVariable;
  25289. int LocalVariable;
  25290.  
  25291. Statement;
  25292. Statement;
  25293. }
  25294. do
  25295. Statement;
  25296. while (Expression);
  25297. do {
  25298. Statement;
  25299. } while (Expression);
  25300. do {
  25301. int LocalVariable;
  25302. int LocalVariable;
  25303.  
  25304. Statement;
  25305. Statement;
  25306. } while (Expression);
  25307. return (Expression);
  25308. }
  25309.  
  25310. /* End of File */
  25311.  
  25312.  
  25313.  
  25314.  
  25315.  
  25316.  
  25317.  
  25318.  
  25319.  
  25320.  
  25321.  
  25322.  
  25323.  
  25324.  
  25325.  
  25326.  
  25327.  
  25328.  
  25329.  
  25330.  
  25331.  
  25332.  
  25333.  
  25334. The Art of Programming Embedded Systems
  25335.  
  25336.  
  25337. Mark Gingrich
  25338.  
  25339.  
  25340. Mark Gingrich has been employed in the medical device industry for the past
  25341. nine years. Presently he serves as a software engineer for Baxter Healthcare
  25342. Corporation's Novacor division. He can be reached at 355 Estabrook St., Apt.
  25343. 403, San Leandro, CA 94577.
  25344.  
  25345.  
  25346. The series of books by Donald Knuth called The Art of Computer Programming may
  25347. be the archetype of its genre. So I approach recent epics leading with "The
  25348. Art of" title with elevated expectation. The Art of Programming Embedded
  25349. Systems is among the latest (though unrelated to Knuth's trilogy). Its author,
  25350. Jack Ganssle, checks in with good credentials: a contributing editor for
  25351. Embedded Systems Programming magazine, a designer and purveyor of in-circuit
  25352. emulators, and a veteran practitioner of said "art."
  25353. But why a book on coding embedded systems? Perhaps because the topic is so
  25354. woefully treated in the engineering/computer science curricula. More often
  25355. it's a trade learned on the job--the hard way--at no small expense to our
  25356. employers. And many of us drift into this sea having cast off on the purely
  25357. hardware or purely software oceans. The luckiest benefit from a mentor helping
  25358. to steer around obstacles: how to coerce the compiler to accept writeable
  25359. variables distinct from RAM; how to debug optimized code sans print
  25360. statements; how to configure the emulator to trap bugs which occur only during
  25361. neap tides on Groundhog Day; how to do this; how not to do that. Don't have a
  25362. mentor as such? Well, then, this book may be a reasonable alternative.
  25363. Only don't expect a tutorial from square zero. Gannsle's approach is more
  25364. casual--rather like talking shop with colleagues. He assumes that you've
  25365. already served your software apprenticeship; now your goal is to fill those
  25366. gaps of wisdom which have postponed your transition to true embedded systems
  25367. programming enlightenment. The parallel path to this state of being entails
  25368. time-consuming and costly mistakes (the euphemism is called "experience").
  25369. And experience is seldom acquired in each and every aspect of embedded design.
  25370. For example, chief among my own gaps of wisdom is one in memory management
  25371. techniques, having never employed bank switching on a project. Ganssle comes
  25372. through in chapter six with a clear depiction of the camouflaged snake pits
  25373. lurking in this area. Reading this chapter made it plainly apparent that I
  25374. would have pathetically underestimated the time required to implement a
  25375. bank-switching scheme.
  25376. Likewise, a good introduction to real-time operating systems is found in
  25377. chapter nine. Although not the be-all, end-all word on the subject, it's an
  25378. appropriate diving-in point for the novice before swimming through the
  25379. voluminous sales literature and spec sheets from the umpteen RTOS-to-go
  25380. vendors. Of particular value is the small--but functional--real-time executive
  25381. supplied in source listing form.
  25382. Ever need a lone transcendental function in your system? Instead of calling
  25383. the compiler's bloated, glacier-speed floating-point math library routine
  25384. (which returns a result with three digits of precision more than you require),
  25385. why not roll your own? Ganssle shows how--illustrated with C--in chapter
  25386. seven.
  25387. In addition, there are chapters on interrupt management; on signal smoothing
  25388. and curve fitting (especially intriguing is the Savitsky and Golay technique);
  25389. on software design which allows for civilized debugging; on designing to
  25390. permit simplified production test--always guaranteed to endear you with the
  25391. harried, under-appreciated manufacturing folk.
  25392. And interspersed with the lucid, here's-the-way-it-is writing style are
  25393. snippets of reality--flashbacks from Ganssle's eventful past:
  25394. "It always seems that just before a demo everything falls apart. After a late
  25395. night of removing the final bugs from microcontroller-based design, I
  25396. unplugged the emulator and installed the computer chip. On power up the unit
  25397. did nothing -- it was completely dead. Fortunately the code had a simple test
  25398. routine that blinked an LED before any other initialization took place. Since
  25399. the LED didn't blink, I knew immediately that the code was not starting and
  25400. indeed found that a floating DMA request line was keeping the processor idle.
  25401. The emulator's slightly different DC characteristics masked the problem during
  25402. weeks of code development."
  25403. Such anecdotal digressions in the prose are welcome. They add realism. And
  25404. they underscore that the proffered advice is not rarefied academic theory;
  25405. these are eyewitness war stories from the front.
  25406. Occasionally, too, Ganssle opines on the softer issues of software
  25407. development: programming style and professional improvement. And he confronts
  25408. business issues so often avoided like the plague by the technical staff. This
  25409. holistic approach is commendable. The still-too-pervasive image of
  25410. "proglodytes" (wearing pocket protectors, of course) hacking away in the back
  25411. room, oblivious to the rest of the world, has been a hindrance to our
  25412. collective professional advancement. There is a bottom line, and Ganssle steps
  25413. back to point out our role and responsibilities within the big picture.
  25414. Reading widely is among our responsibilities, we're admonished. So Gannsle
  25415. supplies an eclectic bibliography: from techy Intel application notes to Alvin
  25416. Toffler's Powershift. (Though I would have preferred a more exhaustive
  25417. reference section--pointers to the richest embedded systems lore. Indigenous
  25418. software types, for instance, may need to "speak" electronic more
  25419. proficiently; another "art of" book, The Art of Electronics, by Horowitz and
  25420. Hill, is an appropriate text. Those of the hardware stripe would benefit from,
  25421. say, Kernighan and Plauger's The Elements of Programming Style.) An appendix
  25422. with recommended periodicals for the cognizant embedded programmer is also
  25423. offered. (The C Users Journal makes the list; but somehow Dr. Dobb's Journal
  25424. is omitted, a conspicuous oversight considering it is cited elsewhere in the
  25425. book.)
  25426. Be advised, however, that the "art" presented is not the state of the art.
  25427. Embedded systems are described as they've existed over the past few years,
  25428. with 4-, 8-, and 16-bit processors. There are no visits from the ghost of
  25429. Embedded-Programming-Yet-To-Be. One must look elsewhere for coverage of fuzzy
  25430. logic, neural nets, and DSP chips as embedded controllers.
  25431. Mind you, I heartily recommend this book, but there are a few too many warts,
  25432. most of which should have been removed with scrupulous copy editing. On page
  25433. 152 the definitions of accuracy and precision are confused, as is the
  25434. described behavior of the sine function, and the constant pi/2 is termed a
  25435. "round" number. (Ironically, these blunders occur on a page with the subhead
  25436. "Errors.") Elsewhere, the repeated misspelling of "kernel," the missing
  25437. arrowhead in the state diagram in Figure 3.2, and the interchanged x-y
  25438. coordinates in Figure 7.4 are annoying flaws. The state diagram in Figure 9.1
  25439. is simple, but it could have been drawn without the confusion-adding crossed
  25440. lines.
  25441. Then there are the source listings. Yes, there's enough source in this book
  25442. (but, alas, no companion disk) to satisfy your minimum daily requirement of
  25443. real code: much of it in C, a few in sundry assembly languages, and one
  25444. listing in Basic. But the art of software book publishing demands faithful
  25445. reproduction of listings. Just a cursory scan caught a missing right
  25446. parenthesis in the for loop on page 31; the phex routine on page 95 lost a
  25447. curly bracket somewhere; page 96 contains a commented-out source line, which
  25448. is somewhat disconcerting. These typos along with the schizophrenic
  25449. indentation style hint of manually-typeset code listings--a dangerous
  25450. practice. My overall impression: Academic Press skimped on (or rushed) the
  25451. proofreading and the illustrations.
  25452. These are nitpicking complaints. I'm being a bit harsh because such a valuable
  25453. work deserves better handling. And pricey books with lofty titles justifiably
  25454. receive more intense scrutiny. But I'll apply a more pragmatic rule of thumb:
  25455. If a book's cost and the invested reading time is more than compensated by the
  25456. added quality and productivity of my work, or to the improved quality of my
  25457. company's product, it's an unequivocable bargain. Without question, The Art of
  25458. Programming Embedded Systems hits this critical breakpoint.
  25459. Title: The Art of Programming Embedded Systems
  25460. Author: Jack G. Ganssle
  25461. Publisher: Academic Press
  25462. Price: $49.00
  25463. ISBN: 0-12-274880-8
  25464.  
  25465.  
  25466.  
  25467.  
  25468.  
  25469.  
  25470.  
  25471.  
  25472.  
  25473.  
  25474.  
  25475.  
  25476.  
  25477.  
  25478.  
  25479.  
  25480.  
  25481.  
  25482.  
  25483.  
  25484.  
  25485.  
  25486.  
  25487.  
  25488.  
  25489.  
  25490.  
  25491.  
  25492.  
  25493.  
  25494.  
  25495. Editor's Forum
  25496. I'm back on the subject of standards again. (See the Editor's Forum, CUJ
  25497. November 1992 and January 1993.) The most interesting news is that the ISO C
  25498. standards committee WG14 voted out an amendment to the C language at its last
  25499. meeting, back in December. It includes a (much modified) set of alternate
  25500. spellings for all those operators and punctuators that use funny characters
  25501. not widely available. It also includes lots more functions for manipulating
  25502. the large character sets used by the Japanese, Chinese, and several other
  25503. cultures.
  25504. Future editions of my column, "Standard C," will discuss the technical details
  25505. in greater depth. They are of interest mostly to people who write for
  25506. international markets. My experience is that more and more of you will fall
  25507. into that category as time goes by. On the subject of time, however, don't
  25508. feel too rushed. The amendment still faces at least two votes within ISO SC22,
  25509. the parent committee. Don't look for an official standard for many months to
  25510. come.
  25511. The next most interesting news is that SC22 has finally given us clear
  25512. guidance for both interpreting and patching the C Standard. WG14 has begun by
  25513. picking up all the ANSI Requests for Interpretation. They should finally see
  25514. the light of day as an ISO Record of Response. WG14 will continue to ask
  25515. X3J11, the original authors of the ANSI C Standard, for assistance in forming
  25516. responses. But we can now use a more streamlined ISO channel for closing the
  25517. loop.
  25518. My job as Convenor of WG14 effectively makes me caretaker for the Standard C
  25519. programming language. Besides convening WG14 meetings on a regular basis, I am
  25520. now the keeper of what SC22 calls the Defect Report Log -- the formal requests
  25521. for interpretation of (or correction to) the C Standard. By an administrative
  25522. quirk, I can also personally expedite the filing of Defect Reports.
  25523. Please don't take this admission as an invitation to send in all your random
  25524. queries about the C Standard. I reserve the right not to sponsor any Defect
  25525. Report that I choose. But if your organization needs a technical
  25526. clarification, sending the request straight to me just might lob a month or
  25527. two off of going through ANSI or another ISO member body.
  25528. I did make good on my threat to resign all my posts within ANSI. That saves me
  25529. a lot of money and a bit of time attending meetings. It costs me the right to
  25530. vote on how C and C++ evolve, but what the heck. I hope to use the extra money
  25531. and time to get more book writing done in 1993. That assumes, of course, that
  25532. you don't all deluge me with interpretation requests.
  25533. P.J. Plauger
  25534. pjp@plauger.com
  25535.  
  25536.  
  25537.  
  25538.  
  25539.  
  25540.  
  25541.  
  25542.  
  25543.  
  25544.  
  25545.  
  25546.  
  25547.  
  25548.  
  25549.  
  25550.  
  25551.  
  25552.  
  25553.  
  25554.  
  25555.  
  25556.  
  25557.  
  25558.  
  25559.  
  25560.  
  25561.  
  25562.  
  25563.  
  25564.  
  25565.  
  25566.  
  25567.  
  25568.  
  25569.  
  25570.  
  25571.  
  25572.  
  25573.  
  25574.  
  25575.  
  25576.  
  25577.  
  25578.  
  25579.  
  25580.  
  25581.  
  25582.  
  25583.  
  25584.  
  25585.  
  25586.  
  25587. New Products
  25588.  
  25589.  
  25590. Industry-Related News & Announcements
  25591.  
  25592.  
  25593.  
  25594.  
  25595. ParcPlace Introduces VisualWorks
  25596.  
  25597.  
  25598. ParcPlace Systems, Inc., has introduced VisualWorks, an application
  25599. development environment (ADE) for corporate developers creating graphical,
  25600. client/server applications that are portable across PC, Macintosh, and UNIX
  25601. platforms (Sun, IBM, HP, DEC, Sequent). VisualWorks includes a GUI builder,
  25602. database access capabilities, and a reusable application framework.
  25603. The GUI builder provides a point-and-click palette and canvas, with layout
  25604. tools that include a menu builder, an icon painter, and a color tool.
  25605. ChamelionView, a component of the GUI builder illustrates portability of a new
  25606. GUI across various front-ends, including Windows, Motif, OS/2 Presentation
  25607. Manager, Macintosh, and OPENLOOK. ChameleonView allows developers to preview
  25608. the new interface in the native application look of these platforms.
  25609. VisualWorks provides direct access to Oracle and Sybase databases. Through
  25610. Information Builders, Inc.'s EDA/SQL gateway, more than 48 different databases
  25611. can be accessed.
  25612. VisualWorks is priced at $2,995 for Windows, OS/2, and Macintosh, and at
  25613. $4,995 for UNIX. Database drivers cost $495 for Oracle or Sybase and $995 for
  25614. EDA/SQL. Contact ParcPlace Systems, 999 E. Arques Avenue, Sunnyvale, CA 94086,
  25615. (408) 481-9090; FAX: (408) 481-9095.
  25616.  
  25617.  
  25618. Xionics Announces PowerTools and ImageSoft 2.0 for Image Processing
  25619.  
  25620.  
  25621. Xionics, a developer of image acceleration technology, has announced
  25622. PowerTools application programming interface for MS-Windows image processing,
  25623. and ImageSoft 2.0, an update of their C library for image applications
  25624. software. PowerTools consists of a Windows Dynamic Link Library (DLL) with ten
  25625. high-level commands to control the aspects of monochrome document image
  25626. processing. The PowerTools commands each operate by issuing a string of calls
  25627. to several of the underlying C routines in Xionics ImageSoft Libraries.
  25628. PowerTools Release 1.0 is available as object code, and is compatible with
  25629. standard C compilers, such as Microsoft C and Turbo C.
  25630. ImageSoft 2.0 encompasses over 80 routines for image processing: scanning,
  25631. printing, compression/decompression, display, and enhancement. Xionics
  25632. maintains a policy of "assuring complete backward compatibility" in its API.
  25633. PowerTools is priced at $895, royalty-free. The price includes one year of
  25634. technical support and software updates. Contact Xionics Inc., Two Corporation
  25635. Way, Peabody, MA 01960, (508) 531-6666; FAX: (508) 531-6669.
  25636.  
  25637.  
  25638. Micro Digital Introduces smx++
  25639.  
  25640.  
  25641. Micro Digital has announced smx++, an Application Program Interface (API)
  25642. which allows C++ programmers to access smx (simple multitasking executive)
  25643. multi-tasking features in object form. smx++ supports Borland C++ v3.1 and
  25644. Microsoft C++ v7.0.
  25645. smx++ is a C++ class library consisting of nine base classes and seven derived
  25646. classes. Redundant and seldom-used smx functionality has been omitted and
  25647. orthogonality has been improved, in order to create a simpler API. A shallow
  25648. class hierarchy was designed to preserve performance. smx++ runs on top of smx
  25649. and direct smx C function calls can still be performed, and smxProbe still
  25650. works the same. The new design made all smx objects (e.g., tasks, messages,
  25651. semaphores, etc.) fully dynamic--deletable as well as createable.
  25652. C++ developers can derive classes from the smx++ classes. For example, device
  25653. drivers can be derived from the Bucket and Pipe classes, (which have been
  25654. derived from the Software I/O Bus (SIObus) class).
  25655. smx++ is priced at $2995 (including a royalty-free license), with source
  25656. available for $1000, and smxProbe for $500. Contact Micro Digital, Inc., 6402
  25657. Tulagi Street, Cypress, CA 90630-5630, (800) 366-2491 or (714) 373-6862; FAX:
  25658. (714) 891-2363.
  25659.  
  25660.  
  25661. Integrated Development Announces LibTools
  25662.  
  25663.  
  25664. Integrated Development Corp. has announced LibTools, a set of programmer's
  25665. tools for creating, managing, and exploring libraries of C, C++, Assembly,
  25666. Xbase, and other Intel-, Microsoft-, and Borland-compatible object modules.
  25667. LibTools provides extensive reporting and cross-referencing capabilities.
  25668. LibTools can resolve public symbol conflicts, forecast overly-large
  25669. executables, and verify module integrity. LibTools can show a complete list of
  25670. the public and external references in a module, showing what will be linked in
  25671. when you call a particular function. LibTools' LibComp utility can compare two
  25672. libraries and produce a list of duplicate symbols, and LibTools can rename
  25673. external and public symbols to resolve conflicts. LibTools also includes a
  25674. Library Dump utility, which provides a detailed listing of the complete
  25675. contents of a library. LibTools documentation includes tutorials, an
  25676. introduction to library management, and tips on designing more granular
  25677. libaries.
  25678. Contact Integrated Development Corp., 190 Main Street, P.O. Box 592,
  25679. Hampstead, NH 03841, (603) 329-5522 or (800) 333-3429; FAX: (603) 329-4842;
  25680. CIS: 700441,2465.
  25681.  
  25682.  
  25683. Archimedes Adds C Tools for Hitachi Microcontrollers
  25684.  
  25685.  
  25686. Archimedes Software, Inc. has introduced C cross compilers and
  25687. debuggers/simulators for the Hitachi H8/300 and H8/500 microcontroller
  25688. families. The Archimedes C-Cross Compilers for the microcontrollers follow the
  25689. ANSI C standard and support all the required libraries. The compilers provide
  25690. several memory models (six of the H8/300 and 11 for the H8/500). Pre-defined
  25691. in-line functions support interrupt handling. The compiler provides both
  25692. single and double precision IEEE floating-point library functions. The
  25693. compilers generate relocatable code and the Archimedes linker generates any of
  25694. 32 different output formats for different emulators and PROM-programmers.
  25695. The family of C-SPY High-Level Language Debuggers/Simulators for embedded
  25696. applications support the Archimedes C-Cross compilers. C-SPY is avialble in
  25697. simulator and emulator driver versions. The emulator versions support the
  25698. MIME-700 in-circuit emulator from Pentica Systems, Inc. The H8/300 and H8/500
  25699. Cross Compilers are hosted on PCs, HP 9000, Sun SPARCs, and DEC MicroVax and
  25700. Vax platforms. The compilers are priced starting at $1295 for H8/300 on a PC.
  25701. C-SPY is supported on PC compatibles, and priced at $1195 for the H8/300 and
  25702. $1995 for the H8/500. Contact Archimedes Software, Inc., 2159 Union Street,
  25703. San Francisco, CA 94123, (415) 567-4010; FAX: (415) 567-1318.
  25704.  
  25705.  
  25706. Nu-Mega Announces BOUNDS-CHECKER 2.0, for MS-DOS Memory Protection
  25707.  
  25708.  
  25709. Nu-Mega Technologies, Inc., has announced BOUNDS-CHECKER 2.0, an MS-DOS memory
  25710. protection tool that provides real-time memory and heap protection.
  25711. BOUNDS-CHECKER 2.0 can detect problems in a program's heap, stack, or data
  25712. segment; handles array over-run detection; finds illegal memory accesses
  25713. outside a program; and finds code overwrites automatically. BOUNDS-CHECKER
  25714. adds a Smart Mode feature, with built-in heuristics to determine the
  25715. legitimacy of a memory access, and avoid unnecessarily flagging legitimate
  25716. out-of-bounds accesses (e.g., video memory, BIOS variables, etc.).
  25717. BOUNDS-CHECKER 2.0 doesn't require anything to be linked-in or compiled, but
  25718. can work directly with both Microsoft C 7.0 and Borland 3.1 with the VROOM
  25719. overlay, as well as support memory managers such as QEMM. Contact Nu-Mega
  25720. Technologies, Inc., P.O. Box 7780, Nashua, NH 03060-7780, (603) 889-2386; FAX:
  25721. (603) 889-1135.
  25722.  
  25723.  
  25724. StratosWare Releases MemCheck for the Macintosh
  25725.  
  25726.  
  25727. StratosWare has introduced versions of its error detection and prevention
  25728. product, MemCheck for the Macintosh, for the Think C and MPW C environments.
  25729. MemCheck requires no source code changes. MemCheck detects memory overwrites
  25730. and underwrites, memory leaks, heap corruption, and other memory errors.
  25731. MemCheck operates transparently, appearing only to report errors with source
  25732. file and line information. MemCheck for the Macintosh detects failure of
  25733. memory allocation routines, failure of many resource operations, invalid
  25734. operations on unlocked or purged handles, and inappropriate use of
  25735. non-resource handles. One include file per source module is required to
  25736. configure projects, and an automated configuration tool is included, MemCheck
  25737. can be switched on or off at runtime, linked out via the production library,
  25738. or compiled out with no source code changes. Contact StratosWare Corporation,
  25739. 1756 Plymouth Road, Suite 1500, Ann Arbor, MI48105, (313) 996-2944 or (800)
  25740. 933-3284; FAX: (313) 747-8519.
  25741.  
  25742.  
  25743.  
  25744. Scientific Endeavors Announces GraphiC/Win Windows Graphics Library
  25745.  
  25746.  
  25747. Scientific Endeavors has announced GraphiC/Win, a version of its C graphics
  25748. library for Windows. The features of the MS-DOS version of GraphiC have been
  25749. provided under Windows, allowing scientists to create graphics for
  25750. publication. GraphiC/Win creates and manages its own window and resources, and
  25751. adds features to take advantage of the Windows environment. GraphiC/Win will
  25752. create Windows metafiles and bitmaps and copy both to the Windows clipboard.
  25753. GraphiC/Win uses Windows video and printer drivers. Graphics are stored using
  25754. the high-resolution Tektronix 4105 format; graphics can be exported in
  25755. Postscript, GEM, Lotus PIC, HPGL, HPGL/2, and TIFF formats. GraphiC's routines
  25756. come as source code. GraphiC/Win is priced at $495. Contact Scientific
  25757. Endeavors Corporation, 508 North Kentucky Street, Kingston, TN 37763, (615)
  25758. 376-4146 or (800) 998-1571; FAX: (615) 376-1571.
  25759.  
  25760.  
  25761. Instrumentation Software Adds Stand-Alone Libraries
  25762.  
  25763.  
  25764. National Instruments has announced LabWindows for MS-DOS Version 2.2, an
  25765. instrumentation software package (for instrument control, data acquisition,
  25766. analysis, and presentation) which now includes stand-alone libraries for the
  25767. Borland C++ and Turbo C++ compilers and the Microsoft Visual Basic for DOS
  25768. (VBDOS) compiler. Users can access the Borland compiler and linker from within
  25769. the LabWindows programming environment, or they can add the LabWindows
  25770. libraries to Borland's development environment. The LabWindows instrument
  25771. driver library includes over 260 instrument drivers. Version 2.2 includes a
  25772. float data type DSP Analysis Library, new cursor functions, DPMI memory
  25773. manager, and utilties for MS-DOS file and directory commands from within
  25774. LabWindows. LabWindows can control GPIB, VXI, and RS-232 instruments, and
  25775. plug-in data acquisition cards. Contact National Instruments, 6504 Bridge
  25776. Point Parkway, Austin, TX 78730-5039, (512) 794-0100 or (800) 433-3488; FAX:
  25777. (512) 794-8411.
  25778.  
  25779.  
  25780. StatSci Introduces S+INTERFACE Application Building Toolkit
  25781.  
  25782.  
  25783. Statistical Sciences, Inc. (StatSci), has introduced S+INTERFACE, a toolkit
  25784. designed to assist users of the S-PLUS data analysis software on UNIX
  25785. workstations. S+INTERFACE can create custom menu interfaces to S-PLUS, and
  25786. provides access for separate C applications to the over 1000 data analysis
  25787. functions of S-PLUS. Under X11-based systems, S+INTERFACE provides a macro
  25788. language that can be used to create a Motif-style user interface. S+INTERFACE
  25789. provides access to S-PLUS functions from another application as if S-PLUS were
  25790. a subroutine library. S-PLUS is initiated as a separate process and C
  25791. functions calls are used for communication. Contact Statistical Sciences,
  25792. Inc., 1700 Westlake Ave. N, Suite 500, Seattle, WA 98109, (206) 283-8802 or
  25793. (800) 569-0123; FAX: (206) 283-8691; E-mail: mktg@statsci.com.
  25794.  
  25795.  
  25796. RTIS Announces Distributed Application Builder
  25797.  
  25798.  
  25799. The Real-Time Intelligent Systems (RTIS) Corporation has announced their DAB
  25800. Distributed Application Builder Software. DAB provides network-wide data
  25801. exchange between computer programs running on PC compatibles connected by LANs
  25802. and serial links. An introductory DAB Kit includes C libraries compatible with
  25803. Microsoft or Borland compilers, a users manual, and identification keys for
  25804. two computers. DAB supports NetBIOS compatible LANs (e.g., Novel and
  25805. Lantastic). DAB creates a multiprocessing environment, with MS-DOS running in
  25806. the foreground and communications software running in the background. Contact
  25807. The Real-Time Intelligent Systems Corporation, 30 Sever Street, Worcester, MA
  25808. 01609, (508) 752-5567; FAX: (508) 752-5491.
  25809.  
  25810.  
  25811. WCSC Releases COMM-DRV Version 12.0
  25812.  
  25813.  
  25814. WCSC has released COMM-DRV Version 12.0, their serial communication
  25815. development libraries and tools. COMM-DRV v12.0 supports Windows 3.x, MS-DOS,
  25816. and DESQview. COMM-DRV supports dumb multiport cards, the ARNET SMARTPORT PLUS
  25817. cards, and the Digiboard COMXi cards. COMM-DRV can be linked directly into
  25818. MS-DOS or Windows applications, or it can be used as a DLL for Windows.
  25819. COMM-DRV is priced at $189.95. Contact WCSC, 2470 S. Dairy Ashford, Suite 188,
  25820. Houston, TX 77077, (800) 966-4832 or (713) 498-4832; FAX: (713) 568-3334.
  25821.  
  25822.  
  25823. EMS Adds Products to Library of PD/Shareware C Utilities
  25824.  
  25825.  
  25826. EMS Professional Software has added 70 new products to its Library of
  25827. PD/Shareware C Utilities. The library includes 787 products for C/C++
  25828. programmers. A database accompanying the library indexes the products and
  25829. includes descriptions. Searches by type, name, vendor, or free text are
  25830. supported. The library is available on disk or CD-ROM. Contact EMS, 4505
  25831. Buckhurst Court, Olney, MD 20832, (301) 924-3594; FAX: (301) 963-2708.
  25832.  
  25833.  
  25834. Shamus Software Announces Version 3.2 of its C Arithmetic Library
  25835.  
  25836.  
  25837. Shamus Software Ltd. has announced version 3.2 of its MIRACL product, a
  25838. Multi-precision Integer and Rational Arithmetic C library. New features
  25839. include extensions conditionally-compiled in-line assembly. The libraries are
  25840. of use primarily for implementing cryptography systems. Source is included.
  25841. MIRACL is portable and supported platforms include: PC compatibles, Macintosh,
  25842. Acorn, Sun, and VAX. Contact Shamus Software Ltd., 94 Shangan Road, Ballymun,
  25843. Dublin 9, Ireland, Tel: 8425430.
  25844.  
  25845.  
  25846. Liant Cuts Price and Boosts Execution Speed of LPI-C Compiler
  25847.  
  25848.  
  25849. Liant Software has improved the execution speed of its LPI-C compiler, while
  25850. cutting the price. LPI-C is bundled with CodeWatch, Liant's X/Motif
  25851. source-level debugger. Liant reports that its LPI-C compiler v2.0 generates
  25852. code that runs 50 percent faster than previous versions, achieving a 43,000
  25853. rating on the Dhrystone 2.1 benchmark on a 33MHz, i486 system. Liant also
  25854. reduced the suggested list price of LPI-C from $895 to $595. Liant LPI-C v2.0
  25855. is a C compiler for UNIX applications on i386/i486 and Sun SPARC platforms.
  25856. LPI-C is ANSI C compliant, and has passed the FIPS-160 ANSI/ISC C
  25857. Validation-Suite (NIST-certified) and the Plum Hall ANSI C Validation Suite.
  25858. Version 2 incorporates a new optimizer and an improved code generator. The
  25859. optimizer provides global optimization across the entire compilation unit,
  25860. loop unrolling, and function in-lining. Users can select either standard UNIX
  25861. system header files or the LPI-C ANSI runtime library to ensure portability
  25862. across operating systems and architectures for strictly conforming ANSI
  25863. applications. Version 2.0 also compiles pre-ANSI sources including PC-based
  25864. code. LPI-C also includes a windowed debugger, CodeWatch. Contact Liant
  25865. SOftware Corporation, (508) 872-8700.
  25866.  
  25867.  
  25868. ImageSoft Updates CommonView for OS/2
  25869.  
  25870.  
  25871. ImageSoft has introducted the OS/2 2.0 version of CommonView, their
  25872. application framework of C++ classes for developing GUI-based applications.
  25873. CommonView for OS/2 v2.0 supports 32-bit applications. ImageSoft has also
  25874. announced an agreement with J Systems, Inc., to publish Object/Designer, an
  25875. extensible C++, C, and Pascal application generator for Windows. Contact
  25876. ImageSoft Incorporated, 2 Haven Avenue, Port Washington, NY 11050, (516)
  25877. 767-2233; FAX: (516) 767-9067.
  25878.  
  25879.  
  25880. Dyad Software Ships M++ Version 4.0
  25881.  
  25882.  
  25883.  
  25884. Dyad Software has begun shipping verion 4.0 of their M++ math library. The new
  25885. version adds a set of spectral operations, a BitArray class, a PointerArray
  25886. class, huge memory pointers for MS-DOS users, and a set of assembly language
  25887. Basic Linear Algebra routines (BLAs). The spectral methods allow FFTs on
  25888. vectors or arrays of vectors as well as multidimensional FFTs (up to four
  25889. dimensions). M++ is available for MS-DOS C++ compilers (Borland, Microsoft,
  25890. Zortech, and MetaWare) for $495, and for UNIX, WIndows NT, and OS/2 compilers
  25891. for $695. Contact Dyad Software, Bellevue, WA, (206) 637-9426.
  25892.  
  25893.  
  25894. Electronic Imagery Enhances Image Processing Software
  25895.  
  25896.  
  25897. Electronic Imagery (EI) has announced enhancements to its image processing
  25898. software. ImageScale Plus, ImageScale Plus for UNIX, and ImageScale Plus
  25899. Developer's Toolkit for MS-DOS and UNIX applications, now include the JPEG
  25900. compression/decompression algorithm. EI has released ImageManager, a file
  25901. manager for pictorial images, documents, and associated text files in an SQL
  25902. database, MSWindows environmennt. Another new release, ImageCount, supports
  25903. image counting and object recognition that can define, count, number, and
  25904. measure objects in imaging files and video frames. Contact Electronic Imagery,
  25905. Inc., 1100 Park Central Boulevard South, Suite 3400, Pompano Beach, FL 33064,
  25906. (305) 968-7100; FAX: (305) 968-7319.
  25907.  
  25908.  
  25909. Liant Upgrades C-scape User Interface Management System
  25910.  
  25911.  
  25912. Liant has announced a major upgrade (version 4.0) of its C-scape User
  25913. Interface Management System, an object-oriented C development tool for
  25914. creating portable text and GUI applications. Liant describes the most
  25915. significant enhancement to C-scape as its greatly improved look and feel, with
  25916. support for CUA (Common User Access) style boarders for both text and graphics
  25917. mode, scroll bars, minimize/maximize buttons, menus, and other windowing
  25918. functions. Contact Liant Software Corporation, Framingham, MA, (508) 872-8700.
  25919.  
  25920.  
  25921. Data Entry Workshop Supports Interactive Design of Validated Entry Screens
  25922.  
  25923.  
  25924. TurboPower Software has announced Data Entry Workshop, a collection of tools
  25925. for writing validated data entry screens and other Windows controls. Data
  25926. Entry Workshop builds controls in a three-step process: first, use Resource
  25927. Workshop to place and edit the controls interactively; second, run the MAKESRC
  25928. utility to generate the source code (C++ or Pascal); finally, use Borland's
  25929. ObjectWindows Library to access the controls.
  25930. Data Entry Workshop provides the following controls: Simple Entry Field,
  25931. Numeric Entry Field, DEW Shade Control, Toolbox Control, Picture Entry Field,
  25932. Spin Control, Meter Control, and Toolbar Control. Data Entry Workshop is
  25933. designed for use with Borland C++, Borland Turbo Pascal for Windows, or
  25934. Borland Turbo C++ for Windows. Data Entry Workshop includes full source code,
  25935. comprehensive documentation, pop-up help, and example programs. Data Entry
  25936. Workshop costs $189, and no royalty payments are required. Contact TurboPower
  25937. Software, P.O. Box 49009, Colorado Springs, CO 80949-9009, (415) 322-3417.
  25938.  
  25939.  
  25940. SET Laboratories Announces PC-METRIC 4.0 for C
  25941.  
  25942.  
  25943. Set Laboratories, Inc., has announced version 4.0 of its software measurement
  25944. and analysis package, PC-METRIC for C. Additions in version 4.0 include new
  25945. measures of control flow complexity, iEEE standard size counting measures, and
  25946. a completely revamped interactive query and analysis system for tracking
  25947. metrics across releases. Contact SET Laboratories, Inc., P.O. Box 868, Mulino,
  25948. OR 97042, (503) 829-7123; FAX: (503) 829-7220.
  25949.  
  25950.  
  25951. Eighteen Eight Laboratories Introduces Three Interface Cards for PL2500
  25952.  
  25953.  
  25954. Eighteen Eight Laboratories has announced three new interface cards for the
  25955. PL2500 family of AT-hosted Floating Point Array Processors. The new cards use
  25956. the PL2500's SPAN32 bus to transfer data at up to 15 million bytes per second.
  25957. The PL2500 on-board rountines are callable from C (also Fortran and Pascal)
  25958. control programs. Contact Eighteen Eight Laboratories, 1247 Tamarisk Lane,
  25959. Boulder City, NV 89005, (702) 294-5009 or (800) 888-1119; FAX: (702) 294-2611.
  25960.  
  25961.  
  25962. Aggregate Releases GNU make Compatible NetMake
  25963.  
  25964.  
  25965. Aggregate Computing, Inc., has released NetMake 1.2, a distributed, parallel
  25966. version of the UNIX make utility. NetMake can use multiple systems across a
  25967. network of Sun workstations and servers in parallel, to handle Sun, BSD, or
  25968. GNU makefiles. Contact Aggregate Computing, Inc., 300 South Highway 169, Suite
  25969. 400, Minneapolis, MN 55426, (612) 546-5579; FAX: (612) 546-9485.
  25970.  
  25971.  
  25972. SunPro Announces Object Technology Agreement with Rouge Wave Software
  25973.  
  25974.  
  25975. SunProg, the software development business of Sun Microsystems, Inc., has
  25976. entered a technology development and licensing agreement with Rogue Wave
  25977. Software, Inc., a supplier of C++ class library technology. The agreement
  25978. centers on Rogue Wave's Tools.h++ class library, a toolbox of nearly 100 C++
  25979. classes. Contact SunPro, 2550 Garcia Avenue, Mountain View, CA 94043-1100,
  25980. (415) 960-1300; FAX: (415) 969-9131.
  25981.  
  25982.  
  25983. Genus Plans for GIF Toolkit and Printer Toolkit
  25984.  
  25985.  
  25986. Genus Microprogramming has announced plans for a December release of GIF
  25987. Toolkit and GX Printer, a printer toolkit. The GIF Toolkit provides over 100
  25988. routines for incorporating GIF images into applications, and conforms to the
  25989. GIF89a specifications. The GX Printer toolkit supoprt grtaphics printing from
  25990. the Genus PCX or GIF toolkits, the display, or any GX virtual buffer. Contact
  25991. Genus Microprogramming, 1155 Dairy Ashford, Suite 200, Houston, TX 77079,
  25992. (800) 227-0918 or (713) 870-0737.
  25993.  
  25994.  
  25995.  
  25996.  
  25997.  
  25998.  
  25999.  
  26000.  
  26001.  
  26002.  
  26003.  
  26004.  
  26005.  
  26006.  
  26007.  
  26008.  
  26009.  
  26010.  
  26011.  
  26012.  
  26013.  
  26014.  
  26015.  
  26016.  
  26017.  
  26018.  
  26019.  
  26020.  
  26021.  
  26022.  
  26023.  
  26024.  
  26025.  
  26026.  
  26027.  
  26028.  
  26029.  
  26030.  
  26031.  
  26032.  
  26033.  
  26034.  
  26035.  
  26036.  
  26037.  
  26038.  
  26039.  
  26040.  
  26041.  
  26042.  
  26043.  
  26044.  
  26045.  
  26046.  
  26047.  
  26048.  
  26049.  
  26050.  
  26051.  
  26052.  
  26053.  
  26054.  
  26055.  
  26056.  
  26057.  
  26058.  
  26059.  
  26060.  
  26061.  
  26062.  
  26063.  
  26064.  
  26065.  
  26066. We Have Mail
  26067. Mr. Plauger,
  26068. I am a subscriber of The C Users Journal and I have been enjoying it from I
  26069. started my subscription. In particular I want to ask you something. I am
  26070. working in a project that requires sophisticated and unusual macros. Working
  26071. on them I realized that the following code:
  26072. {
  26073. int i=1;
  26074.  
  26075. (1, i)++;
  26076.  
  26077. printf("Value of i is %d\n", i);
  26078. }
  26079. will print:
  26080. Value of i is 2
  26081. This makes sense to me. But I have tried to compiled with four different
  26082. compilers:
  26083. Sun ANSI C Compiler complains saying that the result of 1 is not a LHS value.
  26084. It is certainly not.
  26085. GNU C Compiler compiles and executes correctly.
  26086. Sun C++ Compiler compiles and runs correctly
  26087. Because I am getting two different results with two compiler that are supposed
  26088. to be ANSI compliant, which one is the correct one? Is the construction valid?
  26089. Thank you very much for your help. And sorry to bother you with this kind of
  26090. simple questions.
  26091. Daniel M. German
  26092. dmg@cs.wm.edu
  26093. Believe it or not, all compilers are behaving properly. The C Standard says
  26094. the result of a comma operator is an rvalue, and the ++ operator is defined
  26095. only for modifiable lvalue operands. Applying ++ to an rvalue is thus
  26096. undefined behavior, which leaves implementations free to do as they choose.
  26097. The strictest approach is to issue a diagnostic, as the Sun ANSI compiler
  26098. does. But a common extension is to find some lvalue behind the rvalue and
  26099. apply ++ to the lvalue. That's what the other compilers seem to be doing.
  26100. For what it's worth, I was the one who screamed loudest that the comma
  26101. operator (and a few others) be rvalues. Hope this helps. -- pjp
  26102. Dear Mr. Plauger,
  26103. In the July 1991 issue of CUJ, Jonathan Walker III had a lovely article in
  26104. which he presented an algorithm for positioning a generalized tree. Last
  26105. spring and summer, a student and I wrote a couple of programs utilizing this
  26106. algorithm. These programs take bracketted output from syntactic and
  26107. morphological parsers and display visual representations (trees) in an
  26108. X-window or a large curses pad (and use the vi keys, hjkl, to navigate through
  26109. various parts of the tree). The X-window version has been tested on Sun3s,
  26110. Sun4s, and MIPS machines, and the curses version on Suns, PCs running XENIX,
  26111. and PCs running MS-DOS. There is also a primitive version using Borland's bgi
  26112. for MS-DOS.
  26113. We would like to make these programs available for use by linguists by putting
  26114. them in an ftp site. Last summer I wrote to Mr. Walker at the address given in
  26115. his article to ask for permission to do this (his code makes up about 25% of
  26116. each program). I haven't received a reply from him, and I'm writing to you to
  26117. ask first if CUJ has a more recent address for him), and secondly if, failing
  26118. that, it would be possible for CUJ to give up permission to use the code in
  26119. these small, public-domain applications.
  26120. On another topic, I've been a subscriber of CUJ since I found out about it a
  26121. few years ago, and have enjoyed it immensely as well as learned a great deal
  26122. from it. I particularly enjoy your articles on Standard C. (At my age, 50, I
  26123. give myself the luxury, however of sticking to K&R C (the first edition). I'm
  26124. professionally a linguist, anyway, and not actively involved in training
  26125. programmers. I've noticed that the computer science students I work with all
  26126. code in Standard C.
  26127. With many thanks for your help,
  26128. Chet Creider
  26129. <creider@csd.uwo.ca>
  26130. Diane Thomas, CUJ Managing Editor responds:
  26131. All CUJ code is now posted on USENET. Please check the notice about online
  26132. source code located in the table of contents for details.
  26133. Dear P.J. Plauger,
  26134. My copy of the Journal arrived yesterday, and I was pleased to see the usual
  26135. broad range of articles. I found your column of particular interest, as I have
  26136. recently reviewed some of the coding errors that I make. I hope that the
  26137. Journal will not drift too far from C to C++, as I consider C++ to be a very
  26138. different language, requiring different design considerations and often used
  26139. for very different projects. Perhaps you could consider a little coverage of
  26140. Objective C, which appears to me to be a much more helpful framework for OOP.
  26141. The article "Time Complexity" by Wilbon Davies (page 31) displays a
  26142. misunderstanding of the bubble sort. The code shown is incomplete,
  26143. exaggerating the time taken:
  26144. swap = 1;
  26145. while (swap == 1) {
  26146. swap = 0;
  26147. for (i = 0; i < n - 1; i++) {
  26148. if (x[i] < x[i + 1]) {
  26149. tmp = x[i];
  26150. x[i] = x[i + 1];
  26151. x[i + 1] = tmp;
  26152. swap = 1;
  26153. }
  26154. }
  26155. }
  26156. A practical bubble sort expands this simply, collapsing the scope of the sort
  26157. faster. Where the data is already sorted, only (n - 1) compares are performed,
  26158. partially and randomly sorted data require progressively more work. The worst
  26159. case is for reverse sorted input, where no improvement is made over the
  26160. published form. Keeping the format above:
  26161. last = n;
  26162. while (last > 0) {
  26163. limit = last - 1
  26164. last = 0;
  26165. for (i = 0; i < limit; i++) {
  26166. if (x[i] < x[i + 1]) {
  26167. tmp = x[i];
  26168. x[i] = x[i + 1];
  26169. x[i + 1] = tmp;
  26170. last = i;
  26171. }
  26172.  
  26173. }
  26174. }
  26175. A number of improvements can be made to this algorthim to give acceptable
  26176. execution time, and reduce worst-case behaviour. In particular alternating
  26177. between sorting forwards and backwards, this collapses the scope of the sort
  26178. still faster. It also has the benefit of moving worst-case execution from a
  26179. reverse sorted input, to an esoteric order to require the maximum exchanges.
  26180. In several applications I have had the task of sorting large structures,
  26181. rather than pointers, with the primary constraint on use of memory. I have
  26182. found that an optimised version of the Bubble Sort has given very acceptable
  26183. results.
  26184. This incorrect version often features in articles comparing sort methods. The
  26185. problem seems to be that the authors find it in several refernce books. In
  26186. these books it is introduced briefly, only for a paragraph discussing its
  26187. worst case operation. I would very much appreciate if articles in your Journal
  26188. refering to "fastest bubble time," or similar, actually use the practical
  26189. version, rather than propagate the brain-dead one on this occasion.
  26190. I am happy to discuss this further, and you may edit the letter if you wish to
  26191. publish it in the journal.
  26192. Yours sincerely,
  26193. Anthony Naggs
  26194. Software/Electronics Consultant)
  26195. Email: amn@vms.brighton.ac.uk
  26196. P O Box 1080,
  26197. or xa329@city.ac.uk
  26198. Peacehaven,
  26199. East Sussex
  26200. BN10 8BT
  26201. Phone: +44 273 589701
  26202. Great Britain
  26203. When Kernighan and I wrote The Elements of Programming Style, we discovered
  26204. that "improving" a bubble sort often made it run slower rather than faster.
  26205. Naturally, you can favor certain patterns of input to advantage. But the extra
  26206. baggage you add generally slows average behavior for random input. A "brain
  26207. dead" bubble sort is quite fast enough for sorting small quantities of data.
  26208. For sorting large quantities, you're better off switching to a better
  26209. algorithm than gilding this particular lily. Optimizations can pay off in the
  26210. middle region, if you can determine what that is. -- pjp
  26211. Dear P.J.:
  26212. Is there an e-mail address for the C Users Journal? I didn't receive the
  26213. September '92 issue and, being a hard-up student, want to avoid the cost of an
  26214. international phone call to sort it out. By the way, I would also like to
  26215. commend you on the thoughtfulness and intelligence of your writing, I
  26216. appreciate it very much.
  26217. Thanks,
  26218. Jane Anna Langley
  26219. 105 Osborne Street
  26220. South Yarra
  26221. University of Melbourne
  26222. Victoria 3141
  26223. AUSTRALIA
  26224. (613) 03 820 3629
  26225. s342046@emu.insted.unimelb.edu.au
  26226. Diane Thomas, CUJ Managing Editor, responds:
  26227. All questions regarding subscriptions or any other customer relations topic
  26228. can be sent to cujsub@rdpub.com
  26229. Just to start off, I find the magazine that you publish is the best that I
  26230. have come across. To better clarify this, I appreciated the points you brought
  26231. up in the Editor's Forum about product reviews and how the magazine stands on
  26232. the issue.
  26233. After reading that, I examined the editorial box to the right of the article
  26234. and I believe I have found a mistake. Under trademarks, OS/2 is listed under
  26235. the competitor's company, Microsoft. In the article, "Debugging with
  26236. Assertions" on page 42, there is an error in the code in listing 2:
  26237. void CheckEmpty() {
  26238. assert(StackPtr = 1)
  26239. }
  26240. It should be as follows:
  26241. void CheckEmpty() {
  26242. assert(StackPtr == 1)
  26243. }
  26244. Keep up the good work!
  26245. Sincerely
  26246. Eric V. Blood
  26247. ericb@sierra.com
  26248. Thanks.--pjp
  26249. Dear Mr. Pugh,
  26250. To gain control of the specific area of the screen scrolled when printf does a
  26251. line feed from the last row, I installed my own video interrupt service
  26252. routine (ISR) for int10h. My ISR chains to the original ISR after testing AH
  26253. for function code 6 (scroll up). If (and only if) AH==6, my ISR replaces the
  26254. value in CH with a row number passed from the calling program before chaining
  26255. to the original ISR. This gives the calling program a method of preventing
  26256. data above a given row from scrolling off the screen, as long as scrolling is
  26257. done by int10h calls. (Some compilers implement cprintf, cputs, and putch of
  26258. <conio.h> so they can scroll up without using int10h.)
  26259. My test program reports the vector of the original video ISR and the vector of
  26260. the my replacement ISR. A test version of my ISR displays a signature on row
  26261. 14 of the screen whenever AH==6. The signature consists of the char
  26262. equivalents of CH (0), CL (0), DH (24), DL (79), AH (6), AL (1), and the value
  26263. to be put into CH (from the calling program. CH,CL is used by the original ISR
  26264. as the upper-left row,col of the window to be scrolled, while DH,DL is used as
  26265. its lower-right row,col. AL is the number of lines to be scrolled.
  26266. On a PC Designs XT with Hercules video under DOS 3.1, the test program and my
  26267. ISR work as expected. On an AST Premium with VGA under DOS 3.3, my ISR is not
  26268. called at all, as indicated by the absence of the diagnostic signature, even
  26269. though the vectors reported show that my ISR was properly installed. The same
  26270. failure was noted on a Packard-Bell with EGA under DOS 3.3.
  26271. Suspecting that EGA/VGA video subsystems bypass int10h for all scrolling
  26272. functions, I studied PC & PS/2 Video Systems (Richard Wilton, Microsoft Press,
  26273. 1987), Programmer's Guide to the IBM PC (Peter Norton, Microsoft Press, 1985),
  26274. and back issues of C Users Journal and Dr. Dobb's Journal. I found nothing
  26275. that either confirmed or dispelled that notion.
  26276. Can you offer any suggestions that may lead to an answer to this puzzle?
  26277. Thank you,
  26278. Sid Sanders
  26279. 5 Seneca Avenue
  26280. Geneseo, NY 14454-9508
  26281. Sure looks like a bypass to me, but I'm hardly an expert in this area. Anybody
  26282. out there got any ideas? I suggest you contact Mr. Sanders directly for speed.
  26283. Send us a copy of your letter if you think the lore is worth sharing. -- pjp
  26284. Re your editorial in the October 1992 C Users Journal:
  26285. I bought a copy of the Shamus Software library MIRACL to fiddle with p. It has
  26286. many mathematics routines and some top coding and decoding stuff. So I ported
  26287. it to Coherent 310 and now to Coherent 401 which is the 32-bit system. I could
  26288. compute p to 500 places in the 16-bit system and it does much more in the
  26289. 32-bit version. I can get 50,000 places, but it takes 57 hours! It yields a 12
  26290. page printout and I am not sure of its accuracy at this time. I would like a
  26291. copy of the "Spigot" algorithm originally due to Stanley Rabinowitz which
  26292. really works. I got one in an article by Peter Morrison on comp.lang.c which
  26293. produces rubbish only.
  26294. Also the Coherent 401 produces LOTTO programs with one million games in them.
  26295. Of course it takes up 39 Mbytes but I use grep to see how many winning games
  26296. are in the file.
  26297. I tried porting the MIX Multi-C library to Coherent but it is no good. There
  26298. may be a bug in the way coherent uses the typedef enum. It has a definition of
  26299. ECODE in the file mtc_defs.h. This reads typedef enum { 13 names of errors.. }
  26300. ECODE. Then it has the line:
  26301.  
  26302. typedef ECODE (*TRAVERSE_FUNC) (void*,
  26303. void*, int, int, void*);
  26304. This is not acceptable to the compiler, which produces this message:
  26305. 58: mtc_defs.h: missing ')'
  26306. 58: mtc_defs.h: missing semicolon
  26307. 58: mtc_defs.h: declarator syntax
  26308. 58: mtc_defs.h: external syntax
  26309. Have you got any ideas on what I can do with this?
  26310. Jonathan Kitchin
  26311. Perth, Western Australia
  26312. jon@dialix.oz.au
  26313. Once a compiler produces a diagnostic, it often gets confused for awhile. Try
  26314. omitting the earlier statement (or fixing it if you know how). There's a good
  26315. chance the subsequent diagnostics will evaporate. -- pjp
  26316. Dear Sir,
  26317. Some time ago, I purchased your book, "The Standard C Library," together with
  26318. the code disk. I just thought I would pass on that it has been one of the
  26319. better investments I have made in computer science books. I find it very
  26320. instructive and a good source of ideas and algorithms. (I recently had to
  26321. implement malloc on an embedded system with no operating system! Studying how
  26322. you did it gave me a good kick start.)
  26323. I read your "Standard C" column on bugs (CUJ September 1992) with great
  26324. interest. I wish there were more articles about people's bugs and problems. I
  26325. think we could learn more from them than from some of the "how to do it"
  26326. articles. No doubt you have received other letters on this, but there seems to
  26327. be a bug in your bug fix. [Bug report omitted. Same as earlier reports -- pjp]
  26328. I hope that this bug did not creep into the v1.1 code disk.
  26329. Speaking of the v1.1 code disk. You mentioned in the column that it now
  26330. available, but I couldn't see anything in CUJ about how to order it. Do you
  26331. need some "proof of ownership" for the upgrade? I can't see a serial number on
  26332. the disks. Or do you rely on your purchase records? [MasterCard number
  26333. omitted, for obvious reasons. -- pjp]
  26334. Yours faithfully,
  26335. Ian Cargill
  26336. 54 Windfield
  26337. Leatherhead
  26338. Surrey
  26339. KT22 8UQ
  26340.  
  26341.  
  26342.  
  26343.  
  26344.  
  26345.  
  26346.  
  26347.  
  26348.  
  26349.  
  26350.  
  26351.  
  26352.  
  26353.  
  26354.  
  26355.  
  26356.  
  26357.  
  26358.  
  26359.  
  26360.  
  26361.  
  26362.  
  26363.  
  26364.  
  26365.  
  26366.  
  26367.  
  26368.  
  26369.  
  26370.  
  26371.  
  26372.  
  26373.  
  26374.  
  26375.  
  26376.  
  26377.  
  26378.  
  26379.  
  26380.  
  26381. A Portable User Interface Using curses
  26382.  
  26383.  
  26384. Matt Weisfeld
  26385.  
  26386.  
  26387. Matt Weisfeld is currently employed by the Allen-Bradley Company in Highland
  26388. Heights, Ohio. He responsible for the design and development of test software
  26389. on VAX/VMS, UNIX, DOS, and other platforms. Matt is currently working on a
  26390. book entitled Building and Testing Portable Libraries in C, to be published by
  26391. QED in 1993. He can be reached on Compuserve at [71620,2171].
  26392.  
  26393.  
  26394. The topics of portability and user interfaces are usually not mentioned in the
  26395. same conversation, because the hardware-dependent nature of terminal devices
  26396. makes user interfaces notoriously difficult to port. However, you don't
  26397. necessarily need to write a separate user interface for each platform, even
  26398. when you need maximum portability. This article presents a library of routines
  26399. that allows a programmer to create a portable, text-based user interface using
  26400. curses.
  26401. curses, the screen-handling package available as part of the C package on VMS
  26402. and most flavors of UNIX or as shareware, allows you to update screens
  26403. efficiently. curscr keeps an image of the current screen. You change this
  26404. image by changing the standard screen, stdscr, or by creating a new screen.
  26405. refresh or wrefresh change curscr to match stdscr.
  26406.  
  26407.  
  26408. The User Interface
  26409.  
  26410.  
  26411. The example user interface discussed here can be ported to VAX/VMS,
  26412. MS-DOS/BORLAND C, Hewlett-Packard/HPUX, and SUN/GNU with revision. The display
  26413. consists of a main window, a menubar, and a dialog box (see Figure 1).
  26414. The main window occupies the entire screen and is the backdrop for all other
  26415. constructs. At the top of the main window, a single-line menubar presents the
  26416. user with the available program options. The user chooses an option either by
  26417. entering the first letter of the option or by using the arrow and return keys.
  26418. Choosing one of these options will activate a pulldown menu containing further
  26419. options. The dialog box, used to print informational messages and accept
  26420. additional user input, resides at the bottom of the main window.
  26421. Library functions to handle these user interface constructs, and other
  26422. specific tasks, simplify the process of building screen applications. The
  26423. separate library files can be linked into specific user applications.
  26424. There are three major reasons for using curses: portability, availability, and
  26425. usability. Even if curses is inappropriate for a specific application, you can
  26426. apply the methods presented here for creating a menubar and pulldown menus to
  26427. any user interface. The libraries can be treated as shells, with the curses
  26428. commands replaced by other user interface commands. The appropriate #ifdefs
  26429. make the libraries portable to multiple platforms.
  26430.  
  26431.  
  26432. Primary curses Screen Structures
  26433.  
  26434.  
  26435. Listing 1 contains a simple curses application. Just as C defines stdout,
  26436. stdin, and stderr for input and output, curses keeps a memory representation
  26437. of the screen in stdscr. All window operations affect only this memory
  26438. representation. To change the screen itself, kept in curscr, you execute
  26439. refresh or wrefresh--even when deleting a window.
  26440. WINDOW, a data structure in curses.h required by all curses applications,
  26441. contains information such as window location and size. Each window created
  26442. must correspond to a pointer of this structure type. All curses programs
  26443. create stdscr by default. Other curses constructs must be explicitly created.
  26444. In most cases, curses treats stdscr differently from other windows. For
  26445. example, to clear a window, curses performs the wclear(win) command, but to
  26446. clear stdscr, curses uses the clear command, with no parameters.
  26447.  
  26448.  
  26449. Creating a Popup Window
  26450.  
  26451.  
  26452. Since most operations for any user interface involve windows, I built a
  26453. library function called popup to create a popup window. (Listing 3 contains
  26454. popup and all other library code presented in this article.) To create a
  26455. window that entirely covers stdscr, I call popup with MAX_ROWS and
  26456. MAX_COLUMNS.
  26457. WINDOW *mainwin;
  26458.  
  26459. mainwin = popup (MAX_ROWS,
  26460. MAX_COLUMNS, 0,0);
  26461. The constants MAX_ROWS and MAX_COLUMNS represent the standard screen size. The
  26462. header file menu.h (Listing 2) defines all the constants and structures for
  26463. this user interface.
  26464. There are three popup windows in this application: the menubar, the dialog
  26465. box, and a window used for pulldown menus. These are global to all functions
  26466. and thus are declared as extern in most of the files.
  26467. Color presents a special problem when writing a portable routine for creating
  26468. a popup window. Since PC curses has color capabilities, whereas VMS and UNIX
  26469. do not, ifdefs are used to take advantage of this feature. The PC curses
  26470. command wattrset controls color. The colors representing the foreground and
  26471. background are ORed together with:
  26472. wattrset(mainwin, F_RED B_BLACK);
  26473. PC curs es also has many more box characters to choose from. Many different
  26474. effects can be obtained by using colors and box characters on the PC. However,
  26475. if you desire a simple border around a window similar to VMS and UNIX, simply
  26476. set the background to black and execute the box command on all platforms.
  26477.  
  26478.  
  26479. Special Keyboard Input
  26480.  
  26481.  
  26482.  
  26483.  
  26484. Accepting Single Keystrokes
  26485.  
  26486.  
  26487. The example application uses the arrow keys. This causes a portability problem
  26488. when creating a windowed curses application. wgetch gets characters from a
  26489. window. However, curses buffers this input and echoes it to the screen. To
  26490. prevent line buffering and echo, making the program accept one keystroke at a
  26491. time, you must call crmode to set the cbreak mode and noecho to unset the echo
  26492. mode.
  26493. Using the arrow keys (from the keypad) on MS-DOS is very straightforward.
  26494. Either the getch or the wgetch commands will return the necessary key code.
  26495. (For all codes see Listing 2.)
  26496. Obtaining non-printable characters with VMS requires the use of low-level
  26497. Screen Management (SMG) commands. (See the VMS SMG manual for a complete
  26498. description.) The two SMG commands needed here are CREATE_VIRTUAL_KEYBOARD,
  26499. which activates the program for keyboard input, and READ_KEYSTROKE, which
  26500. returns a keystroke. (VMS returns a short.)
  26501.  
  26502.  
  26503.  
  26504. Interpreting Escape Sequences
  26505.  
  26506.  
  26507. On UNIX systems, use of the arrow keys, escape key, and return key presents
  26508. another problem. Both the HP and SUN systems return keystrokes from the keypad
  26509. with escape sequences. Some systems, such as the HP, include a function called
  26510. keypad. This function activates the keypad and returns the proper key code
  26511. directly, saving the programmer from having to interpret the escape sequences.
  26512. When the keypad function is not available, the method for dealing with the
  26513. escape sequences depends on the system. For example, on the SUN system,
  26514. entering a keypad character will return an escape sequence in three parts.
  26515. When the first getch is recognized as an escape, two more getch commands must
  26516. be called in succession. The third character contains the code needed.
  26517. The character codes used in these libraries are defined in the file menu.h as:
  26518. UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, ESCAPE, and RETURN.
  26519.  
  26520.  
  26521. Creating a Menubar
  26522.  
  26523.  
  26524. In all environments, the same structure defines a menubar:
  26525. typedef struct mbar {
  26526. char string[80];
  26527. char letter;
  26528. int pos;
  26529. } MENUBAR;
  26530. The first field holds the actual string that represents the particular option.
  26531. For example, if one of the options across the top relates to printing, then
  26532. the string print is a logical choice. The second field is the letter that
  26533. invokes the option from the keyboard. The final field, called pos, holds the
  26534. location of the string within the menubar.
  26535. This example uses the code
  26536. #define TCHOICES 3
  26537.  
  26538. MENUBAR menubar[TCHOICES] = {
  26539. "file", 'f', O,
  26540. "edit", 'e', O,
  26541. "options", 'o', O,
  26542. };
  26543. to create a menubar (see Listing 4). This declaration creates a menubar with
  26544. three different options, file, edit, and options, invoked by entering an f, e,
  26545. or o respectively. The positions are initially set to zero, and calculated,
  26546. when needed, by the menubar routine. All the libraries use the constant
  26547. TCHOICES to identify the number of options available. To add or delete
  26548. options, adjust TCHOICES and add or delete the appropriate number of lines in
  26549. the menubar structure.
  26550. The routine topbar generates the menubar window (the window only, not its
  26551. contents). In most cases, windows are created as separate entities. But since
  26552. the menubar is a permanent part of stdscr, I made the menubar a subwindow of
  26553. stdscr to improve efficiency. This technique allows you to refresh just
  26554. stdscr, instead of refreshing both stdscr and the menubar. The code
  26555. WINDOW *swin;
  26556.  
  26557. if ((swin = subwin(win,3,(win->MAXX)-4,(win->BEGY)+1,
  26558. (win->BEGX)+2)) == NULL)
  26559. clean_up ();
  26560. creates the menubar. The parameter list for the subwin command includes the
  26561. window pointer of the parent. curses uses this pointer to calculate where to
  26562. position the menubar (see curses.h). Beware not to use a pointer until you
  26563. create the actual window. Until a newwin or subwin command is invoked, the
  26564. pointer is null and passing a null pointer to a function may cause unexpected
  26565. results.
  26566. Even though the curses implementations for all the platforms is highly
  26567. consistent, the variable names vary across different environments. (To
  26568. position the menubar, you need the WINDOW structure coordinates of the parent
  26569. window.) VMS uses _beg_x and beg_y while MS-DOS and UNIX use beg_x and beg_y.
  26570. To make the code more portable, my code uses macros, such as BEGX.
  26571.  
  26572.  
  26573. Displaying a Menu
  26574.  
  26575.  
  26576. The function do_menubar performs all the tasks associated with selecting an
  26577. option from a menubar. First, do_menubar prints the string in the window. The
  26578. sample menubar includes three strings: file, edit, and options. You should
  26579. space these three strings appropriately so that they fill the top of the
  26580. screen evenly. This application uses a function called strmenu for spacing the
  26581. strings. strmenu takes the menubar structure and the width of the parent
  26582. window as parameters.
  26583. strmenu proceeds in three stages. First, strmenu calculates the number of
  26584. spaces allocated to each string by dividing the width of the parent window by
  26585. the number of strings in the menubar (in this case three). Next, strmenu
  26586. enters a loop that builds the menubar by copying each string and padding it
  26587. with the proper number of spaces, highlighting the current choice (initially
  26588. the one on the left) in upper case. Finally, strmenu returns the string
  26589. pointer to the calling function, and prints the menubar using the mvwaddstr
  26590. function.
  26591.  
  26592.  
  26593. Detecting a Selection
  26594.  
  26595.  
  26596. Once the menubar is in place, the user must be able to select one of the
  26597. options. The user will type either the first character of the option or the
  26598. return key to activate the highlighted option. By typing the left and right
  26599. arrow keys, the user can highlight different options. After receiving a
  26600. keystroke, a switch statement controls the action. If the program encounters
  26601. an arrow, it moves the highlight either to the left or right (with allowances
  26602. for wrap-around). An escape terminates the program, while a return breaks out
  26603. of the loop and invokes the option currently highlighted. By default the
  26604. program sends all other sequences back to the calling program as a character
  26605. code.
  26606.  
  26607.  
  26608. Creating a Pulldown Menu
  26609.  
  26610.  
  26611. A pulldown menu is basically a popup window, except the pulldown routine must
  26612. be able to adjust for windows of different sizes. To define the choices
  26613. contained in each pulldown menu, I created the structure CHOICES:
  26614. typedef struct choices {
  26615. char string[20];
  26616. char letter;
  26617. int (*funcptr)();
  26618. } CHOICES;
  26619.  
  26620. As with the menubar structure, CHOICES contains the string that represents the
  26621. option and the letter that invokes it. The third field, a function pointer,
  26622. represents the function that will be executed when the option is chosen. The
  26623. prototypes for these functions are in Listing 5.
  26624. For example, suppose that invoking the menubar option file produces a pulldown
  26625. menu with the options open, close, and exit. The application initializes the
  26626. structure
  26627. CHOICES choices1[3] = {
  26628. "open ", 'o', c_open,
  26629. "close", 'c', c_close,
  26630. "exit ", 'e', c_exit,
  26631. };
  26632. Thus, if the user chooses open, the application calls function c_open. (I used
  26633. the c_ prefix to avoid possible function name conflicts.)
  26634. In this example application, there are three options (see Listing 4). They are
  26635. all tied together with
  26636. typedef struct pmenu {
  26637. int num;
  26638. int maxlength;
  26639. CHOICES *ptr;
  26640. } PULLDOWN;
  26641. The structure PULLDOWN contains three pieces of information. The first field
  26642. represents the number of options in the pulldown. The second indicates the
  26643. maximum string length. The option close has five letters, and thus 5 is the
  26644. maximum length. When creating the pulldown, the menu should have equal
  26645. proportions. Thus, the shorter strings are padded with spaces to match that of
  26646. the longest. The third field is the pointer to the structure that hold the
  26647. choice information for this particular menu. The initialization of the entire
  26648. PULLDOWN structure is
  26649. PULLDOWN pullmenu[3] = {
  26650. 3, 5, choices1,
  26651. 4, 6, choices2,
  26652. 3, 7, choices3,
  26653. };
  26654. To create a pulldown, the application calls the function do_pulldown. Choosing
  26655. an option from the pulldown menu works like the menubar, except that
  26656. do_pulldown uses UP_ARROW and DOWN_ARROW.
  26657. Unlike the menubar, the pulldown menu must be erased after an option has been
  26658. chosen, and whatever was underneath must be restored. The command touchwin
  26659. performs this task. For this example, the pulldown window blocks both the
  26660. menubar and stdscr, so both must be restored.
  26661.  
  26662.  
  26663. Tying It All Together
  26664.  
  26665.  
  26666. The program in Listing 4 demonstrates the advantages of building these
  26667. libraries. The actual application requires only a dozen or so lines of code.
  26668. The program consists of two basic parts: building the screen and creating the
  26669. menus.
  26670. The functions that the menu choices invoke are simply shells. (The programmer
  26671. would substitute the appropriate functionality.) The only functions that
  26672. perform any tasks are the version function, which displays the current program
  26673. version, and the exit function, which terminates the application (Listing 6).
  26674. To run the application, compile menu.c, uilibs.c, and funcs.c with the
  26675. appropriate defines for the host platform. Then link them together with the
  26676. curses library provided by the package. When the user starts the program, the
  26677. menu screen will appear.
  26678.  
  26679.  
  26680. Conclusion
  26681.  
  26682.  
  26683. The curses interface has its limitations. A commercial package written in
  26684. curses will most likely not show up on the store shelves. However, if you need
  26685. a reasonably efficient and portable method of creating user interfaces, curses
  26686. is an option.
  26687. Figure 1 Sample menu screen using curses
  26688.  
  26689. Listing 1 sample.c -- sample curses application
  26690. #include <stdio.h>
  26691. #include <curses.h>
  26692.  
  26693. /* define window pointer */
  26694. WINDOW *win;
  26695.  
  26696. main()
  26697. {
  26698.  
  26699. /* must be called before any curses command */
  26700. initscr();
  26701.  
  26702. /* get space for window at coordinates
  26703. /* (y,x,begy,begx) */
  26704. /* y is # of rows, x is # of columns */
  26705. /* begy & begx are the positions on the screen */
  26706. win = newwin(24,80,0,0);
  26707.  
  26708. /* surround the window with characters provided */
  26709. box (win, '', '-');
  26710.  
  26711.  
  26712. /* place the string in win at position y,x */
  26713. mvwaddstr(win,2,2,
  26714. "enter a character to erase window");
  26715.  
  26716. /* this must be done to view changes on screen */
  26717. /* otherwise changes are made only in memory */
  26718. wrefresh(win);
  26719.  
  26720. /* get a single character from the keyboard */
  26721. wgetch(win);
  26722.  
  26723. /* erase the window in memory */
  26724. werase(win);
  26725.  
  26726. /* necessary to complete the erase on screen */
  26727. wrefresh(win);
  26728.  
  26729. /* remove the window from memory */
  26730. delwin(win);
  26731.  
  26732. /* must be called to end a curses program */
  26733. endwin();
  26734.  
  26735. }
  26736.  
  26737. /* End of File */
  26738.  
  26739.  
  26740. Listing 2 menu.h
  26741. /* screen dimensions*/
  26742. #define MAX_ROWS 24
  26743. #define MAX_COLUMNS 80
  26744.  
  26745. /* keystroke codes and color */
  26746. #ifdef HPUX
  26747. #define UP_ARROW 3
  26748. #define DOWN_ARROW 2
  26749. #define LEFT_ARROW 4
  26750. #define RIGHT_ARROW 5
  26751. #define RETURN 10
  26752. #define ESCAPE 27
  26753. #endif
  26754. #ifdef SUN
  26755. #define UP_ARROW 65
  26756. #define DOWN_ARROW 66
  26757. #define LEFT_ARROW 68
  26758. #define RIGHT_ARROW 67
  26759. #define RETURN 10
  26760. #define ESCAPE 27
  26761. #endif
  26762. #ifdef VMS
  26763. #define UP_ARROW 274
  26764. #define DOWN_ARROW 275
  26765. #define LEFT_ARROW 276
  26766. #define RIGHT_ARROW 277
  26767. #define RETURN 13
  26768. #define ESCAPE 291 /* F11 */
  26769. #endif
  26770.  
  26771. #ifdef BCC
  26772. #define MAINCOLOR (F_RED B_BLACK)
  26773. #define DIALOGCOLOR (F_CYAN B_BLACK)
  26774. #define UP_ARROW 56
  26775. #define DOWN_ARROW 50
  26776. #define LEFT_ARROW 52
  26777. #define RIGHT_ARROW 54
  26778. #define RETURN 10
  26779. #define ESCAPE 27
  26780. #endif
  26781.  
  26782. /* macros for portability */
  26783. #ifndef VMS
  26784. #define BEGX _begx
  26785. #define BEGY _begy
  26786. #define MAXX _maxx
  26787. #else
  26788. #define BEGX _beg_x
  26789. #define BEGY _beg_y
  26790. #define MAXX _max_x
  26791. #endif
  26792.  
  26793. /* box characters */
  26794.  
  26795. #ifdef BCC
  26796. #define SINGLE_SIDE -77 /* single bar */
  26797. #define SINGLE_ACROSS -60
  26798. #define DOUBLE_SIDE -70 /* double bar */
  26799. #define DOUBLE_ACROSS -51
  26800. #else
  26801. #define SINGLE_SIDE ''
  26802. #define SINGLE_ACROSS '-'
  26803. #define DOUBLE_SIDE '"'
  26804. #define DOUBLE_ACROSS '='
  26805. #endif
  26806.  
  26807. #define TCHOICES 3
  26808.  
  26809. /* menubar structure */
  26810. typedef struct mbar {
  26811. char string[80];
  26812. char letter;
  26813. int pos;
  26814. }MENUBAR;
  26815.  
  26816. /* pulldown menu choices */
  26817. typedef struct choices {
  26818. char string[20];
  26819. char letter;
  26820. int (*funcptr)();
  26821. } CHOICES;
  26822.  
  26823. /* pulldown menu structure */
  26824. typedef struct pmenu {
  26825. int num;
  26826. int maxlength;
  26827. CHOICES *ptr;
  26828. } PULLDOWN;
  26829.  
  26830.  
  26831. /* prototypes */
  26832. WINDOW *topbar(WINDOW *);
  26833. WINDOW *pulldown(int,int,int,int);
  26834. WINDOW *popup(int,int,int,int);
  26835. void move_window(WINDOW *win,int y, int x);
  26836. void print_string(WINDOW *,int, int, char *);
  26837. void erase_window(WINDOW *);
  26838. void delete_window(WINDOW *);
  26839. void refresh_window(WINDOW *);
  26840. int to_dialogue(char *);
  26841. void clear_dialogue(void);
  26842. void touch_window(WINDOW *);
  26843. char *strmenu(int, MENUBAR *, int);
  26844. char menu_choice(char *);
  26845. int clean_up(void);
  26846. void repaint(void);
  26847. char do_pulldown(int,PULLDOWN *, MENUBAR *);
  26848. void set_stdscr(void);
  26849. void execute_command(int, int, PULLDOWN *);
  26850. char do_menubar(WINDOW *, MENUBAR *);
  26851. void strtoupper(char *);
  26852. void strtolower(char *);
  26853. void set_stdscr(void);
  26854. char get_keystroke(void);
  26855. /* End of File */
  26856.  
  26857.  
  26858. Listing 3 uilibs.c -- library code
  26859. #include <stdio.h>
  26860. #include <stdlib.h>
  26861. #include <signal.h>
  26862. #include <time.h>
  26863. #include <string.h>
  26864. #ifdef VMS
  26865. #include <smgdef.h>
  26866. #endif
  26867. #ifdef BCC
  26868. #include <dos.h>
  26869. #include <conio.h>
  26870. #include <ctype.h>
  26871. #endif
  26872.  
  26873. #include "curses.h"
  26874. #include "menu.h"
  26875.  
  26876. extern WINDOW *dialogue;
  26877. extern WINDOW *tbar;
  26878.  
  26879. static bar_size; /* size of menubar */
  26880. static int.menu_pos; /* position in menubar */
  26881.  
  26882. /* display menubar */
  26883. WINDOW *topbar(WINDOW *win)
  26884. {
  26885.  
  26886. WINDOW *swin;
  26887.  
  26888. int string_count, string_size;
  26889. if((swin = subwin(win,3,(win->MAXX)-4,(win->BEGY)+1,
  26890.  
  26891. (win->BEGX)+2)) == NULL)
  26892. clean_up();
  26893.  
  26894. #ifdef BCC
  26895. wattrset(swin, F_BLUE B_GRAY);
  26896. #endif
  26897.  
  26898. box (swin, SINGLE_SIDE,SINGLE_ACROSS);
  26899.  
  26900. bar_size = (swin->MAXX)-2;
  26901. menu_pos=0;
  26902.  
  26903. return (swin);
  26904. }
  26905.  
  26906. /* print string to menubar */
  26907. char do_menubar(WINDOW *swin, MENUBAR *menubar)
  26908. {
  26909.  
  26910. char * menu;
  26911.  
  26912. char buffer[80];
  26913.  
  26914. int status;
  26915.  
  26916. #ifdef VMS
  26917. int keyboard;
  26918. short term_code;
  26919. #else
  26920. char term_code;
  26921.  
  26922. #endif
  26923.  
  26924. #ifdef VMS
  26925. if ( (( status =
  26926. SMG$CREATE_VIRTUAL_KEYBOARD(&keyboard))&1)!=1)
  26927. clean_up();
  26928. #endif
  26929.  
  26930. term_code = 0;
  26931.  
  26932. while (term_code != RETURN) {
  26933.  
  26934. /* get the new menubar string */
  26935. menu = strmenu(bar_size, menubar, menu_pos);
  26936.  
  26937. mvwaddstr(swin, 1, 1, menu);
  26938. wrefresh(swin);
  26939.  
  26940. /* get a single keystroke */
  26941.  
  26942. #ifdef VMS
  26943. if ( (( status = SMG$READ_KEYSTROKE
  26944. (&keyboard,&term_code))&l)!=l)
  26945. clean_up();
  26946. #endif
  26947. #ifdef BCC
  26948. term_code = wgetch(swin);
  26949. #endif
  26950.  
  26951. #ifdef HPUX
  26952. term_code = getch();
  26953. #endif
  26954. #ifdef SUN
  26955. term_code = getch();
  26956. if (term_code == ESCAPE) {
  26957. getch();
  26958. term_code = getch();
  26959. }
  26960. #endif
  26961.  
  26962. /* process keystroke */
  26963. switch (term_code) {
  26964.  
  26965. /* arrows check for wrap-around */
  26966. case LEFT_ARROW:
  26967. if (menu_pos == 0)
  26968. menu_pos = TCHOICES-1;
  26969. else
  26970. menu_pos--;
  26971. break;
  26972.  
  26973. case RIGHT_ARROW:
  26974. if (menu_pos == TCHOICES-1)
  26975. menu_pos = 0;
  26976. else
  26977. menu_pos++;
  26978. break;
  26979.  
  26980. /* do nothing */
  26981. case RETURN:
  26982. break;
  26983.  
  26984. /* exit program */
  26985. case ESCAPE:
  26986. clean_up();
  26987. break;
  26988.  
  26989. /* return keyboard input */
  26990. default :
  26991. return (term_code);
  26992. break;
  26993.  
  26994. }
  26995.  
  26996. }
  26997.  
  26998. /* return highlighted option */
  26999. return (menubar[menu_pos].letter);
  27000.  
  27001. }
  27002.  
  27003. WINDOW *popup(int rows,int columns,int sy,int sx)
  27004. {
  27005. WINDOW *win;
  27006.  
  27007. win = newwin(rows, columns, sy, sx);
  27008.  
  27009. if(win == NULL) {
  27010.  
  27011. endwin();
  27012. clean_up();
  27013. }
  27014.  
  27015. #ifdef BCC
  27016. wattrset(win, F_BLACK B_GRAY);
  27017. #endif
  27018. box(win, SINGLE_SIDE, SINGLE_ACROSS);
  27019. wrefresh(win);
  27020.  
  27021. return (win);
  27022.  
  27023. }
  27024.  
  27025. /* erase windows and surrounding box */
  27026. void erase_window(WINDOW *win)
  27027. {
  27028.  
  27029. werase(win);
  27030. box(win, ' ', ' ');
  27031. wrefresh(win);
  27032.  
  27033. }
  27034.  
  27035. void delete_window(WINDOW *win)
  27036. {
  27037.  
  27038. delwin(win);
  27039.  
  27040. }
  27041.  
  27042. void refresh_window(WINDOW *win)
  27043. {
  27044.  
  27045. wrefresh(win);
  27046.  
  27047. }
  27048.  
  27049. void touch_window(WINDOW *win)
  27050. {
  27051.  
  27052. touchwin(win);
  27053. wrefresh(win);
  27054.  
  27055. }
  27056.  
  27057. /* process pulldown menu options */
  27058. char do_pulldown
  27059. (int i, PULLDOWN *pullmenu, MENUBAR *menubar)
  27060.  
  27061. {
  27062.  
  27063. WINDOW *subwin1;
  27064.  
  27065. int j;
  27066. int position, oldpos;
  27067.  
  27068. char *ptr;
  27069.  
  27070.  
  27071. int status;
  27072.  
  27073. #ifdef VMS
  27074. int keyboard;
  27075. short term_code;
  27076. #else
  27077. char term_code;
  27078. #endif
  27079.  
  27080. #ifdef VMS
  27081. if ( (( status =
  27082. SMG$CREATE_VIRTUAL_KEYBOARD(&keyboard))&1)!=1)
  27083. clean_up();
  27084. #endif
  27085.  
  27086. subwin1 = popup( (pullmenu[i].num)+2,
  27087. (pullmenu[i].maxlength)+2,stdscr->BEGY+3,
  27088. (menubar[i].pos)+2);
  27089.  
  27090. /* print pulldown options */
  27091.  
  27092. for (j=0;j<pullmenu[i].num;j++) {
  27093.  
  27094. ptr = pullmenu[i].ptr[j].string;
  27095.  
  27096. mvwaddstr(subwin1, j+1, 1, ptr );
  27097.  
  27098. }
  27099.  
  27100. term_code = 0;
  27101.  
  27102. position=0;
  27103. oldpos = 0;
  27104.  
  27105. while (term_code != RETURN) {
  27106.  
  27107. /* highlight selected option */
  27108.  
  27109. ptr = pullmenu[i].ptr[position].string;
  27110.  
  27111. strtoupper(ptr);
  27112.  
  27113. mvwaddstr(subwin1, position+1, 1, ptr );
  27114.  
  27115. wrefresh(subwin1);
  27116.  
  27117. /* get keystroke */
  27118.  
  27119. #ifdef VMS
  27120. if ( (( status =SMG$READ_KEYSTROKE
  27121. (&keyboard,&term_code)) & 1)!=1)
  27122. clean_up();
  27123. #endif
  27124. #ifdef BCC
  27125. term_code = wgetch(subwin1);
  27126. #endif
  27127. #ifdef HPUX
  27128. term_code = getch();
  27129. #endif
  27130.  
  27131. #ifdef SUN
  27132. term_code = getch();
  27133. if (term_code == ESCAPE) {
  27134. getch();
  27135. term_code = getch();
  27136. }
  27137. #endif
  27138.  
  27139. oldpos = position;
  27140.  
  27141. /* process keystroke */
  27142.  
  27143. switch (term_code) {
  27144.  
  27145. case UP_ARROW:
  27146.  
  27147. if (position == 0)
  27148. position = pullmenu[i].num-1;
  27149. else
  27150. position--;
  27151.  
  27152. break;
  27153.  
  27154. case DOWN_ARROW:
  27155.  
  27156. if (position == pullmenu[i].num-1)
  27157. position = 0;
  27158. else
  27159. position++;
  27160.  
  27161. break;
  27162.  
  27163. /* do nothing */
  27164. case RETURN:
  27165. break;
  27166.  
  27167. /* get keyboard input and
  27168. erase menu */
  27169. default :
  27170. erase_window(subwin1);
  27171. delwin(subwin1);
  27172.  
  27173. touchwin(stdscr);
  27174. wrefresh(stdscr);
  27175. touchwin(dialogue);
  27176. wrefresh(dialogue);
  27177.  
  27178. return (term_code);
  27179. break;
  27180.  
  27181. }
  27182.  
  27183. /* restore to lowercase */
  27184.  
  27185. ptr = pullmenu[i].ptr[oldpos].string;
  27186.  
  27187. strtolower(ptr);
  27188. mvwaddstr(subwin1, oldpos+1, 1, ptr );
  27189.  
  27190.  
  27191. wrefresh(subwin1);
  27192.  
  27193. }
  27194.  
  27195. /* return highlighted optoin
  27196. and erase menu */
  27197. delwin(subwin1);
  27198. erase_window(subwin1);
  27199. touchwin(stdscr);
  27200. wrefresh(stdscr);
  27201. touchwin(dialogue);
  27202.  
  27203. wrefresh(dialogue);
  27204. return (pullmenu[i].ptr[position].letter);
  27205.  
  27206. }
  27207.  
  27208. /* calculate and produce menubar string */
  27209. char *strmenu(int length, MENUBAR *menubar, int pos)
  27210. {
  27211.  
  27212. int i,j,k;
  27213. int count;
  27214. int string_length;
  27215.  
  27216. static char buffer[100];
  27217.  
  27218. /* determine max length for string */
  27219. string_length = length/TCHOICES;
  27220.  
  27221. k = 0;
  27222. j = 0;
  27223.  
  27224. /* add proper number of options */
  27225. for (i=0;i<TCHOICES;i++) {
  27226.  
  27227. menubar[i].pos = k;
  27228.  
  27229. /* add each option, highlight as necessary */
  27230. for (j=0;menubar[i].string[j]!='\0';j++,k++) {
  27231. if (pos == i)
  27232. buffer[k] = toupper(menubar[i].string[j]);
  27233. else
  27234. buffer[k] = tolower(menubar[i].string[j]);
  27235. }
  27236.  
  27237. /* pad with spaces to proper length */
  27238. while (k<(string_length*(i+1)+2)) {
  27239. buffer[k] = ' ';
  27240. k++;
  27241. }
  27242.  
  27243. }
  27244.  
  27245. return(buffer);
  27246.  
  27247. }
  27248.  
  27249. /* initialize the screen at start */
  27250.  
  27251. void set_stdscr(void)
  27252. {
  27253.  
  27254. int i;
  27255.  
  27256. wclear (stdscr);
  27257.  
  27258. #ifdef BCC
  27259. wattrset(stdscr, F_RED B_BLUE);
  27260. /* fill in screen with color */
  27261. for (i=0;i<MAX_ROWS;i++)
  27262. mvinsertln(i,0);
  27263. #endif
  27264. box(stdscr, DOUBLE_SIDE, DOUBLE_ACROSS);
  27265.  
  27266. refresh();
  27267.  
  27268. return;
  27269.  
  27270. }
  27271.  
  27272. /* print string to dialogue box */
  27273. int to_dialogue(char *string)
  27274. {
  27275.  
  27276. clear_dialogue();
  27277. mvwaddstr(dialogue, 1, 1, string);
  27278.  
  27279. box(dialogue, SINGLE_SIDE, SINGLE_ACROSS);
  27280. wrefresh(dialogue);
  27281.  
  27282. return;
  27283.  
  27284. }
  27285.  
  27286. void clear_dialogue(void)
  27287. {
  27288.  
  27289. werase(dialogue);
  27290. box(dialogue, SINGLE_SIDE, SINGLE_ACROSS);
  27291. wrefresh(dialogue);
  27292.  
  27293. return;
  27294.  
  27295. }
  27296.  
  27297. void execute_command
  27298. (int i, int choice, PULLDOWN *pullmenu)
  27299. {
  27300.  
  27301. int j;
  27302.  
  27303. touch_window(tbar);
  27304.  
  27305. for (j=0;j<pullmenu[i].num;j++) {
  27306.  
  27307. /* use function pointer to execute command */
  27308. if ( choice == pullmenu[i].ptr[j].letter) {
  27309. (*(pullmenu[i].ptr[j].funcptr))();
  27310.  
  27311. break;
  27312. };
  27313.  
  27314. }
  27315.  
  27316. clear_dialogue();
  27317.  
  27318. }
  27319.  
  27320. /* convert a string to all uppercase */
  27321. void strtoupper(char *string)
  27322. {
  27323.  
  27324. int i;
  27325.  
  27326. for (i=0;string[i]!='\0';i++) {
  27327.  
  27328. string[i] = toupper(string[i]);
  27329.  
  27330. }
  27331.  
  27332. return;
  27333.  
  27334. }
  27335.  
  27336. /* convert a string to all lowercase */
  27337. void strtolower(char *string)
  27338. {
  27339.  
  27340. int i;
  27341.  
  27342. for (i=0;string[i]!='\0';i++) {
  27343.  
  27344. string[i] = tolower(string[i]);
  27345.  
  27346. }
  27347.  
  27348. return;
  27349.  
  27350. }
  27351.  
  27352. /* End of File */
  27353.  
  27354.  
  27355. Listing 4 menu.c -- code for creating a menubar
  27356. #include <stdio.h>
  27357. #include <stdlib.h>
  27358. #include <signal.h>
  27359. #include <time.h>
  27360. #include <string.h>
  27361.  
  27362. #ifdef BCC
  27363. #include <dos.h>
  27364. #include <conio.h>
  27365. #endif
  27366.  
  27367. #include "curses.h"
  27368. #include "menu.h"
  27369. #include "internal.h"
  27370.  
  27371.  
  27372. char choice;
  27373.  
  27374. /* windows global to all routines */
  27375.  
  27376. WINDOW *dialogue;
  27377. WINDOW *tbar;
  27378.  
  27379. /* define menubar with three options */
  27380.  
  27381. MENUBAR menubar[TCHOICES] = {
  27382. "file", 'f', 0,
  27383. "edit", 'e', 0,
  27384. "options", 'o', 0,
  27385. };
  27386.  
  27387. /* define pulldown menu sub-choices */
  27388.  
  27389. CHOICES choices1[3] = {
  27390. "open ", 'o', c_open,
  27391. "close", 'c', c_close,
  27392. "exit ", 'e', c_exit,
  27393. };
  27394.  
  27395. CHOICES choices2[4] = {
  27396. "copy ", 'c', c_copy,
  27397. "paste ", 'p', c_paste,
  27398. "delete", 'd', c_delete,
  27399. "move ", 'm', c_move,
  27400. };
  27401.  
  27402. CHOICES choices3[4] = {
  27403. "version", 'v', c_version,
  27404. "compile", 'c', c_compile,
  27405. "link ", 'l', c_link,
  27406. "run ", 'r', c_run,
  27407. };
  27408.  
  27409. /* tie all choices into one struct */
  27410.  
  27411. PULLDOWN pullmenu[TCHOICES] = {
  27412. 3, 5, choices1,
  27413. 4, 6, choices2,
  27414. 4, 7, choices3,
  27415. };
  27416.  
  27417. main()
  27418. {
  27419.  
  27420. int i,j,k;
  27421.  
  27422. initscr();
  27423.  
  27424. /* needed to return one keystroke at a time */
  27425.  
  27426. #ifndef VMS
  27427. cbreak();
  27428. #else
  27429. crmode();
  27430.  
  27431. #endif
  27432.  
  27433. /* activate keypad code */
  27434.  
  27435. #ifdef HPUX
  27436. keypad(stdscr, TRUE);
  27437. #endif
  27438.  
  27439. noecho();
  27440.  
  27441. #ifdef BCC
  27442. cursoff();
  27443. #endif
  27444.  
  27445. /* set up screen */
  27446.  
  27447. set_stdscr();
  27448.  
  27449. dialogue = popup(3, MAX_COLUMNS-4, MAX_ROWS-4, 2);
  27450.  
  27451. clear_dialogue();
  27452.  
  27453. tbar = topbar(stdscr);
  27454.  
  27455. /* enter loop to process options */
  27456.  
  27457. for (;;) {
  27458.  
  27459. choice = do_menubar(tbar, menubar);
  27460.  
  27461. for (i=0;i<TCHOICES;i++) {
  27462.  
  27463. if ( choice == menubar[i].letter) {
  27464.  
  27465. choice = do_pulldown(i,pullmenu,menubar);
  27466.  
  27467. execute_command(i, choice, pullmenu);
  27468.  
  27469. break;
  27470.  
  27471. } /* if */
  27472. } /* for loop */
  27473.  
  27474. } /* end main loop (for (;;))*/
  27475.  
  27476. }
  27477.  
  27478. /* these commands must be called at exit */
  27479. int clean_up()
  27480. {
  27481.  
  27482. erase();
  27483. refresh();
  27484. endwin();
  27485.  
  27486. #ifdef BCC
  27487. clrscr();
  27488. #endif
  27489.  
  27490.  
  27491. exit(0);
  27492.  
  27493. return(0);
  27494. }
  27495. /* End of File */
  27496.  
  27497.  
  27498. Listing 5 internal.h -- function prototypes
  27499. extern int c_open();
  27500. extern int c_close();
  27501. extern int c_exit();
  27502. extern int c_copy();
  27503. extern int c_paste();
  27504. extern int c_delete();
  27505. extern int c_move();
  27506. extern int c_compile();
  27507. extern int c_link();
  27508. extern int c_run();
  27509. extern int c_version();
  27510. extern int c_testchar();
  27511. /* End of File */
  27512.  
  27513.  
  27514. Listing 6 funcs.c
  27515. #include <stdio.h>
  27516. #include "curses.h"
  27517. #ifdef BCC
  27518. #include <dos.h>
  27519. #endif
  27520. #include "menu.h"
  27521. #include "internal.h"
  27522.  
  27523. /*
  27524. all these functions are shells
  27525. except for c_exit & c_version
  27526. */
  27527.  
  27528. int c_open()
  27529. {
  27530.  
  27531. to_dialogue("open");
  27532.  
  27533. sleep(3);
  27534.  
  27535. return;
  27536. }
  27537.  
  27538. int c_close()
  27539. {
  27540.  
  27541. to_dialogue("close");
  27542. sleep(3);
  27543.  
  27544. return;
  27545. }
  27546.  
  27547. int c_exit()
  27548. {
  27549. to_dialogue("exit");
  27550.  
  27551. clean_up();
  27552.  
  27553. return;
  27554.  
  27555. }
  27556.  
  27557. int c_copy()
  27558. {
  27559. to_dialogue("copy");
  27560. sleep(3);
  27561.  
  27562. return;
  27563.  
  27564. }
  27565.  
  27566. int c_paste()
  27567. {
  27568. to_dialogue("paste");
  27569. sleep(3);
  27570.  
  27571. return;
  27572. }
  27573.  
  27574. int c_delete()
  27575. {
  27576. to_dialogue("delete");
  27577. sleep(3);
  27578.  
  27579. return;
  27580. }
  27581.  
  27582. int c_move()
  27583. {
  27584. to_dialogue("move");
  27585. sleep(3);
  27586.  
  27587. return;
  27588. }
  27589.  
  27590. int c_compile()
  27591. {
  27592. to_dialogue("compile");
  27593. sleep(3);
  27594.  
  27595. return;
  27596.  
  27597. }
  27598.  
  27599. int c_link()
  27600. {
  27601. to_dialogue("link");
  27602. sleep(3);
  27603.  
  27604. return;
  27605.  
  27606. }
  27607.  
  27608. int c_run()
  27609. {
  27610.  
  27611. to_dialogue("run");
  27612. sleep(3);
  27613.  
  27614. return;
  27615. }
  27616.  
  27617. int c_version()
  27618. {
  27619.  
  27620. to_dialogue("Version 3.0");
  27621. sleep(3);
  27622.  
  27623. return;
  27624.  
  27625. }
  27626. /* End of File */
  27627.  
  27628.  
  27629.  
  27630.  
  27631.  
  27632.  
  27633.  
  27634.  
  27635.  
  27636.  
  27637.  
  27638.  
  27639.  
  27640.  
  27641.  
  27642.  
  27643.  
  27644.  
  27645.  
  27646.  
  27647.  
  27648.  
  27649.  
  27650.  
  27651.  
  27652.  
  27653.  
  27654.  
  27655.  
  27656.  
  27657.  
  27658.  
  27659.  
  27660.  
  27661.  
  27662.  
  27663.  
  27664.  
  27665.  
  27666.  
  27667.  
  27668.  
  27669.  
  27670.  
  27671.  
  27672.  
  27673.  
  27674. A Prompting Function
  27675.  
  27676.  
  27677. Dale A. Panattoni
  27678.  
  27679.  
  27680. Mr. Panattoni received a Bachelor of Science degree in Computer Information
  27681. Systems from DeVry Institute of Technology in Los Angeles, CA. He has worked
  27682. the last four years as a Programmer/Analyst for DataStar Corporation, a
  27683. company that specializes in business and fruit accounting software. You may
  27684. contact him at DataStar Corporation, 6 South 55th Avenue, Yakima, WA 98908,
  27685. (509) 453-2455, or at his home number, (509) 453-2455.
  27686.  
  27687.  
  27688.  
  27689.  
  27690. Introduction
  27691.  
  27692.  
  27693. In school, they tell you that when writing the user interface section of a
  27694. program, you should write it as if a monkey will be sitting at the keyboard.
  27695. Based on my experience, I sometimes think that it would be easier to write
  27696. code for a monkey than a real user. A monkey may bang at the keyboard, but a
  27697. user often times will try to outwit the program and unknowingly enter
  27698. incorrect information.
  27699. Several years ago I was shown a data input function, called prompt, that not
  27700. only provided a good method for entering data, but also did basic validation
  27701. of the data being entered. The function was printed in a book titled
  27702. Variations in C by Steve Schustack (MicroSoft Press, 1985). After several
  27703. modifications and enhancements, this function has become a cornerstone for all
  27704. of my data input routines, both in MS-DOS and UNIX.
  27705. In order to demonstrate prompt without requiring you to have a commercial
  27706. windowing library, the version listed here has been stripped of all of its
  27707. windowing library calls and replaced with Standard C I/O routines. To read a
  27708. character from the keyboard, this version uses getchar. To display to the
  27709. screen it uses printf. And to position the cursor, it uses a macro called
  27710. MOVE_CUR. Since the MOVE_CUR macro uses ANSI codes, you need to include the
  27711. ansi.sys driver in the file config.sys. Because of these changes, this
  27712. function as it stands is written for MS-DOS, but it can easily be made to work
  27713. with any MS-DOS or UNIX windowing library by changing just a few function
  27714. calls.
  27715. prompt accepts input from the user and returns to the calling program an
  27716. integer value that represents a terminating key. The terminating key is the
  27717. key that is pressed by the user signaling either that some action may need to
  27718. be performed by the calling program or that the user is done entering data in
  27719. the field. The calling program will determine what needs to be done based on
  27720. that terminating key. For example, If the terminating key is an F1, you may
  27721. want to display a help screen or a pick list pertaining to the current field.
  27722. If the terminating key is what I call a "moving" terminating key--such as Up
  27723. Arrow, Down Arrow, Home, End, Tab, or Enter--the calling program will probably
  27724. want to move to the next or previous field.
  27725. The keys.h header file included in prompt (see Listing 1) defines the values
  27726. returned from getchar when the special keys, such as function and cursor keys,
  27727. are pressed. This header file needs to be included not only with the file that
  27728. defines prompt, but also with any program that calls it, if that calling
  27729. program needs to know what terminating key caused prompt to return. When you
  27730. implement a windowing library with prompt, this header file will probably need
  27731. to be replaced by the windowing package's header file that defines its own
  27732. return codes for the keyboard.
  27733. Besides the definitions of the special keys, I have included definitions for
  27734. four other terminating keys which can be returned by prompt. Because there are
  27735. times when a data field requires specific validation, I wanted to know whether
  27736. the user changed the data. If no changes occur, there is no need to do any
  27737. validating. Therefore I have defined a NO_CHANGE value for Up Arrow, Down
  27738. Arrow, Tab, and Enter (and only these four keys). They are the most common
  27739. keys that a user would press when done entering information for a field. If
  27740. you find that you use other terminating keys to end data input, you may need
  27741. to add NO_CHANGE values for those keys as well.
  27742.  
  27743.  
  27744. Calling the Prompting Function
  27745.  
  27746.  
  27747. The function prompt expects nine parameters:
  27748. data is a character pointer that points to a buffer holding the default value
  27749. of the data field to be entered by the user. It may be initialized to NULL, or
  27750. it may have a preset value. When prompt returns to the calling program, the
  27751. buffer that data points to will be changed if the user has entered any data.
  27752. match_char is a character code that represents what type of input will be
  27753. accepted as being valid from the keyboard. These codes are setup and
  27754. maintained in the match function.
  27755. min_len is an integer value depicting a minimum number of characters to be
  27756. entered by the user for this data field. The prompt function will not return a
  27757. "moving" terminating key unless the user has met this requirement.
  27758. max_len is an integer value depicting the maximum number of characters that
  27759. can be entered for this data field.
  27760. row and col are two integers which represent the starting row and column where
  27761. the user will begin entering data.
  27762. fyi is a character string that can be used either as the field's title, or as
  27763. a "For Your Information" (FYI) line to give the user instructions about what
  27764. is to be entered. If you decide not to use an FYI, you can pass a null string
  27765. for this parameter.
  27766. fyi_row and fyi_col are two integers which represent the starting row and
  27767. column for the FYI message display.
  27768. All of the row and column values refer to an entire screen of 12 rows and 80
  27769. columns. If you implement a windowing library with prompt, you will probably
  27770. want to add another parameter for a Window pointer. Then all of the row and
  27771. column numbers will be relative to the window being used.
  27772. When calling prompt, you can be as simple as you want, or you can analyze the
  27773. return code in detail. In the sample program (See Listing 2), I check to see
  27774. what terminating key is returned by prompt to determine what field the user
  27775. wants to go to next. If you have more than a couple of fields to call using
  27776. prompt, I suggest making an array of "field" structures that will hold all of
  27777. the values for prompt for each field. This has two immediate advantages.
  27778. First, the parameters passed to prompt are more readable at a glance. And
  27779. second, if the array of structures is used every time a field's data is
  27780. displayed to the screen, when your client asks you to move screen fields
  27781. around, you can move a field anywhere they want in one simple change.
  27782.  
  27783.  
  27784. Using the Prompting Function
  27785.  
  27786.  
  27787. prompt is a versatile function. It not only allows the user to enter in new
  27788. data but gives the user the freedom to edit existing data without having to
  27789. retype the entire field. When the program's cursor first arrives at a field,
  27790. the user can overwrite its data by simply typing new information. prompt will
  27791. wipe out the old data and replace it with the newly-entered information as
  27792. long as the first key entered by the user is not Right Arrow. If Right Arrow
  27793. is pressed as the first key, prompt will enter into an edit mode. Edit mode
  27794. will allow the user to move through the field's data using Left Arrow, Right
  27795. Arrow, Home, and End. The user also has the option of toggling between an
  27796. Insert mode and a Typeover mode by pressing the Insert key. When in Typeover
  27797. mode, the cursor appears as a block. When in Insert mode, the cursor appears
  27798. as an underscore. Because this version of prompt has been stripped of a
  27799. commercial windowing library in order to make it work with the Standard C
  27800. library, I have added the change_cur function to change the cursor's
  27801. appearance. If you implement a windowing library prompt, you will probably
  27802. want to replace change_cur with a similar function from your windowing
  27803. library.
  27804.  
  27805.  
  27806. Implementation
  27807.  
  27808.  
  27809. prompt is really quite basic when you look at it closely. (See Listing 3.)
  27810. When first called, it displays the data field and the For Your Information
  27811. line. After that, the function goes into a loop that gets characters from the
  27812. user until a terminating key is pressed. For each character entered by the
  27813. user, prompt will verify that it is valid based on the match code that was
  27814. passed in as a parameter. If the character entered is found to be valid, it is
  27815. displayed to the screen and the user is prompted to enter another character.
  27816. All editing keys such as Backspace, Left Arrow, Right Arrow, Home, and End can
  27817. be entered by the user.
  27818. When a terminating key is entered, prompt will do one of two things depending
  27819. on what kind of terminating key is pressed. If the terminating key is not a
  27820. "moving" key, prompt simply returns to the calling program the terminating key
  27821. that was pressed. If the terminating key is a "moving" key, prompt will make
  27822. sure that the length of the field's data meets or exceeds the field's minimum
  27823. length requirement. If the minimum length has been met, prompt will return to
  27824. the calling program. But before returning, prompt checks to see if the user
  27825. has entered anything for this field. If not, then a NO_CHANGE value for the
  27826. "moving" key is returned. Otherwise, the "moving" terminating key is returned
  27827. to the calling program.
  27828.  
  27829.  
  27830. Real-Time Validation
  27831.  
  27832.  
  27833. match is a function that you, the programmer, set up to define a series of
  27834. one-character codes that will represent sets of valid data. (See bottom of
  27835. Listing 3.) You can have as many sets as you like, and add them as often as is
  27836. needed. This function is what I think makes prompt so great. Once the user has
  27837. entered in the field's data, you can assume that the field is valid. prompt
  27838. will not allow any characters to be entered that are not defined as being
  27839. correct for a particular match code. There may be times however when you will
  27840. still have to do some validating when prompt returns to the calling program.
  27841. For example, while prompt can make sure that the user enters all of the
  27842. correct characters that make up a date, it does not validate the date. But
  27843. based on my experience with this function, prompt eliminates 50 to 60 percent
  27844. of the user entry errors by keeping them from typing invalid keys from the
  27845. beginning.
  27846.  
  27847. Listing 1 keys.h -- defines the values returned from getchar when special keys
  27848. are pressed
  27849. /* */
  27850.  
  27851. /* Definitions Of Keys From The */
  27852. /* Keyboard */
  27853. /* */
  27854.  
  27855. #define C_UP 328 /* Up Arrow */
  27856. #define C_DOWN 336 /* Down Arrow */
  27857. #define C_PGUP 329 /* Page Up Key */
  27858. #define C_PGDN 337 /* Page Down Key */
  27859. #define C_CR 13 /* Enter Key */
  27860. #define C_BACK 8 /* Backspace Key */
  27861. #define C_LEFT 331 /* Left Arrow */
  27862. #define C_RIGHT 333 /* Right Arrow */
  27863. #define C_TAB 9 /* Tab Key */
  27864. #define C_HOME 327 /* Home Key */
  27865. #define C_END 335 /* End Key */
  27866. #define C_F1 315 /* F1 - F12 Keys */
  27867. #define C_F2 316
  27868. #define C_F3 317
  27869. #define C_F4 318
  27870. #define C_F5 319
  27871. #define C_F6 320
  27872. #define C_F7 321
  27873. #define C_F8 322
  27874. #define C_F9 323
  27875. #define C_F10 324
  27876. #define C_F11 325
  27877. #define C_F12 326
  27878. #define C_ESC 27 /* Escape Key */
  27879. #define C_INS 338 /* Insert Key */
  27880. #define C_DEL 339 /* Delete Key */
  27881. #define CR_NO_CHG -1 /* This Value Represents
  27882. The ENTER Key Pressed
  27883. By The User Without
  27884. Making Any Changes To
  27885. The Field's Data */
  27886. #define UP_NO_CHG -2 /* Up Arrow - No Changes */
  27887. #define TB_NO_CHG -3 /* Tab Key - No Changes */
  27888. #define DN_NO_CHG -4 /* Down Arrow - No Changes */
  27889.  
  27890.  
  27891. Listing 2 Sample Program using prompt ()
  27892. #include <stdio.h>
  27893. #include <conio.h>
  27894. #include <ctype.h>
  27895. #include <dos.h>
  27896. #include <string.h>
  27897. #include "./keys.h"
  27898.  
  27899. /* Identify Field Indexes */
  27900. #define FNAME 0
  27901. #define LNAME 1
  27902. #define SEX 2
  27903. #define AGE 3
  27904.  
  27905. #define MOVE_CUR(row,col) printf("\x1B[%d;%df",row,col);
  27906.  
  27907. /* Prototypes */
  27908. extern int prompt (char *, char, int, int, int, int,
  27909. char *, int, int);
  27910.  
  27911.  
  27912. struct fields {
  27913. short row; /* Field Row */
  27914. short col; /* Field Column */
  27915. short fyi_row; /* FYI Row */
  27916. short fyi_col; /* FYI Column */
  27917. short min_len; /* Minimum Length */
  27918. short max_len; /* Maximum Length */
  27919. char match; /* Match Character Code */
  27920. char fyi[81] ; /* FYI Message */
  27921. };
  27922.  
  27923. struct fields field[] = {
  27924. { 10,37,15,35,0,30,'A',"ENTER IN YOUR FIRST NAME."},
  27925. { 11,37,15,35,0,30,'A',"ENTER IN YOUR LAST NAME. "},
  27926. { 12,37,15,35,0, 1,'X',"ENTER IN YOUR SEX. (M/F) "},
  27927. { 13,37,15,35,0, 3,'#',"ENTER IN YOUR AGE. "}
  27928. };
  27929.  
  27930. void main()
  27931. {
  27932. char xbuf[81]; /* Buffer */
  27933. char fname[31]; /* First Name */
  27934. char lname[31]; /* Last Name */
  27935. char sex; /* Male/Female */
  27936. int c;
  27937. int age;
  27938. int index;
  27939.  
  27940. /* Initialize Variables And Draw Screen Titles */
  27941. fname[0] = lname[0] = sex = '\0';
  27942.  
  27943. MOVE_CUR(10,25);
  27944. printf("FIRST NAME:");
  27945. MOVE_CUR(11,25);
  27946. printf("LAST NAME:");
  27947. MOVE_CUR(12,25);
  27948. printf("SEX M/F ..:");
  27949. MOVE_CUR(13,25);
  27950. printf("AGE ......:");
  27951. age = 0;
  27952. index = FNAME;
  27953.  
  27954. while (1) {
  27955. switch (index) {
  27956. case FNAME:
  27957. sprintf(xbuf,"%-s",fname);
  27958. c = prompt(xbuf,
  27959. field[FNAME].match,
  27960. field[FNAME].min_len,
  27961. field[FNAME].max_len,
  27962. field[FNAME].row,
  27963. field[FNAME].col,
  27964. field[FNAME].fyi,
  27965. field[FNAME].fyi_row,
  27966. field[FNAME].fyi_col);
  27967. switch (c) {
  27968. case C_UP :
  27969. case UP_NO_CHG:
  27970.  
  27971. case C_END :
  27972. /* Go To Last Screen Field */
  27973. index = AGE;
  27974. break;
  27975.  
  27976. case C_ESC :
  27977. /* Exit Program */
  27978. exit(0);
  27979.  
  27980. case C_CR :
  27981. case C_DOWN:
  27982. case C_TAB :
  27983. strcpy(fname,xbuf);
  27984. case CR_NO_CHG:
  27985. case DN_NO_CHG:
  27986. case TB_NO_CHG:
  27987. /* Go To Next Field */
  27988. index = LNAME;
  27989. break;
  27990. }
  27991. MOVE_CUR(10,37);
  27992. printf("%-30.30s",fname);
  27993. break;
  27994.  
  27995. case LNAME:
  27996. sprintf(xbuf,"%-s",lname);
  27997. c = prompt(xbuf,
  27998. field[LNAME].match,
  27999. field[LNAME].min_len,
  28000. field[LNAME].max_len,
  28001. field[LNAME].row,
  28002. field[LNAME].col,
  28003. field[LNAME].fyi,
  28004. field[LNAME].fyi_row,
  28005. field[LNAME].fyi_col);
  28006. switch (c) {
  28007. case C_END :
  28008. /* Go To Last Screen Field */
  28009. index = AGE;
  28010. break;
  28011.  
  28012. case C_ESC :
  28013. /* Exit Program */
  28014. exit (0);
  28015.  
  28016. case C_UP :
  28017. case UP_NO_CHG:
  28018. case C_HOME :
  28019. /* Go To Previous Field */
  28020. index = FNAME;
  28021. break;
  28022.  
  28023. case C_CR :
  28024. case C_DOWN:
  28025. case C_TAB :
  28026. strcpy(lname,xbuf);
  28027. case CR_NO_CHG:
  28028. case DN_NO_CHG:
  28029. case TB_NO_CHG:
  28030.  
  28031. /* Go To Next Field */
  28032. index = SEX;
  28033. break;
  28034. }
  28035. MOVE_CUR(11,37);
  28036. printf("%-30.30s",lname);
  28037. break;
  28038.  
  28039. case SEX:
  28040. sprintf(xbuf,"%c",sex);
  28041. c = prompt(xbuf,
  28042. field[SEX].match,
  28043. field[SEX].min_len,
  28044. field[SEX].max_len,
  28045. field[SEX].row,
  28046. field[SEX].col,
  28047. field[SEX].fyi,
  28048. field[SEX].fyi_row,
  28049. field[SEX].fyi_col);
  28050. switch (c) {
  28051. case C_UP :
  28052. case UP_NO_CHG:
  28053. /* Go To Previous Field */
  28054. index = LNAME;
  28055. break;
  28056.  
  28057. case C_ESC :
  28058. /* Exit Program */
  28059. exit(0);
  28060.  
  28061. case C_HOME:
  28062. /* Go To First Screen Field */
  28063. index = FNAME;
  28064. break;
  28065.  
  28066. case C_CR :
  28067. case C_DOWN:
  28068. case C_TAB :
  28069. sex = xbuf[0];
  28070. case C_END :
  28071. case CR_NO_CHG:
  28072. case DN_NO_CHG:
  28073. case TB_NO_CHG:
  28074. /* Go To Next Previous Field */
  28075. index = AGE;
  28076. break;
  28077. }
  28078. MOVE_CUR(12,37);
  28079. printf("%c",sex);
  28080. break;
  28081.  
  28082. case AGE:
  28083. sprintf(xbuf,"%-d",age);
  28084. c = prompt(xbuf,
  28085. field[AGE].match,
  28086. field[AGE].min_len,
  28087. field[AGE].max_len,
  28088. field[AGE].row,
  28089. field[AGE].col,
  28090.  
  28091. field[AGE].fyi,
  28092. field[AGE].fyi_row,
  28093. field[AGE].fyi_col);
  28094. switch (c) {
  28095. case C_UP :
  28096. case UP_NO_CHG:
  28097. /* Go To Previous Field */
  28098. index = SEX;
  28099. break;
  28100.  
  28101. case C_ESC :
  28102. /* Exit Program */
  28103. exit(0);
  28104.  
  28105. case C_HOME:
  28106. case C_CR :
  28107. case C_DOWN:
  28108. case C_TAB :
  28109. age = atoi(xbuf);
  28110. case CR_NO_CHG:
  28111. case DN_NO_CHG:
  28112. case TB_NO_CHG:
  28113. /* Go To Next Screen Field */
  28114. index = FNAME;
  28115. break;
  28116. }
  28117. MOVE_CUR(13,37);
  28118. printf("%- 3d",age);
  28119. break;
  28120. }
  28121. }
  28122. }
  28123.  
  28124. /* End of File */
  28125.  
  28126.  
  28127. Listing 3 Prompt Function
  28128. #include <stdio.h>
  28129. #include <conio.h>
  28130. #include <ctype.h>
  28131. #include <dos.h>
  28132. #include <string.h>
  28133. #include "./keys.h"
  28134.  
  28135. #ifndef YES
  28136. #define YES 1
  28137. #endif
  28138.  
  28139. #ifndef NO
  28140. #define NO 0
  28141. #endif
  28142.  
  28143. #define M_INSERT 1
  28144. #define M_TYPEOVER 2
  28145.  
  28146. #define MOVE_CUR(row,col) printf("\x1B[%d;%df",row,col);
  28147. #define NORMAL "\x1B[Om" /* ANSI Normal Video */
  28148. #define REVERSE "\x1B[7m" /* ANSI Reverse Video */
  28149.  
  28150.  
  28151. /* Prototypes */
  28152. extern int match (int, char);
  28153. extern void change_cur (int, int);
  28154. extern void main (void);
  28155.  
  28156. int key;
  28157.  
  28158. int prompt( data, match_ch, min_len, max_len, row, col,
  28159. message, fyi_row, fyi_col)
  28160.  
  28161. char data []; /* Return Message */
  28162. char match_ch; /* Code For Which Characters
  28163. Are Allowed */
  28164. int min_len; /* Min Length Allowed */
  28165. int max_len; /* Max Length Allowed */
  28166. int row; /* Window Row */
  28167. int col; /* Window Column */
  28168. char *message; /* Prompt Message */
  28169. int fyi_row; /* FYI Window Row */
  28170. int fyi_col; /* FYI Window Column */
  28171. {
  28172. char buf [80];
  28173. int terminate; /* Key That Caused Termination */
  28174. int index,i; /* General Index Variables */
  28175. int mode; /* Mode: INSERT or TYPEOVER */
  28176. int changed; /* Changed Flag: 1=YES, 0=NO */
  28177. int edit; /* Edit Flag: 1=YES, 0NO */
  28178. int frst_key; /* First Key Flag: 1=YES, 0=NO */
  28179. char more; /* YES/NO Flag . . .
  28180. Used To Terminate Input */
  28181.  
  28182. /* Initializations */
  28183. edit = NO;
  28184. frst_key = NO;
  28185. changed = NO;
  28186.  
  28187. mode = M_TYPEOVER;
  28188. change_cur(0, 13); /* Set Cursor To Block Mode */
  28189.  
  28190. /* Display The FYI Message At The FYI Row, Colunn */
  28191. if (strlen(message)) {
  28192. MOVE_CUR (fyi_row, fyi_col);
  28193. printf(message);
  28194. }
  28195.  
  28196. printf(REVERSE); /* Change To Reverse Video */
  28197.  
  28198. strcpy(buf, data); /* Make A Copy Of The Default
  28199. Return Value */
  28200.  
  28201. for (index = strlen(buf); index < max_len; ++index) {
  28202. buf [index] = ' '; /* Fill Remainder Of Default
  28203. Value With Blanks */
  28204. buf [index] = '\0';
  28205.  
  28206. /* Display Default Value For Field */
  28207. MOVE_CUR (row, col);
  28208. printf(buf);
  28209.  
  28210.  
  28211. /* Position Cursor At Beginning Of Field */
  28212. MOVE_CUR (row, col);
  28213.  
  28214. /* Get Input From User */
  28215. for (index = 0,more = YES; more; ) {
  28216. key = getch();
  28217. if (key == 0)
  28218. key = getch()+256;
  28219.  
  28220. switch(key) (
  28221. case C_F1 : /* F1 Key */
  28222. case C_F2 : /* F2 Key */
  28223. case C_F3 : /* F3 Key */
  28224. case C_F4 : /* F4 Key */
  28225. case C_F5 : /* F4 Key */
  28226. case C_F6 : /* F6 Key */
  28227. case C_F7 : /* F7 Key */
  28228. case C_F8 : /* F8 Key */
  28229. case C_F9 : /* F9 Key */
  28230. case C_F10 : /* F0 Key */
  28231. case C_F11 : /* F11 Key */
  28232. case C_F12 : /* F12 Key */
  28233. case C_ESC : /* ESCAPE Key */
  28234. frst_key = NO;
  28235. terminate = key;
  28236. buf[index] = '\0';
  28237. more = NO;
  28238. break;
  28239. case C_INS : /* INSERT Key */
  28240. frst_key = NO;
  28241. if (mode == M_INSERT) {
  28242. /* Set The Mode To TYPEOVER and
  28243. Change Cursor To Block Mode */
  28244. mode = M_TYPEOVER;
  28245. change_cur(0, 13);
  28246. }
  28247. else {
  28248. /* Set The Mode To INSERT And
  28249. Change Cursor To Underline */
  28250. mode = M_INSERT;
  28251. change_cur(12, 13);
  28252. }
  28253. putchar(bur[index]);
  28254. MOVE_CUR(row, (col + index));
  28255. break;
  28256.  
  28257. case C_BACK : /* BACKSPACE Key */
  28258. case C_DEL : /* DELETE Key */
  28259. frst_key = NO;
  28260. changed = YES;
  28261. if (index >= max_len)
  28262. index = max_len - 1;
  28263. for (i = index; i < max_len; i++) {
  28264. if (buf[i+1] == '\0')
  28265. buf[i] = ' ';
  28266. else
  28267. buf[i] = buf[i+1];
  28268. }
  28269.  
  28270.  
  28271. /* Redisplay The Field */
  28272. MOVE_CUR(row, col);
  28273. printf(buf);
  28274.  
  28275. /* Reposition The Cursor. */
  28276. MOVE_CUR(row, (col + index));
  28277. putchar(buf[index]);
  28278. MOVE_CUR(row, (col + index));
  28279.  
  28280. if (key == C_DEL)
  28281. break;
  28282.  
  28283. case C_LEFT : /* LEFT ARROW */
  28284. first_key = NO;
  28285. if (index <= 0)
  28286. break;
  28287. if (index >= max_len)
  28288. index = max_len -1;
  28289. index--;
  28290. MOVE_CUR(row, (col + index));
  28291. break;
  28292.  
  28293. case C_RIGHT: /* RIGHT ARROW */
  28294. frst_key = NO;
  28295. edit = YES;
  28296. if (index >= (max_len - 1))
  28297. break;
  28298. index++;
  28299. MOVE_CUR(row, (col + index));
  28300. break;
  28301.  
  28302. case C_HOME : /* HOME KEY */
  28303. case C_END : /* END KEY */
  28304. frst_key = NO;
  28305. if (edit == YES) {
  28306. if (key == C_END)
  28307. index = end_of_fld(buf,(strlen(buf)));
  28308. else
  28309. index = 0;
  28310. MOVE_CUR(row, (col + index));
  28311. break;
  28312. }
  28313.  
  28314. case C_DOWN : /* DOWN ARROW */
  28315. case C_UP : /* UP ARROW */
  28316. case C_PGDN : /* PAGE DOWN */
  28317. case C_PGUP : /* PAGE UP */
  28318. case C_CR : /* CARRIAGE RETURN */
  28319. case C_TAB : /* TAB Key */
  28320.  
  28321. frst_key = NO;
  28322.  
  28323. /* Reset Mode and Edit Flag */
  28324. edit = NO;
  28325. mode = M_INSERT;
  28326.  
  28327. if (strlen(data) >= (unsigned)min_len &&
  28328. index == 0 && changed == NO) {
  28329.  
  28330. /* If The User Has Not Changed The Value Of
  28331. The Data Field, Return The Appropriate
  28332. NO CHANGE Key For The terminating Key
  28333. That Was Pressed. */
  28334.  
  28335. if (key == C_CR) {
  28336. /* Return No Change For Carriage Return */
  28337. terminate = CR_NO_CHG;
  28338. }
  28339. if (key == C_UP) {
  28340. /* Return No Change For Up Arrow */
  28341. terminate = UP_NO_CHG;
  28342. }
  28343. if (key == C_DOWN) {
  28344. /* Return No Change For Down Arrow */
  28345. terminate = DN_NO_CHG;
  28346. }
  28347. if (key == C_TAB) {
  28348. /* Return No Change For Tab Key */
  28349. terminate = TB_NO_CHG;
  28350. }
  28351. if (key != C_CR &&
  28352. key != C_UP &&
  28353. key != C_DOWN &&
  28354. key != C_TAB)
  28355. terminate = key;
  28356.  
  28357. strcpy(buf, data);
  28358.  
  28359. more = NO;
  28360. break;
  28361. }
  28362.  
  28363. /* If Minimum Length Requirement Has been Met,
  28364. Quit */
  28365. if (index >= min_len) {
  28366. terminate = key;
  28367. more = NO;
  28368. break;
  28369. }
  28370.  
  28371. /* Else Ignore */
  28372. break;
  28373.  
  28374. default :
  28375. if (index == max_len)
  28376. /* At End Of Data Field - Do Nothing */
  28377. break;
  28378.  
  28379. /* We Have Dealt With All Of The Keys That We
  28380. Needed Above, So Now We Will Filter Out All
  28381. Keys And Characters Above 122 (z) Here. */
  28382.  
  28383. if (key > 'z') /* Ignore Key */
  28384. break;
  28385.  
  28386. if (match(index, match_ch) == YES) {
  28387. /* Only Accept The User Entered Character
  28388. If It Successfully Passed The match
  28389.  
  28390. function. */
  28391. edit = YES;
  28392. changed = YES;
  28393.  
  28394. if (frst_key == YES) {
  28395. /* If This Is The First Valid Key That
  28396. The User Has Entered, Then Wipe Out
  28397. The Field. */
  28398. for (i = 0; i < max_len; i++)
  28399. buf[i] = ' ';
  28400. frst_key = NO;
  28401. }
  28402.  
  28403. if (mode == M_INSERT) {
  28404. /* Insert The User Entered Character
  28405. Into The Field */
  28406. for (i = (max_len - 1); i > index; i--)
  28407. buf[i] = buf[i-1];
  28408. }
  28409.  
  28410. /* Overwrite The Buffer With The User
  28411. Entered Character */
  28412. buf[index] = (char)key;
  28413.  
  28414. /* Redisplay The Field */
  28415. MOVE_CUR(row, col);
  28416. printf(buf);
  28417.  
  28418. /* Reposition The Cursor. */
  28419. index++;
  28420. if (index >= max_len)
  28421. index --;
  28422. MOVE_CUR(row, (col + index));
  28423. putchar(buf[index]);
  28424. MOVE_CUR(row, (col + index));
  28425. }
  28426. break;
  28427. }
  28428. }
  28429. /* Redraw The Field In The Window's Normal
  28430. Color Attribute */
  28431. printf(NORMAL);
  28432. MOVE_CUR (row, col);
  28433. printf(buf);
  28434.  
  28435. strcpy(data, buf);
  28436. data[(end_of_fld(data,(strlen(data))) + 1)] = '\0';
  28437.  
  28438. /* Change Cursor Back To Underline */
  28439. change_cur(12, 13);
  28440. return (terminate);
  28441. }
  28442.  
  28443. static int match(index,match_ch)
  28444. int index;
  28445. char match_ch;
  28446. {
  28447. int matches = YES;
  28448.  
  28449.  
  28450. switch (match_ch) {
  28451. case '$' : /* 0-9 or ,$. */
  28452. if (!isdigit(key) && !strchr(".,$",key))
  28453. matches = NO;
  28454. break;
  28455.  
  28456. case '#' : /* 0 - 9 */
  28457. if (!isdigit(key))
  28458. matches = NO;
  28459. break;
  28460.  
  28461. case 'A' : /* A - Z */
  28462. if (!isalpha(key) && !strchr(" ",key))
  28463. matches = NO;
  28464. break;
  28465.  
  28466. case 'D' : /* Date: 0-9 or - or / or .*/
  28467. if (!isdigit(key) && !strchr(".-*/",key))
  28468. matches = NO;
  28469. break;
  28470.  
  28471. case 'l' : /* a-z, A-Z, 0-9, or ;:.,/?*-$#()'! or
  28472. leading space */
  28473. if (!isalnum(key) &&
  28474. !strchr(" !@#$%^&*()-_=+[{]}';:.,/?",key))
  28475. matches = NO;
  28476. break;
  28477.  
  28478. case 'Q' : /* YyNn as Reply To Yes/No Question */
  28479. if (!strchr("YyNn",key))
  28480. matches = NO;
  28481. key = toupper(key);
  28482. break;
  28483.  
  28484. case 'S' : /* 0-9 or + or - */
  28485. if (!isdigit(key) && !strchr("+-",key))
  28486. matches = NO;
  28487. break;
  28488.  
  28489. case 'T' : /* Time: 0-9 or . or : */
  28490. if (!isdigit(key) && !strchr(".:",key))
  28491. matches = NO;
  28492. break;
  28493.  
  28494. case 'X' : /* MmFf As Reply To Male/Female */
  28495. if (!strchr("MmFf",key))
  28496. matches = NO;
  28497. key = toupper(key);
  28498. break;
  28499.  
  28500. default :
  28501. matches = NO;
  28502. break;
  28503. }
  28504. return (matches);
  28505. }
  28506.  
  28507. /* This Function Will Return A 0 Based Index Of The
  28508. First Non-Blank Character At The End Of A String. */
  28509.  
  28510. int end_of_fld(string, length)
  28511. char *string;
  28512. int length;
  28513. {
  28514. int i;
  28515.  
  28516. for (i = length - 1; i >= 0; i--) {
  28517. if (string[i] != ' ' && string[i] != '\0')
  28518. return(i);
  28519. }
  28520. return(0);
  28521. }
  28522.  
  28523. /* This Function Will Change The Appearance Of The
  28524. Cursor To A Block Or An Underscore */
  28525. void change_cur(start, end)
  28526. int start;
  28527. int end;
  28528. {
  28529. union REGS r;
  28530.  
  28531. r.h.ah = 1;
  28532. r.h.ch = start;
  28533. r.h.cl = end;
  28534. int86(0x10, &r, &r);
  28535. }
  28536.  
  28537. /* End of File */
  28538.  
  28539.  
  28540.  
  28541.  
  28542.  
  28543.  
  28544.  
  28545.  
  28546.  
  28547.  
  28548.  
  28549.  
  28550.  
  28551.  
  28552.  
  28553.  
  28554.  
  28555.  
  28556.  
  28557.  
  28558.  
  28559.  
  28560.  
  28561.  
  28562.  
  28563.  
  28564.  
  28565.  
  28566.  
  28567.  
  28568.  
  28569.  
  28570.  
  28571.  
  28572.  
  28573. Mapping Functions for Repetitive Structures
  28574.  
  28575.  
  28576. Steven K. Graham
  28577.  
  28578.  
  28579. Steven K. Graham has worked for Hewlett-Packard, served on the Faculty at
  28580. UMKC, and is currently a Senior Engineer with CSI.
  28581.  
  28582.  
  28583. Higher-order functions take other functions as arguments. They are usually
  28584. used to apply a function to some repetitive data object, such as a list or a
  28585. tree. Such use of higher-order functions is referred to as mapping, and the
  28586. higher-order functions are called mapping functions. The key benefit of
  28587. higher-order functions is the extra layer of abstraction they introduce. In
  28588. essence, mapping functions allow you to create custom control structures that
  28589. can be applied to data structures routinely used in a particular environment.
  28590. Mapping functions do not simplify code intended for a single use or a single
  28591. purpose. Only when a data object is processed repeatedly in similar
  28592. ways--where the control flow is the same, but the functions performed vary--is
  28593. a mapping function valuable. As an example, consider the general case of
  28594. processing records in a particular kind of data file. This can be regarded as
  28595. mapping the processing function onto the records of the file. A mapping
  28596. function could be built that takes any given record processing function and
  28597. applies it to all the records of a file.
  28598.  
  28599.  
  28600. Uses for Mapping Functions
  28601.  
  28602.  
  28603. Despite sparse use in traditional programming languages, mapping functions are
  28604. used in many familiar contexts. In mathematics, a higher-order function is
  28605. used to express iterated sums:
  28606. Click Here for Equation
  28607. The summation applies the function F to each integer from 1 to n, and
  28608. accumulates the sum. If n were 5 and F(x) = x x, then sum = 11 + 22 + 33 + 44
  28609. + 55. It can be argued that a large part of the success of mathematics is due
  28610. to developing effective language and notations to express complex ideas. In
  28611. UNIX, the notion of higher-order commands has appeared in commands that take
  28612. other commands as arguments and control their application. sed is a good
  28613. example of this.
  28614. Some languages provide convenient methods for expressing higher-order
  28615. functions. Lisp and its dialects such as Scheme, provide functions as first
  28616. class objects--functions can be passed as arguments, returned as values,
  28617. assigned as array elements and so on. So Lisp programmers commonly use mapping
  28618. functions and are accustomed to exploiting their power. Other languages
  28619. restrict the use of functions to varying degrees, but higher-order functions
  28620. are possible in languages readily available, such as C. Because of C's
  28621. (relatively) strong typing, mapping functions force the processing functions
  28622. they apply to return a particular type, and it is necessary to coerce types to
  28623. pass a function argument to a mapping function.
  28624.  
  28625.  
  28626. Example Programs
  28627.  
  28628.  
  28629. Listing 1 illustrates a C version of summation. It defines two mapping
  28630. functions, sum and dsum, and three processing functions which are mapped onto
  28631. a range of integers, pi, id, and sq. Note that sum and dsum are basically for
  28632. loops that process the integers from a to b, at each step accumulating the
  28633. result of applying the function parameter term to the current value of i.
  28634. Because of C's typing, you need two versions to accommodate different return
  28635. types despite the identical control structures, so dsum is used to accumulate
  28636. a floating-point sum. One of the processing functions, id, is particularly
  28637. noteworthy, because it is only in the context of higher-order functions that a
  28638. programmer ever needs an identity function that does nothing but return its
  28639. argument value. However, id is needed for sum to simply add the integers from
  28640. a to b.
  28641. The call to dsum, approximates p using a partial sum of an infinite series.
  28642. This is drawn from the identity that
  28643. Click Here for Equation
  28644. which can be written as
  28645. Click Here for Equation
  28646. with the function pi (in Listing 1) providing the value for each term, given
  28647. the value for i.
  28648. Higher-order functions become mapping functions when applied to repeating
  28649. structures, such as lists or trees. A list is comprised of an item and a
  28650. pointer to the rest of the list (which is also a list). A tree is comprised of
  28651. a node, with subtrees as children. This mapping approach can be applied to the
  28652. directory tree of a UNIX file system (see Listing 2). The function mapdir is a
  28653. recursive function that maps its argument filefn onto all the files in the
  28654. hierarchy below a commandline directory argument.
  28655. This example applies the function suidfile that prints out the name of files
  28656. that have the setuid bit set. The setuid bit indicates that when the file is
  28657. executed, it will change its effective user ID to the owner of the file when
  28658. it is run. Programs that set the user ID can lead to gaping holes in system
  28659. security. (Sun's Programmer's Guide to Security Features comments on its use,
  28660. "1. Don't do it unless absolutely necessary.") Using mapdir, it is simple to
  28661. search an entire directory structure for programs that set the user ID. This
  28662. example could be part of a suite of programs designed to explore the file
  28663. system for potential security problems. A more elaborate system could be
  28664. devised to check other file attributes, such as comparing file permissions to
  28665. the permissions of the directory that contains them. The real power of this
  28666. approach is that the mapdir function is independent of any particular use.
  28667. Simply by calling mapdir with the function printfile (in Listing 3) as an
  28668. argument, you have a program to display directories recursively. By creating
  28669. routines that check for unusually large files, for unusually small files, or
  28670. for large numbers of files within a single directory, you can build tools to
  28671. help manage disk usage.
  28672. The functions and structures used in mapdir are drawn from the standard
  28673. include files, dirent.h, sys/dirent.h and sys/stat.h. These examples were
  28674. developed on a Sun SPARCstation using SunOS 4.1. dirent.h provides the DIR
  28675. struct, along with the functions opendir, closedir, and readdir to manipulate
  28676. it. sys/dirent.h provides the struct dirent, for directory entries:
  28677. struct dirent {
  28678. off_t d_off;
  28679. /* offset of next disk dir entry */
  28680. unsigned long d_fileno;
  28681. /* file number of entry */
  28682. unsigned short d_reclen;
  28683. /* length of this record */
  28684. unsigned short d_namlen;
  28685. /* length of string in d_name */
  28686. char d_name[255+1];
  28687. /* name (up to MAXNAMLEN + 1) */
  28688. };
  28689. For the example programs, the field d_name, a string that specifies the file
  28690. name for a particular directory entry, was the only field used. stat.h
  28691. provides the following structure to store information about files:
  28692. struct stat {
  28693. dev_t st_dev;
  28694. /* device file resides on */
  28695. ino_t st_ino;
  28696. /* file serial number */
  28697. mode_t st_mode;
  28698. /* file mode */
  28699. short st_nlink;
  28700. /* # of hard links to file */
  28701. uid_t st_uid;
  28702. /* owner's user ID */
  28703.  
  28704. gid_t st_gid;
  28705. /* owner's group ID */
  28706. dev_t st_rdev;
  28707. /* dev identifier for *
  28708. /* dev identifier for *
  28709. * special files */
  28710. off_t st_size;
  28711. /* file size in bytes *
  28712. * - (off_t is a long) */
  28713. time_t st_atime;
  28714. /* last access time */
  28715. int st_spare1;
  28716. time_t st_mtime;
  28717. /* last modify time */
  28718. int st_spare2;
  28719. time_t st_ctime;
  28720. /* last status change time */
  28721. int st_spare3;
  28722. long st_blksize;
  28723. /* preferred block size */
  28724. long st_blocks;
  28725. /* # of blocks allocated */
  28726. long st_spare4[2];
  28727. };
  28728. The example programs use the st_mode field, as well as some of the macros and
  28729. constants defined in stat.h. In particular, the macros S_ISDIR, S_ISLNK, and
  28730. the bit mask S_ISUID are used to examine the st_mode field, which records file
  28731. types and permissions. stat.h also declares the stat function that reads the
  28732. file information. stat has the declaration
  28733. int stat(path, buf)
  28734. char *path;
  28735. struct stat *buf;
  28736. The mapdir program can be made more general. In Listing 3, mapif.c, the mapdir
  28737. function has been modified to accept three functions as arguments: a predicate
  28738. function, a then function and an else function. The purpose is to not only map
  28739. these functions across the file system, but also provide alternative actions
  28740. depending on the results of the predicate function. The example uses a
  28741. function that tests for the setuid bit, a printfile function, and a "do
  28742. nothing" function, to reproduce the behavior of the earlier example from
  28743. Listing 2.
  28744.  
  28745.  
  28746. Variations to Mapping Functions
  28747.  
  28748.  
  28749. In these example, the functions that are mapped rely on a single filename
  28750. argument. This can be varied. A file descriptor could be passed. The complete
  28751. path could be passed in addition to the filename. Other parameters might
  28752. specify the level in the file hierarchy or information about the directory
  28753. containing the file. Global variables or additional parameters could be set
  28754. using command-line arguments and used to provide thresholds for searching for
  28755. unusually large or small files, or perhaps a search string for building a
  28756. grep-like function out of mapdir and a routine that searches a single file.
  28757. Besides operating on files within the file system, a mapping function could be
  28758. written that processes the directories rather than (or in addition to)
  28759. processing the files, for example, counting the number of files in each
  28760. directory.
  28761. The file system isn't the only regularly structured data in a UNIX system. A
  28762. simple procedure based on the mapping notion could be written that applies a
  28763. function to each line within a file. Using such a routine, functions could
  28764. operate on the passwd file, checking for users with no password, or checking
  28765. directory protections on the home directories of each user. A function could
  28766. be written to process every user's .cshrc file, perhaps to incorporate changes
  28767. uniformly.
  28768. UNIX, perhaps more than any other system, is characterized by an abundance of
  28769. tools. As I mentioned at the outset, many of these tools incorporate the
  28770. notion of function or command arguments. The find command, with an - exec
  28771. parameter, processes the file system in much the way that mapdir does. So why
  28772. bother with mapdir? First, the key point is not the particular example, but
  28773. the general idea: whenever possible, abstract general behaviors over regular
  28774. structures into mapping functions. Second, tools built in C rather than as
  28775. shell scripts or command invocations are more flexible. Their input and output
  28776. can be varied; alternatives can be handled; and programs can be tailored to
  28777. particular uses. Finally, the performance of different approaches will vary.
  28778. In some cases, a program may be more efficient than a command with respect to
  28779. some essential resources.
  28780.  (C) 1993 by Steven K. Graham
  28781.  
  28782. Listing 1 Mapping and processing functions
  28783. int sum(a,b, term)
  28784. int a,b, (*term) ();
  28785. {
  28786. int i, sum = 0;
  28787. for (i=a; i<b; i++)
  28788. sum = sum + (*term) (i);
  28789. return sum;
  28790. }
  28791.  
  28792. double dsum(a,b, term)
  28793. int a,b;
  28794. double (*term) ();
  28795. {
  28796. int i;
  28797. double sum = 0;
  28798. for (i=a; i<b; i++)
  28799. sum = sum + (*term) (i);
  28800. return sum;
  28801. }
  28802.  
  28803.  
  28804. double pi(x)
  28805. int x;
  28806. { return 1.0/(16*x*x + 16*x + 3); }
  28807.  
  28808. int id(x)
  28809. int x;
  28810. { return x; }
  28811.  
  28812. int sq(x)
  28813. int x;
  28814. { return x*x; }
  28815.  
  28816. int main(argc, argv)
  28817. int argc;
  28818. char **argv;
  28819. {
  28820. int n,x;
  28821. char * pid, *psq, *ppi;
  28822.  
  28823. if (argc < 2) return -1;
  28824. n = atoi(argv[1]);
  28825. x = atoi(argv[2]);
  28826.  
  28827. pid = (char *) id;
  28828. psq = (char *) sq;
  28829. printf("%d is the sum of %d to %d\n", sum(n,x,pid), n, x);
  28830. printf("%d is the sum of squares from %d to %d\n\n", sum(n,x,psq),n,x);
  28831. ppi = (char *) pi;
  28832. printf("%10.8f is an approximation of PI\n\n", 8*dsum(n,x,ppi));
  28833.  
  28834. return 0;
  28835. }
  28836.  
  28837. /* End of File */
  28838.  
  28839.  
  28840. Listing 2 Mapping functions applied to a repeating structure -- in this case a
  28841. UNIX file system
  28842. #include <string.h>
  28843. #include <dirent.h>
  28844. #include <sys/stat.h>
  28845. #include <sys/param.h>
  28846.  
  28847. #define LASTCHR(s) s[strlen(s)-1]
  28848.  
  28849. int suidfile(name)
  28850. char *name;
  28851. {
  28852. struct stat stbuf;
  28853. stat(name, &stbuf);
  28854. if (stbuf.st_mode & S_ISUID) printf("%s\n", name);
  28855. return 0;
  28856. }
  28857.  
  28858. int mapdir(name, filefn)
  28859. char *name;
  28860. int (*filefn) ();
  28861. {
  28862. DIR *dirp;
  28863.  
  28864. struct dirent *entry;
  28865. struct stat stbuf;
  28866. char olddir[MAXPATHLEN];
  28867. char cname[MAXPATHLEN];
  28868.  
  28869. getwd(olddir);
  28870. strcpy(cname, name);
  28871. if (chdir(name)) {
  28872. printf("Couldn't change to directory %s\n", name);
  28873. return -1;
  28874. }
  28875. if ((dirp = opendir(name)) == NULL){
  28876. printf("Unable tto open DIR %s\n", name);
  28877. chdir(olddir);
  28878. return -1;
  28879. }
  28880. for (entry=readdir(dirp); entry != NULL; entry=readdir(dirp)) {
  28881. if (0 != stat(entry->d_name, &stbuf))
  28882. printf("Can't read file information %s\n", entry->d_name);
  28883. else if (strcmp(entry->d_name, ".") == 0 
  28884. strcmp(entry->d_name, "..") == 0)
  28885. /* don't pursue these entries */ ;
  28886. else if (S_ISLNK(stbuf.st_mode))
  28887. /* don't pursue links */ ;
  28888. else if (S_ISDIR(stbuf.st_mode)) {
  28889. if (LASTCHR(cname) != '/') strcat(cname, "/");
  28890. strcat(cname, entry->d_name);
  28891. mapdir(cname, filefn);
  28892. strcpy(cname, name);
  28893. } else
  28894. (*filefn) (entry->d_name);
  28895. }
  28896. closedir(dirp);
  28897. chdir(olddir);
  28898. return 0;
  28899. }
  28900.  
  28901. char *setupdir(buf, name)
  28902. char *name;
  28903. char *buf;
  28904. {
  28905. char curdir[MAXPATHLEN];
  28906.  
  28907. getwd(curdir);
  28908. if (name[0] == '/') /* absolute pathname */
  28909. strcpy(buf, name);
  28910. else { /* relative pathname*/
  28911. strcpy(buf, curdir);
  28912. if (buf[strlen(buf)-1] != '/') strcat(buf, "/");
  28913.  /* may be at root */
  28914. strcat(buf, name);
  28915.  
  28916. }
  28917. return buf;
  28918. }
  28919.  
  28920. int main(argc, argv)
  28921. int argc;
  28922. char **argv;
  28923.  
  28924. {
  28925. char *filefn;
  28926. char name[MAXPATHLEN];
  28927.  
  28928. if (argc < 2) {
  28929. printf("Usage: %s <directory>",argv[0]);
  28930. return -1;
  28931. }
  28932. setupdir(name, argv[1]);
  28933. filefn = (char *) suidfile;
  28934. return mapdir(name, filefn);
  28935. }
  28936. /* End of File */
  28937.  
  28938.  
  28939. Listing 3 mapdir -- maps its argument onto all the files in the hierarchy
  28940. below the command-line directory argument.
  28941. #include <string.h>
  28942. #include <dirent.h>
  28943. #include <sys/stat.h>
  28944. #include <sys/param.h>
  28945.  
  28946. #define LASTCHR(s) s[strlen(s)-1]
  28947. int do_nothing(name)
  28948. char *name;
  28949. { return 0; }
  28950.  
  28951. int printfile(name)
  28952. char *name;
  28953. { printf("%s\n", name);
  28954. return 0;
  28955. }
  28956.  
  28957. int suidfile(name)
  28958. char *name;
  28959. { struct stat stbuf;
  28960. stat(name, &stbuf);
  28961. return (stbuf.st_mode & S_ISUID);
  28962. }
  28963.  
  28964. int mapdir(name, predfn, thenfn, elsefn)
  28965. char *name;
  28966. int (*predfn)(), (*thenfn)(), (*elsefn)();
  28967. {
  28968. DIR *dirp;
  28969. struct dirent *entry;
  28970. struct stat stbuf;
  28971. char olddir[MAXPATHLEN];
  28972. char cname[MAXPATHLEN];
  28973.  
  28974. getwd(olddir);
  28975. strcpy(cname, name);
  28976. if (chdir(name)) {
  28977. printf("Couldn't change to directory %s\n", name);
  28978. return -1;
  28979. }
  28980. if ((dirp = opendir(name)) == NULL){
  28981. printf("Unable tto open DIR %s\n", name);
  28982. chdir(olddir);
  28983. return -1;
  28984.  
  28985. }
  28986. for (entry=readdir(dirp); entry != NULL; entry=readdir(dirp)) {
  28987. if (0 != stat(entry->d_name, &stbuf))
  28988. printf("Can't read file information %s\n",
  28989. entry->d_name);
  28990. else if (strcmp(entry->d_name, ".") == 0 
  28991. strcmp(entry->d_name, "..") == 0)
  28992. /* don't pursue these entries */ ;
  28993. else if (S_ISLNK(stbuf.st_mode))
  28994. /* don't pursue links */ ;
  28995. else if (S_ISDIR(stbuf.st_mode)) {
  28996. if (LASTCHR(cname) != '/') strcat(cname, "/");
  28997. strcat(cname, entry->d_name);
  28998. mapdir(cname, predfn, thenfn, elsefn);
  28999. strcpy(cname,name);
  29000. } else
  29001. if ((*predfn) (entry->d_name))
  29002. (*thenfn) (entry->d_name);
  29003. else (*elsefn) (entry->d_name);
  29004. }
  29005. closedir(dirp);
  29006. chdir(olddir);
  29007. return 0;
  29008. }
  29009.  
  29010. char *setupdir(buf, name)
  29011. char *name;
  29012. char *buf;
  29013. {
  29014. char curdir[MAXPATHLEN];
  29015.  
  29016. getwd(curdir);
  29017. if (name[0] == '/')
  29018. strcpy(buf, name);
  29019. else { strcpy(buf, curdir);
  29020. if (buf[strlen(buf)-1] != '/') strcat(buf, "/");
  29021.  /* may be at root */
  29022. strcat(buf, name);
  29023. }
  29024. return buf;
  29025. }
  29026.  
  29027. int main(argc, argv)
  29028. int argc;
  29029. char **argv;
  29030. {
  29031. char *predfn, *thenfn, *elsefn;
  29032. char name[MAXPATHLEN];
  29033.  
  29034. if (argc < 2) {
  29035. printf("Usage: %s <directory>",argv[0]);
  29036. return -1;
  29037. }
  29038. setupdir(name, argv[1]);
  29039. predfn = (char *) suidfile;
  29040. thenfn = (char *) printfile;
  29041. elsefn = (char *) do_nothing;
  29042. return mapdir(name, predfn, thenfn, elsefn);
  29043. }
  29044.  
  29045.  
  29046. /* End of File */
  29047.  
  29048.  
  29049.  
  29050.  
  29051.  
  29052.  
  29053.  
  29054.  
  29055.  
  29056.  
  29057.  
  29058.  
  29059.  
  29060.  
  29061.  
  29062.  
  29063.  
  29064.  
  29065.  
  29066.  
  29067.  
  29068.  
  29069.  
  29070.  
  29071.  
  29072.  
  29073.  
  29074.  
  29075.  
  29076.  
  29077.  
  29078.  
  29079.  
  29080.  
  29081.  
  29082.  
  29083.  
  29084.  
  29085.  
  29086.  
  29087.  
  29088.  
  29089.  
  29090.  
  29091.  
  29092.  
  29093.  
  29094.  
  29095.  
  29096.  
  29097.  
  29098.  
  29099.  
  29100.  
  29101.  
  29102.  
  29103.  
  29104.  
  29105.  
  29106.  
  29107.  
  29108. A Natural Language Processor
  29109.  
  29110.  
  29111. Russell Suereth
  29112.  
  29113.  
  29114. Russell Suereth has been consulting for over 12 years in the New York City and
  29115. Boston areas. He started coding and designing systems on IBM mainframes and
  29116. now also builds PC software systems. You can write to Russell at 84 Old
  29117. Denville Rd, Boonton, NJ 07005, or call him at (201) 334-0051.
  29118.  
  29119.  
  29120. Hardware and software are sold with CD-ROMs, sound boards, and speech
  29121. synthesizers. These components allow the user and the computer to interact in
  29122. a human manner. This stage of user and computer interaction can be extended
  29123. further with human language. The natural language processor presented in this
  29124. article is a practical application of the use of human language.
  29125.  
  29126.  
  29127. Processing Human Language
  29128.  
  29129.  
  29130. A natural language processor takes a human statement, analyzes it, and
  29131. responds in a way that appears human. But this appearance isn't a mystery. A
  29132. natural language processor performs certain processes based on the words in
  29133. the input sentence. In many ways, processing human language is just another
  29134. data-processing function. The input sentence is the input transaction record.
  29135. The words in the sentence are fields in the input record. The dictionary is a
  29136. master file of word information. The meaning is derived from a combination of
  29137. information in the input sentence, the dictionary, and program code. The
  29138. generated response to the sentence is the output.
  29139. Some simple natural language processors process one-word commands that don't
  29140. require much analysis, such as find, up-date, and delete. This type of
  29141. processor uses a small dictionary to identify the commands. When used with a
  29142. database, commands to such a processor could execute functions to find,
  29143. update, and delete records. A similar processor can be connected to a
  29144. remote-controlled car and used with the commands forward, reverse, and stop.
  29145. Other natural language processors are more sophisticated and can process
  29146. multiple input sentences. A larger dictionary contains information about the
  29147. word such as whether it is a noun, a preposition, or a verb. This complex
  29148. processor can analyze a sentence, identify parts of the sentence, derive
  29149. meaning from the sentence, and generate an appropriate response or action.
  29150. Theoretically, a complex processor may parse through a book and derive
  29151. meanings and themes from the sentences. It may then generate a response based
  29152. on these meanings and themes.
  29153.  
  29154.  
  29155. Building a Processor
  29156.  
  29157.  
  29158. The natural language processor presented here is a miniature version of the
  29159. complex processor just described. It can process several English input
  29160. sentences such as Jim is running in the house, Bill is walking on the street,
  29161. and Sue is going to a store. It can also process sentences such as Where was
  29162. Bill walking? and generate a response that states Bill was walking on the
  29163. street. An actual session with the processor is shown in Figure 1. This
  29164. miniature processor, however, has a limited capability. It can only be used
  29165. with the kinds of input sentences shown in the figure. But this capability can
  29166. be increased by expanding existing routines and adding new ones. Some areas of
  29167. possible expansion will be indicated throughout this article.
  29168. A natural language processor must perform some amount of analysis on the
  29169. sentence and the words in it. The analysis may be simple and only identify
  29170. that the words are valid by matching them with the dictionary. Or, it may be
  29171. complex and identify structures, phrases, meanings, and themes in the
  29172. sentence. The analysis this particular processor performs identifies phrase
  29173. structures and meanings.
  29174. This processor uses transformational grammar to identify underlying phrase
  29175. structures in the input sentence. Each word in the sentence has a type such as
  29176. noun or verb. Specific combinations of these types make a certain phrase
  29177. structure. For example, the words a, the, and these are determiners and are
  29178. located right before a noun. A determiner-noun combination such as the house
  29179. is a kind of noun phrase. Phrase structures must be identified in order to
  29180. identify sentence elements such as subject, action, and place. Figure 2 shows
  29181. some abbreviated phrase structures used in transformational grammar.
  29182. There are a limited number of underlying phrase structures in transformational
  29183. grammar. But there are an unlimited number of possible sentences. The
  29184. relationship of structures to possible sentences is shown with the two
  29185. sentences Jim is running in the house and Sue is going to a store. The
  29186. underlying structure (name-auxiliary-verb-preposition-determiner-noun) is the
  29187. same in these two examples, although the words make the two sentences
  29188. different. The limited number of structures can be coded in the program
  29189. without coding the many combinations of words that may occur.
  29190. Transformational grammar also defines how words are used together or used in a
  29191. certain context. For example, the woman drove the car is correct but the table
  29192. drove the car is not. Words have restrictions to determine how they can be
  29193. used together properly. The inanimate object table cannot be used with the
  29194. action verb drove. Only humans can drive so a HUMAN restriction should be
  29195. assigned to the verb drove. Additional restrictions of MAN, WOMAN, PERSON, and
  29196. TEENAGER should also be assigned. The natural language processor in this
  29197. article uses only one restriction labeled ING for words such as going,
  29198. running, and walking. An expanded natural language processor may use many
  29199. additional restrictions. One of these is verb tense so that Jim was runs in
  29200. the house is recognized as an incorrect sentence.
  29201.  
  29202.  
  29203. Processing the Sentence
  29204.  
  29205.  
  29206. Listing 1 contains the natural language processor. The main routine first
  29207. calls the initialize routine. This routine initializes some variables that are
  29208. used throughout the program. Each entry in the subjects, actions, and places
  29209. arrays is initialized. The first entry contains the subject, action, and place
  29210. of the first input sentence. The second entry contains this information for
  29211. the second input sentence. The arrays have 20 entries and can contain
  29212. information from 20 input sentences. Next, the main routine opens the
  29213. dictionary file named diction. If the dictionary opened successfully, the main
  29214. control flow of the program is entered.
  29215. The main control flow of the program is a while statement that loops once for
  29216. each input sentence. Several processes occur within the while statement for
  29217. each sentence. From a broad view, the program extracts the sentence words and
  29218. matches them with the dictionary, which contains information about the word.
  29219. The program loads this word information into arrays that it analyzes to
  29220. determine the underlying structure of the sentence. The program identifies the
  29221. subject, action, and place words in the sentence and copies these words to
  29222. their arrays, which contain an entry for each input sentence. The program
  29223. determines the appropriate response and then generates that response. When
  29224. there are no more input sentences the program closes the dictionary and then
  29225. ends.
  29226. From a detailed view, the while loop executes until the input sentence is
  29227. null, that is, when only Enter is pressed on the keyboard. First, the program
  29228. calls the reset_sentence routine. This routine initializes variables that are
  29229. used for each input sentence. reset_sentence initializes the word_ct variable,
  29230. which will contain the number of words in the sentence. reset_sentence also
  29231. initializes array entries. These arrays contain an entry for each word in the
  29232. sentence, allowing up to 10 words in an input sentence. The type_array
  29233. contains five additional entries for each word, allowing up to five possible
  29234. types for the word. Examples of types are noun, preposition, and verb. The
  29235. main routine then parses through the input sentence to extract each sentence
  29236. word. For each word in the sentence main calls the get_record routine and
  29237. increments word_ct.
  29238. The get_record routine reads each record in the dictionary and calls the
  29239. match_record routine to determine whether the dictionary word matches the
  29240. sentence word. If they match, then the types variable is incremented to
  29241. accommodate another type for the same word. The dictionary can contain
  29242. multiple types for the same word. The word jump, for instance, can be a noun
  29243. or a verb and would have a dictionary record for each type. When the end of
  29244. the dictionary file has been reached, the types variable is checked to see
  29245. whether the word was found. If the word wasn't found and the first character
  29246. of the word is uppercase, then the word is a name. Names such as Jim and Sue
  29247. aren't kept in the dictionary. The word is then copied to word_array for later
  29248. processing. The routine can be modified to find the word faster by changing
  29249. the dictionary to an indexed file. The dictionary appears in Listing 2. It is
  29250. the same dictionary used in the session shown in Figure 1.
  29251. get_record calls the match_record routine for each record in the dictionary.
  29252. match_record compares the passed sentence word with the word in the current
  29253. dictionary record. match_record extracts the word from the dictionary with the
  29254. extract_word routine, then it matches the extracted dictionary word with the
  29255. passed word. If the match is successful, then the type is extracted from the
  29256. dictionary record and copied to type_array.
  29257. If the type is a verb, then the root is extracted from the dictionary with the
  29258. extract_root routine and copied to root_array. In a group of similar words
  29259. such as run, runs, ran, and running, the word run is the root. Each verb in
  29260. the dictionary has a root. The root will later identify a group of similar
  29261. words that may be used in a generated response sentence. An expanded natural
  29262. language processor that generates many different responses would find the root
  29263. invaluable. For example, given the input sentence Jim is running in the house,
  29264. a generated response may be Why does Jim run in the house? or It appears that
  29265. Jim runs often. The response words run and runs can be identified in the
  29266. dictionary through the common root run.
  29267.  
  29268.  
  29269. The Underlying Structure
  29270.  
  29271.  
  29272. The check_underlying routine identifies underlying phrase structures in the
  29273. input sentence. The code shows two specific underlying structures that can be
  29274. identified. The first underlying structure is a question sentence that starts
  29275. with a WH word. A WH word is a word such as where or what that starts with the
  29276. letters wh. The next word in the input sentence must be an auxiliary which is
  29277. labeled AUX. The next word must be a name, and the last word must be a verb.
  29278. This underlying structure has the types: WH-AUX-NAME-VERB. It can be used for
  29279. many similar sentences such as Where was Bill walking and Where was Sue going.
  29280. The check_underlying routine calls check_type which compares the passed type
  29281. with the possible types in type_array. The type_array variable holds the
  29282. possible types for each input sentence word. If the first input sentence word
  29283. has a WH type, then it matches the structure for the first word. Each word in
  29284. the input sentence is checked to see if it matches the type in the structure.
  29285. If all the input sentence words match, then the sentence has the underlying
  29286. structure WH-AUX-NAME-VERB.
  29287. Once the underlying structure is matched, the correct type is copied to
  29288. prime_types. The prime_types array identifies that the word jump, for example,
  29289. is a noun rather than a verb. Verbs refer to actions and nouns refer to
  29290. places. This type identification will be used later to identify words in the
  29291. sentence that refer to an action or a place.
  29292. Next, the kind of phrase is assigned. Auxiliaries and verbs are assigned to
  29293. verb phrases, determiners and nouns are assigned to noun phrases, and
  29294. prepositions are assigned to prepositional phrases. Phrases are combinations
  29295. of specific, adjacent word types. For instance, a noun phrase has a DET-NOUN
  29296. combination. Phrase identification will be needed later to locate words in the
  29297. sentence that identify a place. For example, a place can be identified by the
  29298. prepositional phrase in the house. In an expanded natural language processor,
  29299. phrase identification would be increased to several processes. What initially
  29300. looks like a noun phrase such as the house may be a prepositional phrase such
  29301. as in the house after additional sentence analysis.
  29302. The second underlying structure that can be identified has the types
  29303. NAME-AUX-VERB-PREP-DET-NOUN. This structure can be used for sentences such as
  29304. Bill is walking in the street and Sue is going to a store. The two coded
  29305. underlying structures will only accept input sentences such as Sue is going to
  29306. a store and Where was Sue going? Other kinds of sentences can be processed
  29307. when other underlying structures are coded.
  29308. Additional underlying structures that can be coded are shown in Figure 2.
  29309. Notice that the two coded structures aren't shown. These two structures were
  29310. created only for explanatory purposes instead of the more lengthy code
  29311. required for all the underlying structures. In an expanded natural language
  29312. processor, the transformational grammar structures would be coded. For
  29313. example, one coded structure would be DET-NOUN to identify a kind of noun
  29314. phrase.
  29315.  
  29316.  
  29317. Identifying Sentence Elements
  29318.  
  29319.  
  29320. The elements of subject, action, and place help convey the meaning in the
  29321. input sentence. Subject identifies who or what the sentence is about, action
  29322. identifies the activity that is performed, and place identifies where the
  29323. activity occurred. In the input sentence Sue is going to a store, the subject
  29324. is Sue, the action is going, and the place is to a store. Without the
  29325. identification of these elements, the sentence would be merely composed of
  29326. meaningless words, types, and phrases.
  29327. Three routines in the processor identify the sentence elements. The
  29328. check_subject routine looks at each word in the input sentence. If the word is
  29329. a name, then check_subject copies the word to the subjects array, which
  29330. contains a subject entry for each input sentence. The check_action routine
  29331. also looks at each word in the input sentence. If the word is a verb, then
  29332. check_action copies the root of the word to the actions array, which contains
  29333. an action entry for each input sentence. The root will be useful when
  29334. expanding the processor. It will allow the processor to determine that Jim ran
  29335. on the street and Jim runs on the track are similar actions. It will also
  29336. allow an appropriate form of run to be used in a response statement. For
  29337. example, Jim ran should be used to describe past tense and Jim runs to
  29338. describe present tense. The check_place routine looks at each word in the
  29339. input sentence too. If the word is in a prepositional phrase, then check_place
  29340. concatenates the word to the places array, which contains a place entry for
  29341. each input sentence. Each word in the prepositional phrase refers to a place
  29342. and will be concatenated to the places array.
  29343.  
  29344. With this information, a simulated understanding of the sentence can be
  29345. derived. The processor does this by matching the subject and action words in
  29346. the current input sentence with information in previous sentences. For
  29347. example, one input sentence can be Jim is running in the house. The processor
  29348. will place Jim in the subjects array, run in the actions array, and in the
  29349. house in the places array. A later input sentence can be a question that asks,
  29350. Where was Jim running? The processor will identify Jim as the subject and run
  29351. as the action. Since this is a question, the processor will also search the
  29352. subjects array and actions array for the words Jim and running. When a match
  29353. is found, the corresponding places array will be used to create a response
  29354. that states, Jim was running in the house. If a person saw only the input
  29355. sentences and responses as shown in Figure 1, then the processor would appear
  29356. to have some degree of understanding. But this is only an appearance. The
  29357. processor generates a canned response of words that are based on a combination
  29358. of input sentence words and information in the arrays.
  29359. An expanded natural language processor can contain code for a number of
  29360. responses. It can also contain the routines check_manner and check_time to
  29361. identify how and when something occurred. These two routines should allow
  29362. prepositional phrases to identify elements of place such as in my house as
  29363. well as elements of manner and time such as in my joy and in the morning.
  29364.  
  29365.  
  29366. Generating a Response
  29367.  
  29368.  
  29369. The response is the output statement from the natural language processor.
  29370. After reading and processing an input sentence, the natural language processor
  29371. must generate an appropriate response in acknowledgement. When a person
  29372. speaks, the listener responds to let the speaker know the words were heard and
  29373. understood. The response may be a simple nod of the head or several sentences
  29374. that explain the listener's understanding. Two considerations that determine
  29375. the kind of response are the listener's knowledge of the spoken information,
  29376. and whether the spoken words were a question or a statement.
  29377. The make_response routine uses these two considerations to generate responses
  29378. to the input sentence. First, it checks to see whether the first word in the
  29379. input sentence is Where. If it is, then the input sentence is a question. The
  29380. second consideration is whether the processor has knowledge of information in
  29381. the input sentence. The processor has this knowledge when the information
  29382. exists in sentence elements from previous sentences. In the question Where was
  29383. Jim running, the subject is Jim, and the action is running. Since it's a
  29384. where-question, it's asking for a location associated with the words Jim, and
  29385. run which is the root of running. The processor keeps information from
  29386. previous sentences in the subjects, actions, and places arrays. The places
  29387. array contains locations. The make_response routine searches the subjects and
  29388. actions arrays for a match with Jim and run. When it finds a match the
  29389. associated entry in the places array will contain the information to generate
  29390. a response that states where Jim is running.
  29391. When the input sentence is a where-question, and make_response does not find
  29392. Jim and run, the processor doesn't have enough information to indicate Jim's
  29393. location. The routine then moves the statement I don't know to the response
  29394. variable. When the input sentence is not a question, it is simply a statement
  29395. of fact. The routine then moves Ok to the response variable. Other kinds of
  29396. responses can be coded and generated. For example, an array of You don't say,
  29397. Please go on, and Tell me more statements can be coded and used as responses.
  29398. When the input sentence is a where-question, and make_response finds Jim and
  29399. run, the make_response routine calls the make_answer routine. make_answer
  29400. creates an answer by placing the associated subject, action, and place words
  29401. together in the response variable.
  29402. The make_answer routine is passed an array index that relates the appropriate
  29403. entries in the subjects and places arrays. First, the routine copies the
  29404. appropriate subject to the response variable giving Jim. It then concatenates
  29405. was to the variable to give Jim was. Next, it calls the get_verb_ing routine
  29406. to retrieve the ING version of the action word. The ING version is a word such
  29407. as running or walking. The ING verb must be used in the response because other
  29408. selections of Jim was runs and Jim was ran are incorrect. The get_verb_ing
  29409. routine reads each record in the dictionary file. It calls the match_verb_ing
  29410. routine to determine whether the record contains the correct ING verb. The
  29411. correct ING verb has an ING restriction. The correct ING verb also has a root
  29412. that matches the action in the input sentence. If match_verb_ing finds the
  29413. correct ING verb, the get_verb_ing routine concatenates it to the response
  29414. giving Jim was running. Finally, the make_answer routine concatenates the
  29415. appropriate place words to the response resulting in Jim was running in the
  29416. house.
  29417.  
  29418.  
  29419. Expanding the Processor
  29420.  
  29421.  
  29422. There are several ways this natural language processor can be expanded. The
  29423. simplest would be to add words to the dictionary. Adding words would enable
  29424. more words to be used in the coded, underlying structures. The number of
  29425. generated responses may increase when words are added to the dictionary. More
  29426. words cause more possible word combinations which allow more possible
  29427. generated responses. The additional words, though, will require word
  29428. restrictions that define how the words can go together properly. When
  29429. expanding the processor in this way, expect to add restrictions to the
  29430. dictionary and to enhance the program code that processes the restrictions.
  29431. Another way to expand the processor is to consider the sentence tense.
  29432. Basically, a sentence refers to something in the past, present, or future.
  29433. Sentence tense affects the words used in the sentence as well as its context
  29434. and meaning. In this miniature processor, the auxiliary that helps define the
  29435. tense is ignored. The generated response is even hard-coded with the auxiliary
  29436. was. The first step in this expansion would be to add a tense restriction in
  29437. the dictionary for auxiliary and verb words. For example, the word is would
  29438. have a present-tense restriction and will a future-tense restriction. Next,
  29439. the code would have to be expanded to accommodate the several kinds of
  29440. auxiliary structures. For example, auxiliaries can occur as have, could have,
  29441. or could have been. An overall auxiliary tense would have to be derived from
  29442. these individual auxiliary words. A verb with the appropriate tense can be
  29443. retrieved from the dictionary after the overall auxiliary tense has been
  29444. determined.
  29445. Expanding this natural language processor will provide additional human
  29446. language capabilities. But not all processors require the same capabilities.
  29447. The capabilities that are needed for a given processor depend, in part, on the
  29448. kinds of input sentences and words that are expected. This natural language
  29449. processor, presented in its current form, doesn't read a book or answer
  29450. questions about the book. But expanding this processor will make that
  29451. capability possible.
  29452. (C) 1993 by Russ Suereth.
  29453. Figure 1 A processor session
  29454. Sentence: Jim is running in the house
  29455. Response: Ok
  29456.  
  29457. Sentence: Sue is going to a store
  29458. Response: Ok
  29459.  
  29460. Sentence: Bill is walking on the street
  29461. Response: Ok
  29462.  
  29463. Sentence: Where was Jim running
  29464. Response: Jim was running in the house
  29465.  
  29466. Sentence: Where was Bill going
  29467. Response: I don't know
  29468.  
  29469. Sentence: Where was Sue going
  29470. Response: Sue was going to a store
  29471. Figure 2 Phrase structures
  29472. Sentence = Noun Phrase + Verb Phrase
  29473. Noun Phrase = Pronoun, or
  29474.  Name, or
  29475.  Noun, or
  29476.  Determiner + Noun
  29477. Verb Phrase = Verb, or
  29478.  Verb + Place, or
  29479.  Verb + Manner, or
  29480.  Verb + Time, or
  29481.  Verb + Reason, or
  29482.  Auxiliary + Verb, or
  29483.  Auxiliary + Verb + Place, or
  29484.  Auxiliary + Verb + Manner, or
  29485.  Auxiliary + Verb + Time, or
  29486.  Auxiliary + Verb + Reason
  29487.  
  29488. The following examples are words that may be
  29489. found in the phrase structures:
  29490.  
  29491.  
  29492. Pronoun = he, she, they, it
  29493. Name = Jim, Florida, Moby Dick
  29494. Common Noun = house, store, street
  29495. Determiner = a, the, that, those
  29496. Verb = run, walk, go, read
  29497. Auxiliary = is, was, have, did, could
  29498. Place = in the house, to a store
  29499. Manner = with ease, quickly
  29500. Time = in the morning, at noon
  29501. Reason = due to the snow, since it rained
  29502.  
  29503. Listing 1 NATURAL.C - A natural language processor
  29504. /* copyright 1993 by Russ Suereth */
  29505. #include <stdlib.h>
  29506. #include <stdio.h>
  29507. #include <string.h>
  29508. #include <ctype.h>
  29509.  
  29510. #define ING 73 /* Restriction for ING word */
  29511.  
  29512. void initialize(void);
  29513. void reset_sentence(void);
  29514. void get_record(char *);
  29515. char *extract_word(void);
  29516. int match_record(char *, int);
  29517. char *extract_root(void);
  29518. void check_underlying(void);
  29519. int check_type(char *,int);
  29520. void check_subject(void);
  29521. void check_action(void);
  29522. void check_place(void);
  29523. void make_response(void);
  29524. void make_answer(int);
  29525. void get_verb_ing(void);
  29526. int match_verb_ing(void);
  29527.  
  29528. FILE *infile;
  29529. char dic_record[80];
  29530. int sentence;
  29531. int word_ct;
  29532. char word_array[10] [15];
  29533. char root_array[10][15];
  29534. char prime_types [10] [11];
  29535. char phrases [10] [11];
  29536. char type_array[10][5][11];
  29537. char subjects [20] [15];
  29538. char actions[20][15];
  29539. char places[20][31];
  29540. char response[80];
  29541.  
  29542. void main()
  29543. {
  29544. char *cur_word;
  29545. char in_sentence[80];
  29546.  
  29547. initialize();
  29548. if ((infile = fopen("diction", "r+")) == NULL) {
  29549. printf ("\nError opening dictionary\n");
  29550. exit(0);
  29551.  
  29552. }
  29553. printf("\nSentence: ");
  29554.  
  29555. while(gets(in_sentence)) {
  29556. if (in_sentence[O] == '\0') break;
  29557. reset_sentence();
  29558.  
  29559. cur_word = strtok(in_sentence, " ");
  29560. while(cur_word != NULL) {
  29561.  
  29562. get_record(cur_word);
  29563. cur_word = strtok(NULL," ");
  29564. if (++word_ct > 9) break;
  29565. }
  29566.  
  29567. check_underlying();
  29568.  
  29569. check_subject();
  29570. check_action();
  29571. check_place();
  29572.  
  29573. make_response();
  29574. printf("Response: %s\n\nSentence: ", response);
  29575.  
  29576. if (++sentence > 19) break;
  29577. } /* end while */
  29578.  
  29579. fclose(infile);
  29580. return;
  29581. }
  29582.  
  29583. /*****************************************************/
  29584. /* Initialize variables (subjects, actions and */
  29585. /* places arrays contain entries for 20 sentences). */
  29586. /*****************************************************/
  29587. void initialize()
  29588. {
  29589. int i;
  29590. for (i=0; i<20; i++) {
  29591. subjects[i][0] = '\0';
  29592. actions [i][0] = '\0';
  29593. places[i][0] = '\0';
  29594. }
  29595. sentence = 0;
  29596. return;
  29597. }
  29598.  
  29599. /****************************************************/
  29600. /* These variables are initialized for each new */
  29601. /* input sentence (each of the 10 word entries for */
  29602. /* the input sentence has 5 type_array entries). */
  29603. /****************************************************/
  29604. void reset_sentence()
  29605. {
  29606. int i,j;
  29607. word_ct = 0;
  29608. for (i=0; i<10; i++) {
  29609. word_array[i] [0] = '\0';
  29610. root_array[i][0] = '\0';
  29611.  
  29612. prime_types[i] [0] = '\0';
  29613. phrases[i][0] = '\0';
  29614. for (j=0; j<5; j++)
  29615. type_array[i][j][0] = '\0';
  29616. }
  29617. return;
  29618. }
  29619.  
  29620. /****************************************************/
  29621. /* Get all the records from the dictionary. If the */
  29622. /* passed word is not in the dictionary, then the */
  29623. /* word could be a name. */
  29624. /****************************************************/
  29625. void get_record(char *pass_word)
  29626. {
  29627. int types = 0;
  29628. rewind (infile);
  29629. fgets(dic_record, 80, infile);
  29630. while (! feof(infile)) {
  29631. if (match_record(pass_word, types) == 0)
  29632. types++;
  29633. fgets(dic_record, 80, infile);
  29634. }
  29635. if (types == 0) {
  29636. if (isupper( (int) pass_word[0]))
  29637. strcpy(type_array[word_ct][types], "NAME");
  29638. else
  29639. strcpy(type_array[word_ct][types],
  29640. "NOTFOUND");
  29641. }
  29642. strcpy(word_array[word_ct], pass_word);
  29643. return;
  29644. }
  29645.  
  29646. /*******************************************************/
  29647. /* Compare the passed word with the word in the */
  29648. /* current dictionary record. If they are the same, */
  29649. /* then extract the type (NOUN, VERB, etc.). If the */
  29650. /* type is a VERB, then also extract the root and */
  29651. /* and copy it to the root array. */
  29652. /*******************************************************/
  29653. int match_record(char *pass_word, int types)
  29654. {
  29655. int i, j;
  29656. char *root;
  29657. char *dic_word;
  29658. dic_word = extract_word();
  29659. /* Check if passed word equals dictionary word */
  29660. if (strcmpi(pass_word, dic_word) != 0) return(1);
  29661.  
  29662. /* Word found, get the type */
  29663. for (i=14,j=0; i<20; i++) {
  29664. if (isspace(dic_record[i])) break;
  29665. type_array[word_ct][types][j++] = dic_record[i];
  29666. }
  29667. /* Trim the type */
  29668. type_array [word_ct][types][j] = '\0';
  29669.  
  29670. if (strcmp(type_array[word_ct][types],
  29671.  
  29672. "VERB") == 0) {
  29673. root = extract_root();
  29674. strcpy(root_array[word_ct], root);
  29675. }
  29676.  
  29677. return(0);
  29678. }
  29679.  
  29680. /******************************************************/
  29681. /* Extract the word from the dictionary. The word is */
  29682. /* 14 characters in length and starts in column 1. */
  29683. /******************************************************/
  29684. char *extract_word()
  29685. {
  29686. int i, j;
  29687. char dic_word[15];
  29688. for (i=0,j=0; i<14; i++) {
  29689. if (isspace(dic_record[i])) break;
  29690. dic_word[j++] = dic_record[i];
  29691. }
  29692. /* Trim the dictionary word */
  29693. dic_word[j] = '\0';
  29694. return(dic_word);
  29695. }
  29696.  
  29697. /******************************************************/
  29698. /* Extract the root from the dictionary. It */
  29699. /* identifies a group of similar words (the root for */
  29700. /* run, ran, runs and running is run). It is 14 */
  29701. /* characters in length and starts in column 35. */
  29702. /******************************************************/
  29703. char *extract_root()
  29704. {
  29705. int i, j;
  29706. char root[15];
  29707. for (i=34,j=0; i<48; i++) {
  29708. if (isspace(dic_record[i])) break;
  29709. root[j++] = dic_record[i];
  29710. }
  29711. /* Trim the root */
  29712. root[j] = '\0';
  29713. return(root);
  29714. }
  29715.  
  29716. /******************************************************/
  29717. /* Determine if the input sentence contains a known, */
  29718. /* underlying structure. If it does, then assign the */
  29719. /* correct types and phrases for the words. */
  29720. /******************************************************/
  29721. void check_underlying()
  29722. {
  29723. int i;
  29724.  
  29725. /* Structure WH-AUX-NAME-VERB */
  29726. i = 0;
  29727. if ( (check_type("WH", i) == 0) &&
  29728. (check_type("AUX", i+1) == 0) &&
  29729. (check_type("NAME", i+2) == 0) &&
  29730. (check_type("VERB", i+3) == 0) ) {
  29731.  
  29732. strcpy(prime_types[i], "WH");
  29733. strcpy(prime_types[i+1], "AUX");
  29734. strcpy(prime_types[i+2], "NAME");
  29735. strcpy(prime_types[i+3], "VERB");
  29736. strcpy(phrases[i], "WHQUESTION");
  29737. strcpy(phrases[i+1], "VERBPHRASE");
  29738. strcpy(phrases[i+2], "NOUNPHRASE");
  29739. strcpy(phrases[i+3], "VERBPHRASE");
  29740. return;
  29741. }
  29742.  
  29743. /* Structure NAME-AUX-VERB-PREP-DET-NOUN */
  29744. if ( (check_type("NAME", i) == 0) &&
  29745. (check_type("AUX", i+1) == 0) &&
  29746. (check_type("VERB", i+2) == 0) &&
  29747. (check_type("PREP", i+3) == 0) &&
  29748. (check_type("DET", i+4) == 0) &&
  29749. (check_type("NOUN", i+5) == 0) ) {
  29750. strcpy(prime_types[i], "NAME");
  29751. strcpy(prime_types [i+1], "AUX");
  29752. strcpy(prime_types [i+2], "VERB");
  29753. strcpy(prime_types[i+3], "PREP");
  29754. strcpy(prime_types[i+4], "DET");
  29755. strcpy(prime_types[i+5], "NOUN");
  29756. strcpy(phrases[i], "NOUNPHRASE");
  29757. strcpy(phrases[i+1], "VERBPHRASE");
  29758. strcpy(phrases[i+2], "VERBPHRASE");
  29759. strcpy(phrases[i+3], "PREPPHRASE");
  29760. strcpy(phrases[i+4], "PREPPHRASE");
  29761. strcpy(phrases[i+5], "PREPPHRASE");
  29762. return;
  29763. }
  29764.  
  29765. return;
  29766. }
  29767.  
  29768. /******************************************************
  29769. /* Compare the passed type with all the types for */
  29770. /* this word in the type_array. If the type is */
  29771. /* found, then return 0. The pass_number parameter */
  29772. /* identifies the word in the input sentence. */
  29773. /*****************************************************/
  29774. int check_type(char *pass_type, int pass_number)
  29775. {
  29776. int i;
  29777. for (i=0; type_array[pass_number][i][0]; i++) {
  29778. if (strcmp(type_array[pass_number][i],
  29779. pass_type) == 0)
  29780. /* Passed type is found in array */
  29781. return(0);
  29782. }
  29783. /* Passed type is not found in array */
  29784. return(1);
  29785. }
  29786.  
  29787. /*****************************************************/
  29788. /* If the correct type is "NAME", then the word */
  29789. /* refers to a subject so copy the word to the */
  29790. /* subjects array. */
  29791.  
  29792. /*****************************************************/
  29793. void check_subject()
  29794. {
  29795. int i;
  29796. for (i=0; i<word_ct; i++) {
  29797. if (strcmp(prime_types[i], "NAME") == 0) {
  29798. strcpy(subjects[sentence], word_array[i]);
  29799. break;
  29800. }
  29801. }
  29802. return;
  29803. }
  29804.  
  29805. /*****************************************************/
  29806. /* If the correct type is "VERB", then the word */
  29807. /* refers to an action so copy the word's root from */
  29808. /* the root array to the actions array. */
  29809. /*****************************************************/
  29810. void check_action()
  29811. {
  29812. int i;
  29813. for (i=0; i<word_ct; i++) {
  29814. if (strcmp(prime_types[i], "VERB") == 0) {
  29815. strcpy(actions[sentence], root_array[i]);
  29816. break;
  29817. }
  29818. }
  29819. return;
  29820. }
  29821.  
  29822. /*****************************************************/
  29823. /* If the phrase is a "PREPPHRASE", then all the */
  29824. /* words in the phrase refer to a place. Concatenate */
  29825. /* these words to the places array. */
  29826. /*****************************************************/
  29827. void check_place()
  29828. {
  29829. int i;
  29830. for (i=0; i<word_ct; i++) {
  29831. if (strcmp(phrases[i], "PREPPHRASE") == 0) {
  29832. strcat(places[sentence], " ");
  29833. strcat(places[sentence], word_array[i]);
  29834. }
  29835. }
  29836. return;
  29837. }
  29838.  
  29839. /****************************************************/
  29840. /* Determine the kind of response to generate. If */
  29841. /* the input sentence is a where-question and the */
  29842. /* subject and action is found in a previous array */
  29843. /* entry, then the response can state the location */
  29844. /* of where the subject and action occured. */
  29845. /****************************************************/
  29846. void make_response()
  29847. {
  29848. int i;
  29849.  
  29850. /* Last input sentence is not a where-question */
  29851.  
  29852. if (strcmpi(word_array[0],"where") != 0) {
  29853. strcpy(response, "Ok");
  29854. return;
  29855. }
  29856.  
  29857. /* Last input sentence is a where-question */
  29858. for (i=sentence-1; i >= 0; i--]) {
  29859. if ( (strcmp(subjects[i],
  29860. subjects[sentence]) == 0) &&
  29861. (strcmp(actions[i],
  29862. actions[sentence]) == 0) &&
  29863. (strlen(places[i]) != 0) ) {
  29864. make_answer(i);
  29865. return;
  29866. }
  29867. }
  29868.  
  29869. /* Not enough information in actions and */
  29870. /* subjects arrays. */
  29871. strcpy(response, "I don't know");
  29872. return;
  29873. }
  29874.  
  29875. /****************************************************/
  29876. /* Generate a response that states the location of */
  29877. /* where the subject and action occured. */
  29878. /****************************************************/
  29879. void make_answer(int prev_sentence)
  29880. {
  29881. strcpy(response, subjects[prev_sentence]);
  29882. strcat(response, " ");
  29883. strcat(response, "was "};
  29884. get_verb_ing();
  29885. strcat(response, places[prev_sentence]);
  29886. return;
  29887. }
  29888.  
  29889. /****************************************************/
  29890. /* Retrieve the ING version of the word from the */
  29891. /* dictionary (the ING version of run is running). */
  29892. /****************************************************/
  29893. void get_verb_ing()
  29894. {
  29895. rewind (infile);
  29896. fgets(dic_record, 80, infile);
  29897. while (! feof(infile)) {
  29898. if (match_verb_ing() == 0) break;
  29899. fgets(dic_record, 80, infile);
  29900. }
  29901. return;
  29902. }
  29903.  
  29904. /******************************************************/
  29905. /* If the root in the current dictionary record */
  29906. /* matches the root in the actions array, and the */
  29907. /* current dictionary record has an ING restriction, */
  29908. /* then extract the dictionary word and return O. */
  29909. /******************************************************/
  29910. int match_verb_ing()
  29911.  
  29912. {
  29913. int i;
  29914. char *root;
  29915. char *dic_word;
  29916.  
  29917. root = extract_root();
  29918. if (strcmp(actions[sentence],root) == 0) {
  29919. /* Root found, look for ING restriction */
  29920. for (i=24; i<33; i++) {
  29921. if (isspace(dic_record[i])) break;
  29922. if (dic_record[i] == ING) {
  29923. dic_word = extract_word();
  29924. strcat(response, dic_word);
  29925. return(0);
  29926. }
  29927. }
  29928. }
  29929. return(1);
  29930. }
  29931.  
  29932. /* End of File */
  29933.  
  29934.  
  29935. Listing 2 DICTION -- the dictionary file
  29936. /* copyright 1993 by Russ Suereth */
  29937. a DET
  29938. the DET
  29939. house NOUN
  29940. street NOUN
  29941. store NOUN
  29942. jump NOUN
  29943. go VERB go
  29944. goes VERB go
  29945. going VERB I go
  29946. went VERB go
  29947. run VERB run
  29948. runs VERB run
  29949. running VERB I run
  29950. ran VERB run
  29951. walk VERB walk
  29952. walks VERB walk
  29953. walking VERB I walk
  29954. walked VERB walk
  29955. jump VERB jump
  29956. jumps VERB jump
  29957. jumping VERB I jump
  29958. jumped VERB jump
  29959. is AUX
  29960. was AUX
  29961. to PREP
  29962. in PREP
  29963. on PREP
  29964. where WH
  29965.  
  29966.  
  29967.  
  29968.  
  29969.  
  29970.  
  29971.  
  29972.  
  29973.  
  29974.  
  29975.  
  29976.  
  29977.  
  29978.  
  29979.  
  29980.  
  29981.  
  29982.  
  29983.  
  29984.  
  29985.  
  29986.  
  29987.  
  29988.  
  29989.  
  29990.  
  29991.  
  29992.  
  29993.  
  29994.  
  29995.  
  29996.  
  29997.  
  29998.  
  29999.  
  30000.  
  30001.  
  30002.  
  30003.  
  30004.  
  30005.  
  30006.  
  30007.  
  30008.  
  30009.  
  30010.  
  30011.  
  30012.  
  30013.  
  30014.  
  30015.  
  30016.  
  30017.  
  30018.  
  30019.  
  30020.  
  30021.  
  30022.  
  30023.  
  30024.  
  30025.  
  30026.  
  30027.  
  30028.  
  30029.  
  30030.  
  30031.  
  30032.  
  30033.  
  30034.  
  30035. Mixed Numbers in C
  30036.  
  30037.  
  30038. P.J. LaBrocca
  30039.  
  30040.  
  30041. P.J. LaBrocca is the author of ReCalc(TM), a set of rational expression
  30042. calculators that never give answers (well, almost never), and run identically
  30043. on PCs, Macintoshes, and Apples. He has a BS and MA in Chemistry and teaches
  30044. computer science at Peter Rouget Middle School 88 in Brooklyn, NY 13782, (607)
  30045. 746-7175.
  30046.  
  30047.  
  30048.  
  30049.  
  30050. Introduction
  30051.  
  30052.  
  30053. This article describes a method of representing and manipulating mixed numbers
  30054. in C. A simple mixed number calculator, MixCalc, exercises the functions. You
  30055. can use MixCalc to help you do your kids' fractions homework.
  30056. The first part presents the package for working with mixed numbers in a C
  30057. program. It describes mixed_t, the data structure used to represent mixed
  30058. numbers, and the functions that do calculations with them. The discussion then
  30059. turns to MixCalc, a rational number calculator (Listing 1 and Listing 2).
  30060. MixCalc accepts infix expressions involving any combination of whole numbers,
  30061. fractions and mixed numbers and returns an answer as either a whole number, a
  30062. fraction, or a mixed number. For example, given this input
  30063. 3 23 / ( 12 + 2 )
  30064. MixCalc returns
  30065. 1 715
  30066. A vertical bar, , is used as the fraction bar to differentiate it from the
  30067. division sign.
  30068. I based the code for MixCalc's parser (Listing 3) on the recursive-descent
  30069. parser from Bjarne Stroustrup's The C++ Programming Language. I modified it to
  30070. work with mixed_ts and added some error recovery.
  30071. A rational number is any number that can be expressed as the quotient of two
  30072. whole numbers. Integers are rational numbers. A proper fraction's value is
  30073. less than one. An improper fraction's value is equal to or greater than one. A
  30074. mixed number is the sum of an integer and a proper fraction. Since a mixed
  30075. number can be expressed as an improper fraction it is a rational number. I use
  30076. the term mixed number loosely to refer to any rational number.
  30077. Most programming languages do not provide built-in support for fractions or
  30078. mixed numbers. You approximate mixed numbers as decimal numbers using
  30079. floating-point types. For most applications this is fine. However, mixed
  30080. numbers have one big advantage over decimal numbers--they are always exact.
  30081. Using decimal numbers, 1 divided by 3 results in the approximation 0.33333...;
  30082. the mixed number result is exactly 13. Of course, there is a down side. In
  30083. certain calculations the integers used for the numerator and denominator can
  30084. overflow. However, in an application using, say, the English measuring system,
  30085. five-eighths looks a lot better as 58 than as 0.625.
  30086.  
  30087.  
  30088. Mixed Number Package
  30089.  
  30090.  
  30091. The data structures and functions for mixed numbers are in Listing 4 - Listing
  30092. 8, fraction.c, mixed.h, error.c, mix_num2.c, and primes.c.
  30093. Since I wanted the intermediate results of calculations available for display
  30094. (for an application not discussed here) I provided storage for each possible
  30095. component of a mixed number. Integer types are used for the whole number part,
  30096. the numerator part, the denominator part and the sign. Fractions must be
  30097. factored into primes in order to reduce them. The factors of the numerator and
  30098. denominator are stored in a two-dimensional array of integers. Including the
  30099. arrays rather than pointers to dynamically-allocated storage simplifies the
  30100. code at the expense of stack size. At compile time, be sure to provide a
  30101. generous stack.
  30102. Include mixed.h in each file that uses the functions, and call init_primes
  30103. once before doing any calculations. init_primes sets up a table of prime
  30104. numbers using the sieve of Eratosthenes. Declare mixed numbers using mixed_t.
  30105. It's a good idea to initialize mixed_ts as soon as possible. You assign values
  30106. by calling mix_init or mix_clear. mix_init accepts a pointer to a mixed_t, and
  30107. three integer values for the whole number, numerator, and denominator parts of
  30108. the mixed number. The sign of the mixed_t is determined by the first integer;
  30109. the others should be positive. mix_clear sets the mixed number to zero. Both
  30110. functions truncate the arrays holding the prime factors of the numerators and
  30111. denominators.
  30112. The typedef Integer controls the size of the int used to store the parts of
  30113. mixed_ts. Use any size as long as it's signed. Naturally, larger ints make
  30114. overflow less likely, but take more room and time. The output function
  30115. mix_print, and any input functions you supply, need adjustment if you change
  30116. the size of Integer. The sign of a mixed_t is stored in an int as +1 or -1. It
  30117. is used in intermediate calculations.
  30118. Notice that a mixed_t with value zero has its whole number and numerator parts
  30119. set to zero, but its denominator part is set to one. A denominator is also a
  30120. divisor, and division by zero is meaningless. Also note that a fraction that
  30121. has not yet been factored has the first elements of its factors array set to
  30122. one.
  30123. The functions for manipulating mixed_ts are in fraction.c. They are used by
  30124. the functions that perform arithmetic operations on mixed numbers, and for
  30125. converting mixed numbers to equivalent forms.
  30126. To reduce fractions to lowest terms you need to know the prime factors of the
  30127. numerator and denominator. These are provided by a call to mix_factor.
  30128. mix_factor takes the address of a mixed number and puts its prime factors into
  30129. the factors array. The whole number part is not considered. mix_factor is
  30130. called by mix_reduce, which returns a pointer to a mixed number with the
  30131. fraction part in lowest terms. mix_reduce loops through the two arrays of
  30132. factors comparing them element by element, and either ignoring them or
  30133. multiplying them into temporary variables. For example, consider reducing the
  30134. fraction 4290. After the call to mix_factor the arrays look like this:
  30135. Numerator 2 3 7 1
  30136. Denominator 2 3 3 5 1
  30137. The pointers top and bottom point to the prime factors under consideration,
  30138. one from the numerator, the other from the denominator. If the elements are
  30139. equal, the factors are common. Advancing the pointers cancels the common
  30140. factors. In the example, the two's, and then the three's are ignored. When the
  30141. factors are unequal the smaller is multiplied into its temporary variable and
  30142. its pointer is advanced. This continues until the sentinel, i.e., an element
  30143. equal to one, is encountered in one or both arrays. Any remaining factors are
  30144. multiplied into the corresponding temporary variable. The rest of the function
  30145. adjusts the whole number part, if needed, and makes sure the mixed number is
  30146. in a consistent state if it started out equal to zero.
  30147. An improper fraction is one in which the numerator is greater than or equal to
  30148. the denominator. In the case of the mixed numbers developed here this means
  30149. that the whole number part must be zero. mix_make_improper converts a mixed_t
  30150. into an improper fraction. This function is called by mix_add and mix_sub. You
  30151. can it call before mix_print to display a mixed_t as an improper fraction.
  30152. mix_print displays a mixed number on standard output in a reasonable form
  30153. within the limits of a text-based environment. Some details are presented
  30154. later.
  30155. mix_num2.c contains the functions for doing arithmetic with mixed numbers. The
  30156. four basic operations are provided, plus negation and calculating the lowest
  30157. common denominator.
  30158. Addition and subtraction are accomplished by converting the mixed numbers to
  30159. improper fractions, bringing them to a common denominator and adding the
  30160. numerators. For multiplication the numerators are multiplied, then the
  30161. denominators. The division function takes the reciprocal of the second mixed
  30162. number and calls the multiplication function. The possibility of taking the
  30163. reciprocal of zero presents a complication. As it stands, mix_recip complains
  30164. if the denominator will become zero. Otherwise it returns the reciprocal.
  30165. mix_recip does not alter its argument. Your program should check if a mixed
  30166. number is zero before dividing by it. The arithmetic functions return
  30167. unreduced results.
  30168. mix_neg reverses the sign of a mixed_t. mix_lcd returns the lowest common
  30169. denominator of two mixed_ts.
  30170. If during calculations the numerator becomes zero, the denominator gets set to
  30171. one. If the value of a mixed number becomes zero the sign is set to positive.
  30172. These actions ensure a consistent starting point for further calculations.
  30173. A greatest_common_divisor function could be used to "pre-reduce" fractions
  30174. during calculations. However, taking out gcds does not guarantee lowest terms.
  30175. To ensure lowest terms mix_reduce still has to be called. There might be some
  30176. practical advantage, say, when adding up a long list of fractions if you avoid
  30177. overflow. However, 61% of the time random number pairs have a gcd equal to
  30178. one. Most of the time calling a gcd function adds overhead but little else.
  30179. Note that mix_add and mix_sub alter the representation, but not the value, of
  30180. their arguments. To preserve the original representation copy it to another
  30181. mixed_t.
  30182.  
  30183.  
  30184. Limits
  30185.  
  30186.  
  30187. Storing numbers in computer memory has limitations. Range and domain errors
  30188. are reported for many calculations involving floating-point types. For integer
  30189. types errors may or may not be reported. If you are lucky bizarre results let
  30190. you know something is amiss. When working with mixed_ts here are some limits
  30191. to keep in mind.
  30192. Whole numbers, numerators and denominators are stored in longs. If the size of
  30193. a calculation's result doesn't fit in a long the result is wrong. You might
  30194. get a run-time error or garbage. The functions do not take any precautions.
  30195. The largest prime number in the prime number table is 15991. Numbers with
  30196. prime factors bigger than that produce all kinds of annoying errors. Some
  30197. examples are given later.
  30198. The factors array holds 40 prime factors, a generous amount. If more need to
  30199. be stored other memory gets over-written with the usual unpredictable results.
  30200. Actually you might consider reducing the size.
  30201. The mixed_ts in this version are big so stack overflow is likely. Increase the
  30202. amount of stack space allocated at compile and link time as needed.
  30203.  
  30204.  
  30205.  
  30206. Scanning a Mixed Number
  30207.  
  30208.  
  30209. MixCalc's parser is based on the one presented in Stroustrup's The C++
  30210. Programming Language. I discuss only those parts I modified for MixCalc.
  30211. The main alteration was to the function than scans the input. For MixCalc the
  30212. scanning function, gettok (Listing 9), has to recognize mixed numbers in their
  30213. varying guises. gettok reads the standard input, breaks it up into tokens,
  30214. returns the tokens to the parser and constructs a mixed_t. If it detects an
  30215. unrecognized token it issues a diagnostic to standard error and resets MixCalc
  30216. via a call to longjmp.
  30217. The setjmp/longjmp combination provides a good method of error recovery for a
  30218. calculator program where an error in the input stream makes the rest of the
  30219. expression meaningless. A variable of type jmp_buf is defined and a call is
  30220. made to setjmp before entering the main loop. If longjmp is called later on
  30221. the environment is reset to the way it was when setjmp was called. An error in
  30222. an expression causes the rest of the current line to be discarded. MixCalc
  30223. resumes on the next line.
  30224. Scanning a mixed number presents a problem similar to scanning a
  30225. floating-point number. Valid input can come in several forms. An acceptable
  30226. mixed number may consist of a whole number, a fraction, or a whole number
  30227. followed by a fraction. An optional sign may precede any of these. An irksome
  30228. part of scanning a mixed number is that it is natural to have space embedded
  30229. in it. gettok tackles these problems.
  30230. Positive and negative signs are treated as operators, not as part of the mixed
  30231. number. They are passed to the parser.
  30232. When gettok encounters a digit it pulls in an integer using scanf. Then white
  30233. space is skipped. The next character can be a digit, a fraction bar, or some
  30234. other character (an operator or an unknown character). If the next character
  30235. is a digit then the scanner expects an integer, optional white space, a
  30236. fraction bar, optional white space, and another integer. Otherwise an error
  30237. message is displayed and MixCalc resets itself. If the next character is a
  30238. fraction bar then an integer preceded by optional white space must follow.
  30239. Otherwise an error as above is generated. For each of the three possible
  30240. successful scans gettok constructs a mixed_t and returns the token NUMBER.
  30241. Note that the fraction bar is not an operator in MixCalc, rather it is a
  30242. delimiter.
  30243.  
  30244.  
  30245. Compiling MixCalc
  30246.  
  30247.  
  30248. MixCalc compiles as presented here with Microsoft C 6.00 or C/C++ 7.0, and
  30249. Zortech C/C++ 3.0. A makefile (Listing 10) is provided for building MixCalc
  30250. using Microsoft and Zortech. It's set to use the Microsoft compilers as
  30251. shipped. To switch to Zortech move the # on the lines referring to the CC
  30252. macro and the linker. The file works with Microsoft's NMAKE and Zortech's MAKE
  30253. utilities. I also use this makefile to backup source files and update
  30254. hardcopy. For example, make backup copies those files that changed since the
  30255. last backup to a floppy on drive a:. To use backup and hcopy with Zortech's
  30256. MAKE add exclamation points before the commands.
  30257. For Microsoft's PWB create a project called mixcalc. Add the seven C files to
  30258. the project. Then choose build from the project menu. The PWB does not use the
  30259. makefile provided with MixCalc.
  30260. I like to compile everything with the large model, but any model should work.
  30261. For more information on using MixCalc see the sidebar "How to Use MixCalc."
  30262.  How to Use MixCalc
  30263. MixCalc is a rational number calculator. It accepts expressions consisting of
  30264. any combination of whole numbers, fractions, and mixed numbers and returns a
  30265. result in the appropriate reduced form. +,-, * and / perform addition,
  30266. subtraction, multiplication, and division, respectively. Parentheses change
  30267. the order of operations and nest arbitrarily deep. Several expressions,
  30268. separated by semicolons, may appear on one line. A carriage return ends a
  30269. line. MixCalc accepts input from the command line or from a redirected file.
  30270. An error in the input flushes the current line and resets MixCalc. Start
  30271. MixCalc by typing mixcalc at the DOS prompt. To do a series of calculations
  30272. from a text file called examples, type
  30273. mixcalc < examples
  30274. To save the results of your calculations to a file called results, type
  30275. mixcalc < examples > results
  30276. An error causes a message to be displayed and halts the redirected session.
  30277. Several lines of a MixCalc session with some comments follow.
  30278. White space is optional except to separate the whole number part from the
  30279. fraction part of a mixed number.
  30280. > 12*12
  30281. 14
  30282. >12 * 12
  30283. 14
  30284. > 2 23+1 56
  30285. 4 12
  30286. Parentheses work just as expected.
  30287. > 5 79 + 11 23 * 3 12
  30288. 46 1118
  30289. > (5 79 + 11 23) * 3 2
  30290. 61 118
  30291. The fraction bar is not an operator; it has meaning only within a fraction.
  30292. > (1/2)3
  30293. Unknown token: 
  30294. > 1/23
  30295. 1 12
  30296. This is acceptable to MixCalc, but might not be what was intended. The above
  30297. example is "one divided by two-thirds," NOT "one divided by two, divided by
  30298. 3." One-half over three equals one-sixth:
  30299. > (1/2)/3
  30300. 16
  30301. End a MixCalc session by hitting control-Z (or control-C). All of the limits
  30302. of the mixed number package apply to MixCalc. Numbers near the largest prime
  30303. available to MixCalc are used in some examples to force errors. Here's how two
  30304. compilers dealt with them.
  30305.  
  30306.  
  30307. Microsoft C/C++ 7.0
  30308.  
  30309.  
  30310. > 115991 + 115991
  30311. 215991
  30312. > 116000 + 115991
  30313. - integer divide by 0
  30314. > 116001 + 115991
  30315. - integer divide by 0
  30316.  
  30317.  
  30318. Zortech C/C++ 3.0
  30319.  
  30320.  
  30321.  
  30322. > 115991 + 115991
  30323. 215991
  30324. > 116000 + 115991
  30325. 1
  30326. > 116001 + 115991
  30327. 721426102 1879503890-2113923584
  30328. In the first example everything stays within bounds. The numerator becomes
  30329. 2x15,991 and the denominator 1,5991x15,991. Both fit in longs and have prime
  30330. factors less than or equal to 15,991. The second example produces the
  30331. numerator 31,991, which is a prime bigger than the biggest available to
  30332. MixCalc. Note the denominator, 255,856,000, fits in a long and factors nicely.
  30333.  
  30334. Listing 1 mixcalc.h - header for MixCalc
  30335. /* mixcalc.h */
  30336. /* Adapted from The C++ Programming Language by Bjarne
  30337. Stroustrup Modified by P.J. LaBrocca */
  30338.  
  30339. #ifndef MIXCALC_H
  30340. #define MIXCALC_H
  30341.  
  30342. #include "mixed.h"
  30343. #include <setjmp.h>
  30344.  
  30345. enum token_value {
  30346. NAME = 1, NUMBER, END, ENDFILE = END,
  30347. PLUS = '+', MINUS = '-',MUL = '*', DIV = '/',
  30348. PRINT = ';',ASSIGN = '=', LP = '(', RP = ')'
  30349. };
  30350. extern enum token_value curr_tok;
  30351.  
  30352. extern mixed_t *M;
  30353.  
  30354. //extern mixed_t number_value; //parser.c
  30355.  
  30356. extern jmp_buf startup;
  30357.  
  30358. //function prototypes
  30359. mixed_t expr(void);
  30360. mixed_t term(void);
  30361. mixed_t prim(void);
  30362.  
  30363. enum token_value get_token(void);
  30364.  
  30365. #endif
  30366. /* End of File */
  30367.  
  30368.  
  30369. Listing 2 mixcalc.c - a rational number calculator
  30370. /* mixcalc.c */
  30371. /* Copyright 1992 by P.J. LaBrocca */
  30372.  
  30373. #include "mixed.h"
  30374. #include "mixcalc.h"
  30375. #include <stdio.h>
  30376.  
  30377. #ifdef __ZTC__ /* For Zortech Compiler */
  30378. _stack = 15000;
  30379. #endif
  30380.  
  30381. enum token_value curr_tok;
  30382.  
  30383.  
  30384. void main()
  30385. {
  30386. mixed_t ans;
  30387. mixed_t work;
  30388.  
  30389. init_primes();
  30390.  
  30391. mix_clear( &ans );
  30392. mix_init( &work, 3, 1, 7 );
  30393.  
  30394. setjmp( startup );
  30395. M = &work;
  30396. while(1) {
  30397. printf("> ");
  30398. curr_tok = gettok();
  30399. if(curr_tok == END) {
  30400. break;
  30401. }
  30402. if(curr_tok == PRINT)
  30403. continue;
  30404. ans = expr( );
  30405.  
  30406. mix_print( mix_reduce( &ans) );
  30407. }
  30408. }
  30409.  
  30410. /* End of File */
  30411.  
  30412.  
  30413. Listing 3 parser.c - parser For mixcalc.c
  30414. /* parser.c */
  30415. /* Adapted from The C++ Programming Language by Bjarne Stroustrup
  30416. Modified by P.J. LaBrocca
  30417. */
  30418.  
  30419. #include "mixed.h"
  30420. #include "mixcalc.h"
  30421. #include <stdio.h>
  30422.  
  30423. mixed_t number_value;
  30424.  
  30425. mixed_t expr(void)
  30426. {
  30427. mixed_t left = term();
  30428. mixed_t tmp;
  30429.  
  30430. for(;;)
  30431. switch(curr_tok) {
  30432. case PLUS:
  30433. curr_tok = gettok();
  30434. tmp = term();
  30435. left = mix_add( &left, &tmp );
  30436. break;
  30437. case MINUS:
  30438. curr_tok = gettok();
  30439. tmp = term();
  30440. left = mix_sub( &left, &tmp );
  30441. break;
  30442. default:
  30443.  
  30444. return left;
  30445. }
  30446. }
  30447.  
  30448. mixed_t term()
  30449. {
  30450. mixed_t d;
  30451. mixed_t left = prim();
  30452.  
  30453. for(;;)
  30454. switch(curr_tok) {
  30455. case MUL:
  30456. curr_tok = gettok();
  30457. d = prim();
  30458. left = mix_mul( &left, &d );
  30459. break;
  30460. case DIV:
  30461. curr_tok = gettok();
  30462. d = prim();
  30463. if( (d.whole == 0) && (d.num == 0) ) {
  30464. fprintf( stderr, "Division by 0\n");
  30465. fflush( stdin );
  30466. longjmp( startup, 1 );
  30467. }
  30468. left = mix_divide( &left, &d );
  30469. break;
  30470. default:
  30471. return left;
  30472. }
  30473. }
  30474.  
  30475. mixed_t prim()
  30476. {
  30477. struct name *n;
  30478. mixed_t e;
  30479.  
  30480. switch(curr_tok) {
  30481. case NUMBER:
  30482. number_value = *M;
  30483. curr_tok = gettok();
  30484. return number_value;
  30485.  
  30486. case MINUS:
  30487. curr_tok = gettok();
  30488. e = prim();
  30489. mix_neg( &e );
  30490. return e;
  30491. case PLUS:
  30492. curr_tok = gettok();
  30493. return prim();
  30494.  
  30495. case LP:
  30496. curr_tok = gettok();
  30497. e = expr();
  30498. if(curr_tok != RP) {
  30499. fprintf( stderr, "')' expected\n");
  30500. fflush( stdin );
  30501. longjmp( startup, 1 );
  30502. }
  30503.  
  30504. curr_tok = gettok();
  30505. return e;
  30506. case END:
  30507. return * mix_init( &e, 1, 0, 1 );
  30508. default:
  30509. fprintf( stderr, "primary expected, found %c\n", curr_tok);
  30510. fflush( stdin );
  30511. longjmp( startup, 1 );
  30512. }
  30513. }
  30514.  
  30515. /* End of File */
  30516.  
  30517.  
  30518. Listing 4 fraction.c - functions for manipulating mixed_ts
  30519. /* fraction.c */
  30520. /* Copyright 1992 by P.J. LaBrocca */
  30521.  
  30522. #include <stdio.h>
  30523. #include "mixed.h"
  30524.  
  30525. mixed_t *mix_init( mixed_t *m, Integer w, Integer n, Integer d )
  30526. {
  30527. m->sign = POSITIVE;
  30528. m->whole = w;
  30529. if( w < 0) {
  30530. m->sign = NEGATIVE;
  30531. m->whole *= -1;
  30532. }
  30533. m->num = n;
  30534. m->den = d;
  30535. m->factors[ NUMER ][ 0 ] = 1;
  30536. m->factors[ DENOM ][ 0 ] = 1;
  30537. return m;
  30538. }
  30539.  
  30540. mixed_t *mix_clear( mixed_t *m )
  30541. {
  30542. m->whole = 0;
  30543. m->num = 0;
  30544. m->den = 1;
  30545. m->factors [ NUMER ][ 0 ] = 1;
  30546. m->factors [ DENOM ][ 0 ] = 1;
  30547. m->sign = POSITIVE;
  30548. return m;
  30549. }
  30550.  
  30551. mixed_t *mix_factor( mixed_t *m )
  30552. {
  30553. Integer n;
  30554. int i;
  30555. Integer *pi;
  30556. Integer *pp;
  30557.  
  30558. for( i = 0; i < 2; ++i) {
  30559. pp = Primes; /* point to global array of primes */
  30560. (i != 0) ? (n = m->den) : (n = m->num);
  30561. pi = &m->factors[i][0];
  30562.  
  30563.  
  30564. while(n > 1) {
  30565. if( !(n % *pp) ) { /* if there is no remainder */
  30566. n = (Integer) (n / *pp); /* factor the prime out of number */
  30567. *pi = *pp; /* save the prime */
  30568. ++pi;
  30569. continue; /* try the prime again */
  30570. }
  30571. ++pp; /* next prime */
  30572. }
  30573. *pi = 1;
  30574. pp = Primes;
  30575. }
  30576. return m;
  30577. }
  30578.  
  30579. mixed_t *mix_reduce( mixed_t *m )
  30580. {
  30581. Integer tnum = 1, tden = 1;
  30582. Integer *top = &m->factors[NUMER][0];
  30583. Integer *bot = &m->factors[DENOM][0];
  30584.  
  30585. if( m->num == 0) {
  30586. return m;
  30587. }
  30588. if( m->den == 1 ) {
  30589. m->whole += m->num;
  30590. m->num = 0;
  30591. return m;
  30592. }
  30593. mix_factor( m ); /* got to factor to reduce */
  30594. /*accumulators for reduced numerator & denominator*/
  30595. while(*top != 1 && *bot != 1) { /* neither factor is sentinel */
  30596. if(*top == *bot) { /* if the current factors are equal..*/
  30597. ++top; /* ..cancel them & continue */
  30598. ++bot;
  30599. continue;
  30600. } /* otherwise accumulate the smaller*/
  30601. (*top < *bot) ? (tnum *= *top++) : (tden *= *bot++);
  30602.  
  30603. }
  30604. while(*top != 1) /* any remaining factors are */
  30605. tnum *= *top++; /* multiplied in */
  30606. while(*bot != 1)
  30607. tden *= *bot++;
  30608. if(tnum == tden) { /*ie, n/d == 1*/
  30609. ++m->whole; /*add 1 to whole*/
  30610. m->num = 0;
  30611. m->den = 1;
  30612. }
  30613. else if(tnum > tden) { /*improper fraction*/
  30614. m->whole += (Integer) (tnum / tden);
  30615. m->num = tnum % tden;
  30616. m->den = tden;
  30617. }
  30618. else { /*proper fraction*/
  30619. m->num == tnum;
  30620. m->den = tden;
  30621. }
  30622. if(m->num == 0) { /* keep zero-valued fractions*/
  30623.  
  30624. m->den = 1; /* in consistent state*/
  30625. if(m->whole == 0)
  30626. mix_clear( m );
  30627. }
  30628. return m;
  30629. }
  30630.  
  30631. void mix_make_improper( mixed_t *m ) /* converts invoking instance*/
  30632. { /* into an improper fraction*/
  30633. m->num += m->whole * m->den; /* if possible*/
  30634. m->whole = 0;
  30635. }
  30636.  
  30637. /* If sizeof( Integer ) changes
  30638. change %ld
  30639. */
  30640. void mix_print( mixed_t *m )
  30641. {
  30642. printf("\t");
  30643. if( m->sign == -1 )
  30644. printf("-");
  30645. if( m->whole != 0 )
  30646. printf("%ld", m->whole);
  30647. if( m->num != 0 )
  30648. printf(" %ld%ld",m->num, m->den);
  30649. if( (m->whole == 0) && (m->num == 0) )
  30650. printf("0");
  30651. printf("\n");
  30652. }
  30653.  
  30654. /* End of File */
  30655.  
  30656.  
  30657. Listing 5 mixed.h
  30658. /* mixed.h */
  30659. /* Copyright 1992 by P.J. LaBrocca */
  30660.  
  30661. #ifndef MIXED_H
  30662. #define MIXED_H
  30663.  
  30664. typedef long Integer;
  30665.  
  30666. #define MAXFACTOR 40 /* maximum number of factors */
  30667. #define NUMER 0 /* index for numerator */
  30668. #define DENOM 1 /* index for denominator */
  30669. #define POSITIVE 1
  30670. #define NEGATIVE -1
  30671.  
  30672. typedef struct {
  30673. Integer whole;
  30674. Integer num;
  30675. Integer den;
  30676. int sign;
  30677. Integer factors[2][MAXFACTOR];
  30678. } mixed_t;
  30679.  
  30680. extern Integer Primes[]; /* space for prime numbers */
  30681.  
  30682. mixed_t mix_error(char *s);
  30683.  
  30684.  
  30685. void init_primes( void );
  30686.  
  30687. mixed_t *mix_init( mixed_t *m, Integer w, Integer n, Integer d );
  30688. mixed_t *mix_clear( mixed_t *m );
  30689. mixed_t *mix_factor( mixed_t *m );
  30690. mixed_t *mix_reduce( mixed_t *m );
  30691. void mix_make_improper( mixed_t *m );
  30692. void mix_print( mixed_t *m );
  30693.  
  30694. mixed_t mix_sub(mixed_t *x, mixed_t *y);
  30695. mixed_t mix_add(mixed_t *x, mixed_t *y);
  30696. mixed_t mix_mul(mixed_t *x, mixed_t *y);
  30697. mixed_t mix_recip(mixed_t *f); -
  30698. mixed_t mix_divide(mixed_t *f, mixed_t *g);
  30699. Integer lcd(mixed_t *f, mixed_t *g);
  30700. void mix_neg(mixed_t *f);
  30701.  
  30702. #endif
  30703.  
  30704. /* End of File */
  30705.  
  30706.  
  30707. Listing 6 error.c
  30708. /* error.c */
  30709. /* Copyright 1992 by P.J. LaBrocca */
  30710.  
  30711. #include "mixed.h"
  30712. #include <stdio.h>
  30713.  
  30714. mixed_t mix_error(char *s)
  30715. {
  30716. mixed_t t;
  30717.  
  30718. fprintf(stderr, "Error: %s\n", s);
  30719. return *mix_init( &t, 1, 0, 1 );
  30720. }
  30721.  
  30722. /* End of File */
  30723.  
  30724.  
  30725. Listing 7 mix_num2.c -- functions for doing arithmetic with mixed numbers
  30726. /* mix_num2.c */
  30727. /* Copyright 1992 by P.J. LaBrocca */
  30728.  
  30729. #include "mixed.h"
  30730. #include <stdlib.h>
  30731.  
  30732. mixed_t mix_sub(mixed_t *x, mixed_t *y)
  30733. {
  30734. mixed_t sum, xt, yt;
  30735.  
  30736. mix_clear( &sum );
  30737. mix_make_improper(x);
  30738. mix_make_improper(y);
  30739.  
  30740. xt.num = x->num * y->den;
  30741. xt.den = x->den * y->den;
  30742. yt.num = y->num * x->den;
  30743.  
  30744. yt.den = y->den * x->den;
  30745.  
  30746. sum.num = x->sign * xt.num - y->sign * yt.num;
  30747.  
  30748. if(sum.num < 0) {
  30749. sum.num = labs(sum.num);
  30750. sum.sign = NEGATIVE;
  30751. }
  30752. sum.den = xt.den; /*xt.den == yt.den at this point*/
  30753. if(sum.num == 0) {
  30754. sum.den = 1;
  30755. if(sum.whole == 0)
  30756. sum.sign = POSITIVE;
  30757. }
  30758. return sum;
  30759. }
  30760.  
  30761. mixed_t mix_add(mixed_t *x, mixed_t *y)
  30762. {
  30763. mixed_t sum, t, xt, yt;
  30764.  
  30765. mix_clear( &sum );
  30766.  
  30767. mix_make_improper(x);
  30768. mix_make_improper(y);
  30769.  
  30770. xt.num = x->num * y->den;
  30771. xt.den = x->den * y->den;
  30772. yt.num = y->num * x->den;
  30773. yt.den = y->den * x->den;
  30774.  
  30775. sum.num = x->sign * xt.num + y->sign * yt.num;
  30776.  
  30777. if(sum.num < 0) {
  30778. sum.num = labs(sum.num);
  30779. sum.sign = NEGATIVE;
  30780. }
  30781. sum.den = xt.den; /*xt.den == yt.den at this point*/
  30782.  
  30783. if(sum.num == 0) {
  30784. sum.den = 1;
  30785. if(sum.whole == 0)
  30786. sum.sign = POSITIVE;
  30787. }
  30788. return sum;
  30789. }
  30790.  
  30791. mixed_t mix_mul(mixed_t *x, mixed_t *y)
  30792. {
  30793. mixed_t product;
  30794. Integer xn, yn;
  30795.  
  30796. mix_clear( &product );
  30797.  
  30798. xn = x->sign * (x->whole * x->den + x->num);
  30799. yn = y->sign * (y->whole * y->den + y->num);
  30800.  
  30801. product.num = xn * yn;
  30802. product.den = x->den * y->den;
  30803.  
  30804. if(product.num < 0) {
  30805. product.num = labs(product.num);
  30806. product.sign = NEGATIVE;
  30807. }
  30808. if(product.num == 0) {
  30809. product.den = 1;
  30810. if(product.whole == 0)
  30811. product.sign = POSITIVE;
  30812. }
  30813. return product;
  30814. }
  30815.  
  30816. mixed_t mix_recip(mixed_t f) /*reciprocal*/
  30817. { /* does not alter f*/
  30818. Integer tmp;
  30819.  
  30820. mix_make_improper( &f );
  30821. if(f.num == 0) {
  30822. mix_error("denominator will become zero");
  30823. return f;
  30824. }
  30825.  
  30826. tmp= f.num;
  30827. f.num = f.den;
  30828. f.den = tmp;
  30829. return f;
  30830. }
  30831.  
  30832. mixed_t mix_divide(mixed_t *f, mixed_t *g)
  30833. {
  30834. mixed_t rec = mix_recip( *g );
  30835.  
  30836. return mix_mul( f, &rec );
  30837. }
  30838.  
  30839. Integer mix_lcd(mixed_t *f, mixed_t *g)
  30840. {
  30841. int i = 0, j = 0;
  30842. Integer low[30];
  30843. Integer *l = low;
  30844. Integer t;
  30845.  
  30846. mix_factor(g);
  30847. mix_factor(f);
  30848. white(1) {
  30849. if(f->factors[1][i] == 1) {
  30850. while(g->factors[1][j] != 1)
  30851. *l++ = g->factors[1][j]++];
  30852. break;
  30853. }
  30854. else if(g->factors[1][j] == 1) {
  30855. while(f->factors[1][i] != 1)
  30856. *l++ = f->factors[1][i++];
  30857. break;
  30858. }
  30859. else if(f->factors[1][i] == g->factors[1][j]) {
  30860. *l++ = f->factors[1][i];
  30861. ++i;
  30862. ++j;
  30863.  
  30864. }
  30865. else if(f->factors[1][i] > g->factors[1][j]) {
  30866. *l++ = g->factors[1][j];
  30867. ++j;
  30868. }
  30869. else if(f->factors[1][i] < g->factors[1][j]) {
  30870. *1++ = f->factors[1][i];
  30871. ++i;
  30872. }
  30873. }
  30874. *l = 1;
  30875. t = 1;
  30876. i = 0;
  30877.  
  30878. while(low[i] !=1)
  30879. t *= low[i++];
  30880. Return t;
  30881. }
  30882.  
  30883. void mix_neg(mixed_t *f)
  30884. {
  30885. if(f->sign == NEGATIVE)
  30886. f->sign = POSITIVE;
  30887. else
  30888. f->sign = NEGATIVE;
  30889. }
  30890. /* End of File */
  30891.  
  30892.  
  30893. Listing 8 primes.c -- function to set up table of prime numbers
  30894. /* primes.c */
  30895. /* Copyright 1992 by P.J. LaBrocca */
  30896.  
  30897. #include <stdlib.h>
  30898. #include "mixed.h"
  30899.  
  30900. #define NUM 16000 /* highest number to check for being prime */
  30901.  
  30902. Integer Primes[ 2000 ];
  30903.  
  30904. void init_primes( void )
  30905. {
  30906. char *mark = malloc(NUM * sizeof(char) );
  30907. Integer *pr = Primes; /* point to global array */
  30908. Integer j, k;
  30909.  
  30910. for(j = 0; j < NUM; ++j)
  30911. mark[j] = 1; /* mark everything prime */
  30912. for(j = 4; j < NUM; j += 2)
  30913. mark[j] = 0; /* scratch off all the even numbers ... */
  30914. *pr++ = 2; /* ... except for 2; put it in primes array */
  30915. for(j = 3; j < NUM; j += 2) /* check each odd number: */
  30916. if(mark[j]) { /* if it's marked... */
  30917. *pr++ = j; /* ..record it in array.. */
  30918. for(k = j + j; k < NUM; k += j)
  30919. mark[k] = 0; /* ..and scratch off all its multiples */
  30920. }
  30921. free( mark );
  30922. }
  30923.  
  30924. #undef NUM
  30925. /* End of File */
  30926.  
  30927.  
  30928. Listing 9 gettok.c - scanning function that must recognize mixed numbers in
  30929. various forms
  30930. /* gettok.c */
  30931. /* Copyright 1992 by P.J. LaBrocca */
  30932.  
  30933. #include <stdio.h>
  30934. #include <ctype.h>
  30935. #include "mixed.h"
  30936. #include "mixcalc.h"
  30937.  
  30938. #define LookAhead(x) while( (x = getchar()) == '' x == '\t')
  30939.  
  30940. jmp_buf startup;
  30941.  
  30942. mixed_t *M;
  30943. enum token_value CurrentToken;
  30944.  
  30945. enum token_value gettok( void ) {
  30946. int c;
  30947. Integer tmp1, tmp2, tmp3;
  30948.  
  30949. LookAhead( c );
  30950. if( c == EOF )
  30951. return CurrentToken = ENDFILE;
  30952. if( isdigit(c) ) { 
  30953. ungetc( c, stdin );
  30954. scanf("%ld", &tmp1);
  30955. LookAhead(c);
  30956. if( isdigit(c) ) {
  30957. ungetc( c, stdin );
  30958. scanf("%ld", &tmp2);
  30959. LookAhead(c);
  30960. if(c != '') {
  30961. fprintf( stderr, "Expected ''\n");
  30962. fflush( stdin );
  30963. longjmp( startup, 1 );
  30964. }
  30965. LookAhead(c);
  30966. if( !isdigit(c) ) {
  30967. fprintf( stderr, "Expected a digit\n");
  30968. fflush( stdin );
  30969. longjmp( startup, 1 );
  30970. }
  30971. ungetc( c, stdin );
  30972. scanf("%ld", &tmp3 );
  30973. if( tmp3 == 0 ) {
  30974. fprintf( stderr, "Denominator cannot be zero\n");
  30975. fflush( stdin );
  30976. longjmp( startup, 1 );
  30977. }
  30978. mix_init( M, tmp1, tmp2, tmp3 );
  30979. return CurrentToken = NUMBER;
  30980. }
  30981. if( c == ''){
  30982. LookAhead(c);
  30983. if( !isdigit(c) ) {
  30984.  
  30985. fprintf( stderr, "Expected a digit\n");
  30986. fflush( stdin );
  30987. longjmp( startup, 1 );
  30988. }
  30989. ungetc( c, stdin );
  30990. scanf("%ld", &tmp2 );
  30991. if( tmp2 == 0 ) {
  30992. fprintf( stderr, "Denominator cannot be zero\n");
  30993. fflush( stdin );
  30994. longjmp( startup, 1 );
  30995. }
  30996. mix_init( M, 0, tmp1, tmp2 );
  30997. return CurrentToken = NUMBER;
  30998. }
  30999. ungetc( c, stdin );
  31000. mix_init( M, tmp1, 0, 1, );
  31001.  
  31002. return CurrentToken = NUMBER;
  31003. }
  31004. if( c == '\n' c == ';')
  31005. return CurrentToken = PRINT;
  31006. switch ( c ) {
  31007. case '*':
  31008. case '+':
  31009. case '-':
  31010. case '/':
  31011. case '(':
  31012. case ')':
  31013. return CurrentToken = c;
  31014. default:
  31015. fprintf( stderr, "Unknown token: %c\n", c);
  31016. fflush( stdin);
  31017. longjmp( startup, 1 );
  31018. }
  31019. }
  31020.  
  31021. /* End of File */
  31022.  
  31023.  
  31024. Listing 10 A makefile
  31025. #makefile for mixcalc.exe
  31026. #CC =ztc -c -ml
  31027. CC = cl /c /AL
  31028.  
  31029. .c.obj:
  31030. $(CC) $*.c
  31031.  
  31032. all :mixcal.exe
  31033.  
  31034. mixcalc.exe :mixcalc.obj fraction.obj parser.obj mix_num2.obj primes.obj
  31035. gettok.obj error.obj
  31036. # blink mixcalc+fraction+parser+mix_num2+primes+gettok+error,,, /NOI
  31037. link mixcalc+fraction+parser+mix_num2+primes+gettok+error,,, /NOI /ST:15000 ;
  31038.  
  31039. mixcalc.obj : mixed.h mixcalc.h mixcalc.c
  31040. fraction.obj : mixed.h fraction.c
  31041. mix_num2.obj : mixed.h mix.num2.c
  31042. primes.obj : mixed.h primes.c
  31043. error.obj : mixed.h error.c
  31044. gettok.obj : mixed.h mixcalc.h gettok.c
  31045.  
  31046. parser.obj : mixed.h mixcalc.h parser.c
  31047.  
  31048. backup: backup1 backup2 backup3 backup4
  31049.  
  31050. backup1: mixcalc.c fraction.c gettok.c parser.c
  31051. cp -m $? a:\mixb
  31052. touch backup1
  31053.  
  31054. backup2: mix_num2.c primes.c error.c
  31055. cp -m $? a:\mixb
  31056. touch backup2
  31057.  
  31058. backup3: makefile mixed.h mixcalc.h #mixctxt
  31059. cp -m $? a:/mixb
  31060. touch backup3
  31061.  
  31062. backup4:
  31063. cp -m mixcalc.exe a:\mixb
  31064. touch backup4
  31065.  
  31066. hcopy: hcopy1 hcopy2
  31067.  
  31068. hcopy1: mixcalc.c fraction.c gettok.c parser.c mixcalc.h
  31069. pr -W -e4 -o5 $? > prn
  31070. touch hcopy1
  31071.  
  31072. hcopy2: mix_num2.c primes.c error.c makefile mixed.h #mixctxt
  31073. pr -W -e4 -o5 $? > prn
  31074. touch hcopy2
  31075.  
  31076.  
  31077.  
  31078.  
  31079.  
  31080.  
  31081.  
  31082.  
  31083.  
  31084.  
  31085.  
  31086.  
  31087.  
  31088.  
  31089.  
  31090.  
  31091.  
  31092.  
  31093.  
  31094.  
  31095.  
  31096.  
  31097.  
  31098.  
  31099.  
  31100.  
  31101.  
  31102.  
  31103.  
  31104.  
  31105.  
  31106.  
  31107.  
  31108.  
  31109. Standard C
  31110.  
  31111.  
  31112. Formal Changes to C
  31113.  
  31114.  
  31115.  
  31116.  
  31117. P.J. Plauger
  31118.  
  31119.  
  31120. P.J. Plauger is senior editor of The C Users Journal. He is convenor of the
  31121. ISO C standards committee, WG14, and active on the C++ committee, WG21. His
  31122. latest books are The Standard C Library, published by Prentice-Hall, and ANSI
  31123. and ISO Standard C (with Jim Brodie), published by Microsoft Press. You can
  31124. reach him at pjp@plauger.com.
  31125.  
  31126.  
  31127.  
  31128.  
  31129. Current Status
  31130.  
  31131.  
  31132. The last meeting of the C standards committees, ISO JTC1/SC22/WG14 and
  31133. ANSI-authorized X3J11, occurred jointly last December near Dulles Airport. I
  31134. left that meeting with a warm sense of accomplishment and a humongous amount
  31135. of homework. Both were a direct result of having the meeting go the way I'd
  31136. hoped, for a change. In soap operas, senators and board chairpeople always
  31137. finagle their political goals against all odds. In the real world, we lowly
  31138. Convenors of standards committees mostly go with the flow. 
  31139. I summarized the administrative highlights in my "Editor's Forum" last issue.
  31140. (That was in CUJ March 1993, but see also the "Editor's Forum" for January
  31141. 1993 and for November 1992.) Here again is a brief synopsis of what happened:
  31142. WG14 finally voted out an amendment to the C Standard. We were charged several
  31143. years ago by SC22, our parent committee, to produce such a "normative
  31144. addendum" to correct several perceived flaws in the coverage and expression of
  31145. the C Standard. With a bit of eleventh-hour compromising, we finally got
  31146. agreement within WG14. Now the amendment must survive at least two rounds of
  31147. balloting within SC22 before it becomes formal.
  31148. SC22 finally established sensible procedures for interpreting and correcting
  31149. ISO programming language standards. Various authorized agencies issue Defect
  31150. Reports to the Convenor (me again). She/he (I) must log them, acknowledge
  31151. them, and submit them to the Working Group to develop a response. A Technical
  31152. Corrigendum patches the standard, while a Record of Response simply explains
  31153. the standard. (I helped develop these procedures at the SC22 plenary in
  31154. Finland last August.)
  31155. ANSI has adopted the ISO C Standard verbatim as its own, replacing the
  31156. original C Standard, with slightly different formatting. That was a prelude to
  31157. having X3J11 start an I-Project to track development of the normative
  31158. addendum. The English of all this is that responsibility for maintaining the C
  31159. Standard has passed from ANSI to ISO.
  31160. X3J11 recognizes that its principal current business is to apply its expertise
  31161. in interpreting the C Standard. WG14 has requested that X3J11 develop initial
  31162. interpretations and X3J11 has graciously agreed to do so. WG14 retains
  31163. ultimate responsibility for publishing the responses, as I described above.
  31164. For these and other reasons, I have resigned as Secretary and member of X3J11.
  31165. I get all the glory I need as Convenor of WG14, thank you. And I seem to have
  31166. more than enough work as well. That homework I mentioned earlier has occupied
  31167. me for over a month, off and on, since the last meeting. The responsibility
  31168. lies with me to prepare the normative addendum for SC22 balloting as a
  31169. Committee Draft (CD). I also inherit several years of X3J11 interpretations as
  31170. a huge batch of Defect Reports to log and organize.
  31171. My aim in writing this report is not to win your symphathy. (But I'll take any
  31172. I can get by the way.) Rather, it's to spell out the current formal activities
  31173. in the C standard arena. Note that this report does not cover the work of
  31174. X3J11.1 (a.k.a. the Numerical C Extension Group, or NCEG). That's because the
  31175. charter of that subcommittee is to produce only a Technical Report (TR). In X3
  31176. land, a TR does not have the force of a standard. It is simply advisory.
  31177. You'll hear more about X3J11.1 in future installments of this column.
  31178. I need to cover a lot of administrivia first, so please bear with me. I
  31179. promise to give you a few technical details of what's happening to Standard C
  31180. before I'm done.
  31181.  
  31182.  
  31183. The Normative Addendum
  31184.  
  31185.  
  31186. The normative addendum has, until recently, consisted of three contributions,
  31187. each put forth by a separate member body:
  31188. The UK contribution endeavors to clarify several dozen areas where people
  31189. found the C Standard unclear. Some issues arose from queries within the UK.
  31190. Others arose from early Requests for Interpretation (RFIs) submitted to X3J11.
  31191. All took the form of examples to be added to the C Standard. (Examples are
  31192. part of the C Standard, but don't affect the definition of the C language.
  31193. Hence, they are a good vehicle for adding clarification without running the
  31194. risk of inadvertently changing the language.) 
  31195. The Danish contribution adds macros and several alternate ways to spell some
  31196. of the punctuators and operators in C. The idea is to provide a way to write C
  31197. source code more readably in character sets that commandeer things like braces
  31198. and the tilde character for other graphics. The C Standard includes trigraphs
  31199. for this purpose, but nobody pretends that using them makes for readable code.
  31200. Even if you can't replace all trigraphs, proponents argue, any improvement in
  31201. readability is worth supporting. 
  31202. The Japanese contribution adds extensive support for manipulating large
  31203. character sets in C. The C Standard provides only the bare minimum of the
  31204. functionality you need to play with Kanji or other large character sets. The
  31205. Japanese delegation has developed a much more ambitious extension to C for
  31206. this purpose.
  31207. The biggest change we agreed to last December was to delete the UK
  31208. contribution from the normative addendum. Don't think we considered it
  31209. unimportant--quite the contrary. Rather, we observed that the new machinery
  31210. for handling Defect Reports offered more apropos vehicles for publishing the
  31211. work of the UK delegation. So we threw this piece over the wall, as it were.
  31212. The other two pieces got final approval at the meeting. Both, however,
  31213. suffered from a serious shortcoming. They needed to be translated into better
  31214. "standardese." The Danish contribution evolved as a one- or two-page statement
  31215. of intent. The Japanese contribution was remarkably refined, given the
  31216. difficulty that English presents to the Japanese. But still there were places
  31217. where the wording was a bit rough, or where more formal jargon was called for.
  31218. Lucky for me, Dave Prosser took it upon himself to correct these problems. As
  31219. the final Redactor (editor) of the C Standard, Dave speaks standardese like an
  31220. ISO bureaucrat. He also understands C better than practically anybody else I
  31221. know. By the time he completed a pass over the normative addendum, I had
  31222. little left to do except carp at details, then make a stack of review copies.
  31223. As of this writing, a review committee is checking our work. I will then
  31224. submit the document to SC22 for CD balloting, once we get everyone's approval.
  31225. By the time you read this, the balloting should be under way. My goal is to
  31226. have the balloting period close shortly after the next X3J11 meeting (New York
  31227. City in May), and before the next WG14 meeting London in July). That's all
  31228. part of a little game of brinksmanship that we Convenors play all the time.
  31229.  
  31230.  
  31231. Defect Reports
  31232.  
  31233.  
  31234. Meanwhile, back at the ranch, I have this great stack of interpretations from
  31235. X3J11. Four dozen Requests for Interpretation have percolated through ANSI
  31236. official channels since the C Standard was approved in 1989. Over the years,
  31237. X3J11 has patiently addressed and debated every one. The result has been two
  31238. Technical Information Bulletins (TIBs) summarizing the RFIs and committee
  31239. responses.
  31240. I described some of the earliest RFIs and responses in these pages way back
  31241. when. (See "Standard C: A Matter of Interpretation," CUJ June 1990, and
  31242. "Standard C: Interpreting the Nasties," CUJ July 1990.) Other people have also
  31243. discussed some of the interpretations here and in other publications. Sadly,
  31244. however, the TIBs have yet to be officially published by ANSI.
  31245. Now it looks like they never will be. An administrative foulup or two delayed
  31246. the publication of TIB #1. Then ANSI switched over to the ISO C Standard and
  31247. the situation changed. No longer was ANSI obliged to interpret the C Standard,
  31248. since it was now an ISO document. Worse, it wasn't clear whether ANSI was even
  31249. permitted to issue interpretations, under the agreement with ISO. TIB #2
  31250. sailed straight into the same swamp. Now both are mired in bureaucratic
  31251. uncertainty.
  31252. We didn't want to lose all those probing questions to public view. And we
  31253. certainly didn't want to waste the carefully crafted responses. So I accepted
  31254. the obligation to treat each of the ANSI RFIs as a separate Defect Report.
  31255. I've ensured that Defect Reports #001 through #048 correspond to ANSI RFIs #01
  31256. through #48. (And I've already been handed Defect Report #049 through a
  31257. separate channel, even before the dust has settled on the changeover.)
  31258. I've built this 100-page (typeset) Defect Report Log. It contains the original
  31259. ANSI RFIs, each accompanied by a "suggested response"--the response crafted by
  31260. X3J11 for publication in a TIB. And remember all those examples from the UK
  31261. contribution of the normative addendum? Well, I dealt them out as appropriate
  31262. among the RFIs. Each example is labeled as a "suggested correction" to the C
  31263. Standard. 
  31264. That's not the end of it, of course. X3J11 developed most of its responses
  31265. under a severe constraint. We were originally told that we could not change a
  31266. single word of the C Standard. Even if a slight change of wording, or an added
  31267. sentence, could clarify our intent without changing the language definition,
  31268. we couldn't make the change. Thus, we put a lot of energy into rationalizing
  31269. that you could read the C Standard the way we intended. That's not the best
  31270. way to respond to a serious complaint from a confused questioner. 
  31271. Now WG14 has machinery for making such clarifications, as Technical
  31272. Corrigenda. The sentiment among many members of both WG14 and X3J11 is that we
  31273. should not waste this opportunity. We could simply publish the two ANSI TIBs
  31274. as an ISO Record of Response. That would get the interpretations out to the
  31275. public (at last) fairly quickly. But it would leave us in the position of
  31276. rationalizing bad standards language instead of fixing it.
  31277. So my task instead is to circulate this Defect Report Log among the membership
  31278. of both committees. I hope that X3J11 can give us prompt guidance about the
  31279. best way to respond to each Defect Report. Either we accept the explanation
  31280. from the ANSI TIB, we include the example from the UK contribution, or we
  31281. develop amended wording to clarify the C Standard. (I like to think that only
  31282. one of these three options will suffice in each case.)
  31283. I hope for prompt guidance because this process has already dragged on for too
  31284. long. The sooner we can clarify the gray areas of Standard C for the world at
  31285. large, the happier I'll be.
  31286.  
  31287.  
  31288.  
  31289. The Danish Contribution
  31290.  
  31291.  
  31292. Now for a few technical details. The Danish contribution requires that all
  31293. implementations of Standard C add a header called <iso646.h>. (The name honors
  31294. the ISO standard which corresponds to ASCII, except that it permits certain
  31295. graphics to be substituted for those we Americans know and love.) Listing 1
  31296. shows the contents of this header.
  31297. Note that you can use this file as is with any variant of ISO 646. It just
  31298. prints funny on some national variant of that character set. The idea, in
  31299. fact, is to confine most of the funny printing to just this header (which you
  31300. should seldom feel moved to print.) You can then write:
  31301. if (x != 0 x != XMAX)
  31302. .....
  31303. as
  31304. if (x ne 0 or x ne XMAX)
  31305. .....
  31306. and the code should be readable with any national variant. If that is not
  31307. important to you, don't include the new header. Then none of the new macros
  31308. conflict with any names you choose. 
  31309. Besides this header, all implementations of Standard C must also recognize
  31310. alternate spellings for six tokens:
  31311. <: :> <% %> %: %:%:
  31312. /* are the same as */
  31313. [ ] { } # ##
  31314. /* respectively */
  31315. Because they are just alternate ways to spell the same token, you can balance
  31316. <: with }, if you want to be perverse. And if you "stringize" one of these
  31317. alternate forms, you get a different result than when using the older token
  31318. (or its trigraph form). Thus:
  31319. #define STR(X) #X
  31320. printf(STR( <: ) STR( { ) STR( ??< ));
  31321.  
  31322. prints <:{{.
  31323. Before you start writing letters, let me make a few observations:
  31324. Not all of these alternate forms are needed to solve problems with ISO 646,
  31325. despite the name. Some help with EBCDIC as well.
  31326. None of these alternate forms help much with using certain changeable
  31327. characters inside character constants and string literals. You still need to
  31328. use trigraphs sometimes. 
  31329. bitand is hardly a great name to use for & as the address-of operator. 
  31330. You can improve on these names all sorts of ways. In fact, many people have.
  31331. In further fact, suggesting alternative lists has been a popular indoor sport
  31332. at C standards meetings for several years now.
  31333. The point is that this addition is not perfect. I'm pretty convinced after
  31334. years of trying, though, that perfection is unattainable here. This particular
  31335. approach is good enough. It can also be argued that the problem this addition
  31336. solves is small and rapidly getting smaller. Others are pretty convinced,
  31337. though, that it is still a problem worth solving. I believe the clutter is
  31338. small enough that the rest of us should be tolerant.
  31339.  
  31340.  
  31341. The Japanese Contribution
  31342.  
  31343.  
  31344. By far the largest part of the normative addendum is the Japanese component. I
  31345. count one new macro, three new type definitions, and 60 (!) new functions. The
  31346. basic idea is to provide a complete set of parallels between functions that
  31347. act on one-byte characters and functions that act on the newer wide
  31348. characters. It's too bad we have to invent a whole set of variant names for
  31349. these new functions. (That's one of the ways that C++ has improved code
  31350. hygiene over C.) But I believe the time is ripe to introduce better
  31351. wide-character support.
  31352. Windows NT traffics consistently in (16-bit) wide characters. It exemplifies
  31353. the new trend toward supporting large and varied character sets. Multiple sets
  31354. of 256 characters just don't cut it for systems and applications with an
  31355. international market.
  31356. I plan to devote next month's column to a detailed look at the Japanese
  31357. contribution. It's too big to due justice to in the space remaining here. For
  31358. now, I'll simply summarize what it contains:
  31359. A set of functions analogous to those in <ctype.h> lets you classify wide
  31360. characters much the way you do conventional ones. You can also define your own
  31361. categories of characters and test for them.
  31362. A set of functions analogous to those in <string.h> lets you manipulate
  31363. wide-character strings much the way you do conventional ones. 
  31364. A set of functions analogous to those in <stdlib.h> lets you convert numeric
  31365. wide-character strings much the way you do conventional ones.
  31366. Additions to the wide-character conversion functions in <stdlib.h> give you
  31367. much tighter control over the conversion process.
  31368. A function analogous to strftime in <time.h> lets you encode time information
  31369. as a wide-character string much the way you do conventional ones.
  31370. Additional conversion specifiers for the existing print and scan functions let
  31371. you convert between occasional multibyte sequences in files and wide
  31372. characters internal to the program.
  31373. A set of functions analogous to those in <stdio.h> lets you manipulate
  31374. "wide-character streams". These are files of multibyte characters that appear
  31375. internally as sequences of wide characters.
  31376. Some of this stuff sounds redundant, and it is. Still, there are good reasons
  31377. for each of the additions. I'll do my best to convince you of that next month.
  31378.  
  31379. Listing 1 The header <iso646.h>
  31380. #define and &&
  31381. #define and_eq&=
  31382. #define bitand&
  31383. #define bitor 
  31384. #define compl ~
  31385. #define ne !=
  31386. #define not !
  31387. #define or 
  31388. #define or_eq =
  31389. #define xor ^
  31390. #define xor_eq^=
  31391.  
  31392. // End of File
  31393.  
  31394.  
  31395.  
  31396.  
  31397.  
  31398.  
  31399.  
  31400.  
  31401.  
  31402.  
  31403.  
  31404.  
  31405.  
  31406.  
  31407.  
  31408.  
  31409.  
  31410.  
  31411.  
  31412.  
  31413.  
  31414.  
  31415.  
  31416.  
  31417.  
  31418.  
  31419.  
  31420.  
  31421.  
  31422.  
  31423.  
  31424.  
  31425.  
  31426.  
  31427.  
  31428.  
  31429.  
  31430.  
  31431.  
  31432.  
  31433.  
  31434.  
  31435.  
  31436.  
  31437.  
  31438.  
  31439.  
  31440.  
  31441.  
  31442.  
  31443.  
  31444.  
  31445.  
  31446.  
  31447.  
  31448.  
  31449.  
  31450.  
  31451.  
  31452.  
  31453.  
  31454.  
  31455.  
  31456.  
  31457.  
  31458. On the Networks
  31459.  
  31460.  
  31461. It's Back?
  31462.  
  31463.  
  31464.  
  31465.  
  31466. Sydney S. Weinstein
  31467.  
  31468.  
  31469. Sydney S. Weinstein, CDP, CCP is a consultant, columnist, lecturer, author,
  31470. professor, and President of Datacomp Systems, Inc., a consulting and contract
  31471. programming firm specializing in databases, data presentation and windowing,
  31472. transaction processing, networking, testing and test suites, and device
  31473. management for UNIX and MS-DOS. He can be contacted care of Datacomp Systems,
  31474. Inc.,3837 Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail
  31475. on the Internet/USENET mailbox syd@DSI.COM (dsinc!syd for those that cannot do
  31476. Internet addressing).
  31477.  
  31478.  
  31479. Once again, and with no explanation, comp.sources.unix. seems to have gotten
  31480. restarted again, just in time for the new year. However, comp.sources.x is
  31481. still silent. Well, while it is still active, I guess it's time to make the
  31482. best of comp.sources.unix. Welcome back Paul. (The moderator is Paul Vixie.)
  31483. Let's see if we can keep it coming and empty out the backlog, as many of these
  31484. sources are over a year old.
  31485. First on the new list is mytinfo from Ross Ridge <ross@zooid.guild.org>.
  31486. mytinfo is a single library that combines the functionality of the standard
  31487. UNIX termcap and terminfo libraries. It has the special ability of being able
  31488. to fetch terminal descriptions from both termcap and terminfo databases
  31489. regardless of which set of functions, termcap or terminfo, are used. It can
  31490. even read terminal descriptions from terminfo source files. Also included is a
  31491. tool to convert from/to termcap and terminfo source and binary formats. It was
  31492. posted as Volume 26, Issues 77-79.
  31493. If you need restrict privileged access (super-user access) to a small set of
  31494. commands, then op from David Koblas <koblas@mips.com> is just what you need.
  31495. Posted as Volume 26, Issue 80, it provides restrictions on who can execute the
  31496. commands, what commands they can execute, and even what arguments are to be
  31497. allowed to the commands. Just perfect for restricting mounting of CD-ROM
  31498. drives or floppies as UNIX file systems (a task, alas, that requires super
  31499. user-access).
  31500. A variant on John Walker's settime for setting the clock on a Sun Sparcstation
  31501. from a dial up access service was posted by John Rushford <rushpc!jjr@csn.org>
  31502. as nisttime for Volume 26, Issue 81. This version is for System V flavors of
  31503. UNIX and supports accessing both the NIST master clock at the National
  31504. Institute of Standards and Technology and the USNO master clock at the Naval
  31505. Observatory. The script will dial the respective clock, receive the correct
  31506. time, and then update the systems clock.
  31507. Copying boot tapes, or other multi-volume tapes under UNIX can be troublesome.
  31508. You often have to spool them to disk first, and there isn't enough room. Thad
  31509. Floryan <thad@btr. com> has provided tprobe-1.0 which can reveal a
  31510. tapes-existing saveset layout, copy the tape using drives located anywhere on
  31511. the network, and perform media conversions. Published as Volume 26, Issue 84,
  31512. it even includes documentation.
  31513. If your C is missing the library routine strftime then the version submitted
  31514. by Arnold Robbins <arnold@skeeve.ATL.GA.US> will fit the bill. strftime is a
  31515. format-string-controlled internal time representation to ASCII conversion
  31516. filter. It supports locale for internationalization. strftime, and a date
  31517. command wrapper, are Volume 26, Issue 85.
  31518.  
  31519.  
  31520. Utilities Galore
  31521.  
  31522.  
  31523. Utilities abound this time in comp. sources. misc.
  31524. If you are tired of nohup leaving nohup.out files lying around, consider nhup
  31525. from Gnanasekaran Swaminathan <gs4t@virginia.edu>. It works like nohup, as it
  31526. runs the command in the background, immune from hangup and quit signals, but
  31527. unlike nohup, if the input and output are not redirected, they are set to
  31528. /dev/null and not a nohup. out file. nhup is Volume 33, Issue 80.
  31529. pdcurses is a public domain Curses C library that is compatible with the UNIX
  31530. System V 3.2 curses library. It is written to support most of the popular DOS
  31531. and OS/2 C Compilers. This new version, 2.0, is almost a total rewrite of the
  31532. prior 1.4 version, by a new maintainer, Mark Hessling <M.Hessling@gu.edu.au>.
  31533. Posted as Volume 33, Issues 81-91, a dmake 3.8 flavor makefile is included for
  31534. both DOS and OS/2. New in 2.0 include X/Open routines, Color support in System
  31535. V format, OS/2 port, and many functions supported as both functions and
  31536. macros.
  31537. Farrell McKay <fbm@ptcburp.ptcbu.oz.au> released a bug-fixed version of his
  31538. xcb-2.1 program for Volume 33, Issue 92. xcb provides easy access to the
  31539. cut/paste buffers in the X server. New is WM_DELETE_WINDOW protocol support,
  31540. and fixing of two major bugs.
  31541. MetalBase, a simple database engine, portable between platforms (UNIX, MS-DOS,
  31542. etc.) has been posted as mbase by Richard Parvin Jernigan
  31543. <richid@owlnet.rice.edu> for Volume 33, Issues 119-126. This latest release,
  31544. 5.0, has more field types, three-phase locking to ensure multi-user access
  31545. without any system-based IPC, and internal caching to provide twice the speed
  31546. and up for rebalancing indices. A conversion utility for MetalBase 4.0 and
  31547. 4.1a relations is provided. Other features include runtime encryption on all
  31548. platforms, creation of relations on-the-fly, and a flexible report writer.
  31549. Lee Hounshell <tlhouns@srv.pacbell. com> has submitted his C++ object library
  31550. var for Volume 33, Issues 127 and 128. var provides a "super-string" class
  31551. with associate array capabilities. The var class does a pretty good job of
  31552. offering a data object that assumes its "type" at runtime, based on context of
  31553. use. Rarely will you need to declare ints, or longs, or doubles or char[] or
  31554. even string objects! var does it all (or at least tries to).
  31555. An alternative user interface for ftp was created by Mike Gleason
  31556. <mgleason@cse.unl.edu>. ncftp was posted as Volume 34, Issues 14-16. It
  31557. supports autologin as anonymous, access to sites in your .netrc file using a
  31558. substring of their name, use of a pager to view remote directory listings, a
  31559. transfer progress meter, activity logging, and much more. It's not as
  31560. impressive as ftptool, but it doesn't require an X play either. A bug-fix
  31561. patch (patch 1) was posted in Volume 34, Issue 20.
  31562. Two C++ classes for outputting formatted numbers were contributed by Scott
  31563. Kirkwood <kirkwood@qucis.queensu.ca>. The first, fformat for Volume 34, Issue
  31564. 23, supports scaling and editing, truncation to fit using SI notation (K, M,
  31565. etc.) for floating-point numbers. The second, iformat, in Issue 24 is for
  31566. integer numbers and offers similar capabilities.
  31567. A collection of X11 image processing and display utilities was contributed by
  31568. John Cristy <cristy@eplrx7.es.duPont.com> for Volume 34, Issues 28-54 with
  31569. patches in Issues 85-87, 88, 89, 98 and 118. The imagemagick utilities read
  31570. and write MIFF images and has utilities to access GIF, JPEG, PostScript, PPM,
  31571. RLE, SUN Raster, TIFF, Utah Raster, VICAR, X Bitmap, X Window Dump formats. It
  31572. can display images, support animation, perform processing on images (scaling,
  31573. rotation, color reduction, and more), and much more. Support for the JPEG4
  31574. release is also included. (See the next item.)
  31575. In addition, the Independent JPEG Group <jpeginfo@uunet.uu.net> has updated
  31576. its JPEG software to version 4 in Volume 34, Issues 55-72 jpeg in this new
  31577. version provides significant spe