home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-02-07 | 3.2 MB | 100,450 lines |
Text Truncated. Only the first 1MB is shown below. Download the file for the complete contents.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Porting C Libraries To C ++
-
-
- David Brumbaugh
-
-
- David Brumbaugh is a project manager at Advanced Information Services, a
- systems integrator in Peoria, IL. He has been programming in C for over five
- years and in C++ for over a year. He can be reached by mail at 2807 N. Renwood
- Ave., Peoria, IL 61604.
-
-
- Most C programmers have large investments in existing C libraries. If C++ is
- to fulfill the promise of reusable software, the C libraries of today cannot
- suddenly become obsolete. Usually, existing libraries can be given "OOPness"
- by encapsulating their functions in C++ classes. Furthermore, if those classes
- are well-designed, they can enhance an application's portability, readability,
- and maintainability.
-
-
- C++ And C
-
-
- For all practical purposes, C++ is a superset of C. C++ can be linked with
- code compiled by a C compiler if the functions are prototyped and enclosed in
- the linkage specifier:
- extern "C" { }
- This declaration lets you use C libraries directly, without modification. Of
- course, you must have a compatible object file to begin with. You can link a
- Turbo C 2.0 library and a Turbo/Borland C+ + program, but you cannot use the
- declaration to link a Turbo/Borland C+ + program and a Microsoft C 5.1
- library. (I know because I tried.) While this scheme doesn't exploit C+ +'s
- bent towards OOP, almost anything your old C libraries can do can be made to
- work in C++.
-
-
- Terms
-
-
- Cutting away the object-oriented jargon leaves little difference between a
- method and a function. Objects contain both data and functions. In OO jargon,
- these are attributes and methods. (For the purposes of this article, I use
- "method" to denote a class's member function and "function" to mean a
- traditional C function.) In many cases libraries already contain most of the
- code for the methods. To make the libraries OO, you simply need to wrap
- classes around the libraries.
-
-
- Preparation
-
-
- In many cases, it is not necessary to wrap libraries. A library that is not
- often used or has no common thread probably is not a candidate for wrapping.
- On the other hand, a method may need some functions found in a library that is
- not wrapped itself. In this case, simply enclose the C function in extern "C"
- { } and call it as necessary. Often you can use conditional compilation in the
- library's header file. (See Listing 1. The __cplusplus macro is Turbo/Borland
- C++ specific.)
- Before wrapping a class around a library, ask yourself what advantages you
- expect. Typically, these might include:
- Common Access. By overloading methods, it isn't necessary to create functions
- named replaceString, replaceInt, replaceReal. A replace method will suffice.
- Code becomes more readable and, in turn, more maintainable.more readable and,
- in turn, more maintainable.
- Reusable Code. Object-oriented code is more reusable because it encourages
- encapsulation. In traditional programming, programmers tend to reuse code by
- using an editor's "cut and paste" functions. While this works, the same code
- in different locations frequently requires slight changes in variable names,
- function names, or control structures. An object-oriented style enhances code
- reusability by localizing and formalizing the areas being changed.
- Localized Impact of Changes. Changes in understanding, requirements, and
- design prompt changes in program code. A typical problem arises when two areas
- of a program call the same function. If that function is subsequently modified
- to accommodate a change in one of the two areas, the other area may behave
- improperly. An object-oriented solution creates a descendant of the object in
- the first area, where the change is needed. All other code remains the same.
- Again, the change is local and formal.
- Portability of Applications. By isolating platform-specific libraries from
- your application in private and protected methods, you can change the
- implementation details of a specific class without touching the actual
- application code (this is a variation on number three). While preparing to
- wrap the library in a class, remember that in C++, all functions need
- prototypes. Most libraries come with a header file containing the prototypes.
- If your library doesn't come with such a header file, you must make one in
- order to use the functions in C++.
- C++ has several keywords not found in C: asm, catch, class, delete, friend,
- inline, new, operator, private, protected, public, template, this and
- virtual[1]. If any of your library functions have these names, you will have
- problems. To work around this, remove the prototype of the offending function
- from the header file using conditional compilation. Write a new function, with
- a different name, in C that calls the library function (see Listing 2 for an
- example).
- The final consideration for encapsulation concerns the availability of source
- code. If you have purchased the source code for a commercial library, you may
- want to avoid the overhead of wrapping, and simply use the cut and paste
- method to build classes from the original source.
-
-
- Designing Classes Around Libraries
-
-
- Since a class encapsulates both data and functions, you must identify data
- structures in the library that can become attributes of your class. As a
- general rule, data structures should be made protected variables. You will
- want to group common functions and data structures into a class.
- Once you have identified the common data structures, determine the class
- protocol. A class protocol is the list of methods used to send messages to
- objects of the class. In C++, the class protocol is the set of public methods
- and data items. Use generic names for the methods in your protocol. A database
- library may have several read functions: db_read_int(), db_read_str(),
- db_read_float(), etc. The protocol for this set of functions would be read(int
- &), read(char *), read(float &), etc. The bodies of these methods would
- contain calls to the proper library functions.
- Avoid the temptation to give class methods library-specific names. Create
- general classes that permit inheritance in the future. For example, a generic
- database class could have descendants that call two completely different
- database libraries. Changing from one database to another would require no
- changes to the application.
- Just because a function is in a library does not mean it must be represented
- in the class. In the example that follows, the Pfm_List class concerns itself
- with a single table in a database. It does not need database creation or
- administration functions.
- Round out the class with internal methods. In C++, methods designated as
- private or protected are internal and are used by other methods. The library
- will generally dictate the internal methods, but don't allow it to dictate the
- overall class concept.
-
-
- Pinnacle File Manager Tables
-
-
- I have translated into C+ + an example that I wrote for an earlier CUJ
- article, "Object-Oriented Programming In C" (July 1990). I used the Pinnacle
- File Manager v3.5 by Vermont Database Corporation as the library to
- encapsulate. (If you wish to run the code in the examples, you can obtain a
- free sample disk that is limited to 100 records per table from Vermont
- Database Corporation by calling 802-253-4437.)
- I first needed to decide how to implement the list concept I presented in July
- 1990. Turbo C++ came with a Container class library, which met some of the
- needs I was trying to meet with the LIST_CLASS. Should I try to fit my code
- into their hierarchy or create my own hierarchy? I finally decided to use my
- own. This hierarchy is shown in Figure 1.
- A D_List defines the operations you would normally perform on a list. The list
- will be ordered in some way, even if only in a physical order. The list will
- have a "top" and an "end," and will include the concept of a "current" member.
- The methods in Table 1 are common to all lists.
-
-
-
- The Files
-
-
- LISTCLAS.H (Listing 3) defines the abstract class D_List. LISTCLAS.CPP
- (Listing 4) defines the source code. Note the large number of purely virtual
- functions. The D_Array descendant of D_List is a list in memory. Since it uses
- no commercial libraries, it is outside the scope of this article. The next
- class in the hierarchy, Pfm_List, is a class specifically designed to use the
- protocol defined in D_List to access a Pinnacle File Manager database table.
- This is a case of making a library fit your design, rather than designing
- around a library.
- Because the scope of the class is limited to a single list (table), wrapping
- the Pinnacle File Manager functions does not take full advantage of the
- manager's advanced features. Since the class hierarchy was in place before I
- had PFM, I will add more features to the class when I need them, either
- directly or through inheritance. I may also choose to create a friend class to
- apply some of the other features.
- PINCLASS.H (Listing 5) defines Pfm_List. Note the extern "C" { } around the
- inclusion of PINNACLE.H, informing the C++ compiler that I intend to use the C
- functions defined in pinnacle.h. The data structures used by Pinnacle, DB and
- DBTAB, are private, providing enforced data encapsulation. The other methods
- (functions) are defined as public. In addition to the required constructors
- and destructors, two additional methods, DB_Handle() and TableHandle(),
- interface with other classes and functions requiring database access.
- PINCLASS.CPP (Listing 6) contains the source code for the class Pfm_List. Note
- that the DB_ functions are the same functions defined between extern "C"
- brackets. This is where we "wrap" the C+ + classes around the library.
- The sample application is an imaginary payroll list. PAYLIST.H and PAYLIST.CPP
- (Listing 7 and Listing 8) define the class and code for the class specific to
- the application.
- PTEST.CPP (Listing 9) tests the classes. It is simply a small program that
- runs through the major methods of the classes. Note that with the exception of
- the constructor, there is no database specific code. You can see that if the
- company decided to port the PC application elsewhere, all the changes would
- occur within the PayList class.
- Other files include the script for creating the database (Listing 10) and the
- database itself (PAYROLL.DB), which is included on the code disk.
-
-
- Conclusion
-
-
- One of the major advantages of C++ is its compatibility with the vast number
- of existing C libraries. In addition to protecting your investment, the
- encapsulation of libraries can enhance maintainability, portability, and ease
- of use.
-
-
- Bibliography
-
-
- [1] Stevens, A1, Teach Yourself C++, MIS:Press, Portland Oregon, 1990.
- Figure 1
- Table 1
- Method Purpose
- ----------------------------------------------------------------------------
- at_top Return TRUE if current member is top member.
- at_end Return TRUE if current member is last member.
- is_empty Return TRUE if LIST is empty, FALSE otherwise.
- find Search the list for an implementation defined member. If not
- found don't change currency.
- prev Make the member previous to this one current. If current
- member is top, do nothing.
- next Make the member after this one current. If current member is
- last, do nothing.
- seek Search to a position in the list. Use like fseek.
- top Make the top member current.
- end Make the last member current.
- display Display the current member.
- add_member Add a new member to the list.
- replace_member Replace data in current member.
- current Return a pointer to the current member.
- total_members Return the total number of members in the list.
- tell Return the position, from the start of the list, of the
- current member. The top member is 0.
-
- Listing 1 Example of Header File with and C++ Prototypes
- example1.h:
-
- #ifdef __cplusplus
- extern "C" {
- #endif
-
- int foo(int x);
- int bar(int x);
-
- #ifdef __cplusplus
- }
- #endif
-
-
-
- Listing 2 Example of C Functions with C++ Key Word Names
- This code doesn't work:
-
- new.cpp:
- extern int new();
-
- main()
- {
- int x = new();
- }
-
- So replace it with this code:
-
- example2.h:
- #ifndef __cplusplus
- extern int new();
- #endif
- #ifdef __cplusplus
- extern int new_one();
- #endif
-
- example2.c:
-
- #include "example2.h"
-
- int new_one()
- {
- return(new());
- }
-
-
- new2.cpp:
-
- #include "example2.h"
-
- main()
- {
- int x = new_one();
- }
-
- /* End of File */
-
-
- Listing 3 (listclas.h)
- //////////////////////////////////////////////////////
- // D_List Class - Similar to list class
- // developed for CUJ July, 1990
- //
- // Dave's List.
- //
- //////////////////////////////////////////////////////
- #ifndef LISTCLAS_H
- #define LISTCLAS_H
- #include <stdio.h>
- enum Boolean {false, true};
-
- class D_List {
-
- public:
- virtual Boolean at_top()
- { return ((Boolean) (tell() == 0L));}
- virtual Boolean at_end0 : 0;
- virtual Boolean is_empty()
- { return ((Boolean) (total() == 0L)); }
- virtual Boolean find(void *key) = 0;
- virtual void prev() = 0,
- next() = 0,
- seek(long where, int start),
- top() = 0,
- end() = 0,
- add() = 0,
- replace(void *member) = 0,
- remove() = 0;
- virtual void *current() = 0;
- long virtual total(),
- tell() = 0;
- void * operator[] (long where)
- { seek(where,SEEK_SET); return current(); }
- void * operator[] (void *key)
- { return (find(key) ? current() : NULL); }
- };
- #endif
-
- /* End of File */
-
-
- Listing 4 Source Code for C++D_List Class
- #include "listclas.h"
-
-
- void D_List::seek(long where, int start)
- {
- long count;
-
- switch(start)
- {
-
- case SEEK_SET:
- top();
- for (count = 0; count < where; ++count)
- {
- if (at_end())
- break;
- next();
- }
- break;
- case SEEK_CUR:
- if (where > 0)
- {
- for (count = 0; count < where; ++count)
- {
- if (at_end())
- break;
- next();
- }
- }
- else
-
- {
- for(count = 0; count > where; ++count)
- {
- if (at_top())
- break;
- prev ();
- }
- }
- break;
- case SEEK_END:
- end();
- for(count = 0; count > where; ++count)
- {
- if (at_top())
- break;
- prev ();
- }
- break;
- }
- }
-
- long D_List::total ()
- {
- long thisone, count;
-
- thisone = tell();
- top();
- count = 0;
- do
- {
- if ( ! at_end() )
- {
- ++count;
- next();
- }
- } while( ! at_end() );
- seek(thisone,SEEK_SET);
- return(count);
- }
-
- // End of File
-
-
- Listing 5 (pinclass.h) Pinnacle File Manager Class
- #ifndef PINCLASS_H
- #define PINCLASS_H
-
- extern "C" {
- #include <pinnacle.h>
- }
- #include "string.h"
- #include "listclas.h"
-
- class Pfm_List: public D_List {
- protected:
- DB db;
- DBTAB table;
- DBCOL default_key;
- DBSEARCH default_dbsearch;
-
- Boolean is_at_top, // Flags
- is_at_bottom,
- needs_closed;
- char *buffer;
- size_t max_buffer_size;
- public:
- // Constructors and Destructors
- Pfm_List(char *database, char *table_name,
- size_t mbs = 1024);
- Pfm_List(DB &open_db, char *table_name,
- size_t mbs = 1024);
- Pfm_List(DB &open_db, DBTAB &db_table,
- size_t mbs = 1024);
- virtual ~Pfm_List();
-
- // Database Specific Methods
- DB DBHandle() {return db;}
- DBTAB TableHandle() {return table;}
-
- // List Status
- virtual Boolean at_top()
- { return( is_at_top); }
- virtual Boolean at_end()
- { return( is_at_bottom); }
- long virtual tell();
-
- // List Navigation
- virtual Boolean find(void *key),
- find(void *key, char *relation),
- find(char *col, char *relation, void *key);
- virtual void prev(), next(), top(), end();
- virtual Boolean findnext(), findprev();
-
- // Interface to and from List
- virtual void add();
- virtual void replace(char *field,
- char *value);
- virtual void replace(char *field,
- long value);
- virtual void replace(char *field,
- double value);
- // virtual void replace(char *field, void *value);
- virtual void replace(void *member){ };
- // Not truly defined.
- virtual void remove();
- virtual void *current()
- {return (void *)buffer; }
- long virtual total()
- { return ((long) DB_CountRows(table));}
- virtual char *get(char *field, char *value);
- virtual long get(char *field, long &value);
- virtual double get(char *field, double &value);
- virtual void *get(char *field, void *value);
-
- };
- #endif
-
- /* End of File */
-
-
-
- Listing 6 (pinclass.cpp) Source code for Wrapping C++ around Pinnacle
- ////////////////////////////////////////////////////
- // David Brumbaugh, 1991
- ////////////////////////////////////////////////////
- #include <iostream.h>
- #include <conio.h>
- #include "PINCLAS.H"
-
- Pfm List::Pfm_List(char *database, char *table_name,
- size_t mbs)
- {
- db = DB_Open(database, "rw", 0);
- if (db == NULL)
- {
- cerr << "Error opening database:"
- << database << "Error ="
- << DB_ErrorString() << "\n";
- cerr << "Strike a Key"; getch();
- return;
- }
- table = DB_Table(db,table_name);
- if (DB_Errno != DB_OK)
- {
- cerr << "Error opening table:"
- << table name << "Error = "
- << DB_ErrorString() << "\n";
- cerr << "Strike a Key";
- getch();
- DB_Close(db);
- return;
- }
- DB_FirstRow(table);
- DB_NextRow(table,DBNEXT);
- is_at_top = true;
- is_at_bottom = false;
- needs_closed = true;
- default_key = NULL;
- default_dbsearch = NULL;
- max_buffer_size = mbs;
- buffer = new char[max_buffer_size];
-
- }
-
- Pfm_List::Pfm_List(DB &open_db, char *table_name, size_t mbs)
- {
- db = open_db;
-
- table = DB_Table(db,table_name);
- if (DB_Errno != DB_OK)
- {
- cerr << "Error opening table:"
- << table_name << "Error = "
- << DB_ErrorString() << "\n";
- cerr << "Strike a Key"; getch();
- return;
- }
- DB_FirstRow(table);
- DB_NextRow(table,DBNEXT);
-
- is_at_top = true;
- is_at_bottom = false;
- needs_closed = false;
- default_key = NULL;
- default_dbsearch = NULL;
- max_buffer_size = mbs;
- buffer = new char[max_buffer_size];
-
- }
-
- Pfm_List::Pfm_List(DB &open db, DBTAB &db_table,
- size_t mbs)
- {
- db = open_db;
- table = db_table;
- DB_FirstRow(table);
- DB_NextRow(table,DBNEXT);
- is_at_top = true;
- is_at_bottom = false;
- needs_closed = false;
- default_key = NULL;
- default_dbsearch = NULL;
- max_buffer_size = mbs;
- buffer = new char[max_buffer_size];
-
-
- }
-
- Pfm_List::~Pfm_List()
- {
- if (needs_closed)
- DB_Close(db);
- delete buffer;
- }
-
- Boolean Pfm_List::find(void *key, char *relation)
- {
- if (default_key == NULL)
- return(false);
- default_dbsearch = DB_SearchObject(db,
- DB_GetType(default_key),key,relation);
- DB_FirstRow(table);
- return((Boolean) DB_FindNext(default_key,
- default_dbsearch,DBNEXT));
-
- }
-
- Boolean Pfm_List::find(char *col, char *relation,
- void *key)
- {
- default key = DB_Column(table,col);
- return(find(key,relation));
- }
-
- Boolean Pfm_List::find(void *key)
- {
- return(find(key,"=="));
- }
-
-
- void Pfm_List::prev()
- {
- if (DB_NextRow(table,DBPREVIOUS) != DB_OK)
- {
- is_at_top = false;
- }
- else
- {
- is_at_top = true;
- if (total () > 1L)
- {
- is_at_bottom = false;
- }
- }
- }
-
- void Pfm_List::next()
- {
- if (DB_NextRow(table,DBNEXT) != DB_OK)
- {
- is_at_bottom = false;
- }
- else
- {
- is at bottom = true;
- if (total() = 1L)
- {
- is_at_top = false;
- }
- }
- }
-
- void Pfm_List::top()
- {
- DB_FirstRow(table);
- DB_NextRow(table,DBNEXT);
- is_at_top = true;
- if (tota1() > 1L)
- is_at_bottom = false;
-
- }
-
- void Pfm_List::end()
- {
- DB_ForAllRows(table);
- DB_NextRow(table,DBPREVIOUS);
- is_at_bottom = true;
- if (total() > 1L)
- is_at_top = false;
- }
-
- void Pfm_List::add()
- {
- DB_AddRow(table);
- }
-
-
- void Pfm_List::replace(char *field,
- char *value)
-
- {
- DBCOL col = DB_Column(table,field);
- DB_PutString(col,(unsigned char *) value);
- }
-
- void Pfm_List::replace(char *field,
- long value)
- {
- DBCOL col = DB_Column(table,field);
- DB_PutInteger(col, value);
- }
-
- void Pfm_List::replace(char *field, double value)
- {
- DBCOL col = DB_Column(table,field);
- DB_PutReal (col, value);
- }
-
- void Pfm_List::remove()
- {
- DB_DeleteRow(table);
- next();
- }
-
- long Pfm_List::tell()
- {
- DBROWID thisrow, checkrow;
- long position = 0L;
-
- thisrow = DB_CurrentRow(table);
- top();
- do
- {
- checkrow = DB_CurrentRow(table);
- if (checkrow != thisrow)
- {
- ++position;
- DB_NextRow(table,DBNEXT);
- }
- } while(checkrow != thisrow);
-
- return(position);
-
- }
- Boolean Pfm_List::findnext()
- {
- if (default_key == NULL default_dbsearch == NULL)
- return(false);
-
- return((Boolean) DB_FindNext(default_key,
- default_dbsearch,DBNEXT));
- }
-
- Boolean Pfm_List::findprev()
- {
- if (default_key -= NULL default_dbseerch == NULL)
- return(false);
-
- return((Boolean) DB_FindNext(default_key,
-
- default_dbsearch,DBPREVIOUS));
- }
-
- char *Pfm_List::get(char *field, char *value)
- {
- DBCOL col = DB_Column(table, field);
- if (value != NULL)
- {
- strcpy(value,(char *)DB_GetString(col));
- {
- return(value);
- }
-
- long Pfm_List::get(char *field, long &value)
- {
- DBCOL col = DB_Column(table, field);
- value = DB_GetInteger(col);
- return(value);
- }
- double Pfm_List::get(char *field, double &value)
- {
- DBCOL col = DB_Column(table, field);
- value = DB_GetReal(col);
- return(value);
- }
-
- void *Pfm_List::get(char *field, void *value)
- {
-
- DBCOL col = DB_Column(table,field);
- unsigned long type - DB_GetType(col);
-
- if (type == Integer)
- {
- DBINTEGER *dbint = (DBINTEGER *) value;
- get(field,*dbint);
- }
- else if (type == Real)
- {
- DBREAL *dbreal = (DBREAL*) value;
- get(field,*dbreal);
- }
- else if (type == String)
- {
- get(field,(DBSTRING) value);
- }
- else if (type == NBytes)
- {
- memcpy(value,DB_GetNBytes(col,NULL),
- DB_GetSize(col));
- }
- return(value);
- }
-
- // End of File
-
-
- Listing 7 (paylist. h) Concrete List Class for a Payroll List
- //////////////////////////////////////////////////
-
- // By David Brumbaugh
- //////////////////////////////////////////////////
-
- #ifndef PAYLIST_H
- #define PAYLIST_H
- #include "pinclas.h"
-
- struct employee
- {
- char last[21], first[11];
- double pay_rate; // Dollars per day
- long days_worked;
- // Days worked in this pay period.
- };
-
- class PayList: public Pfm_List {
- protected:
- employee empBuffer;
-
- public:
- // Constructors
- PayList():Pfm_List("payroll.db","Employees")
- {default_key = DB_Column(table,"LastFirst"); }
- PayList(DB &open_db):PfmList(open_db, "Employees")
- {default_key = DB_Column(table,"LastFirst");}
- PayList(DB &open_db, DBTAB &db_table):
- Pfm_List(open_db, db_table)
- {default_key = DB_Column(table,"LastFirst");}
-
- // List Navigation
- virtual Boolean find (char *last),
- find(char *last, char *first);
- virtual Boolean find(void *key)
- {return (find( (char *) key));}
-
- // List Interface
- virtual void add(employee &emp);
- virtual void replace(employee &emp);
- virtual void get(employee &emp);
- virtual void *current()
- { get(empBuffer); return (void *) &empBuffer;}
-
- };
- #endif
-
-
- Listing 8 Source Code for the "Payroll List" Class
- #include "paylist.h"
-
- Boolean PayList::find(char *last)
- {
- if (Pfm_List::find("Last","==",(void *) last))
- {
- get(empBuffer);
- return(true);
- }
- return(false);
- }
-
-
- Boolean PayList::find(char *last, char *first)
- {
- if (DB_Find(table,"Last == %s && First == %s",
- last, first))
- {
- get(empBuffer);
- return(true);
- }
- return(false);
- }
-
- void PayList::add(employee &emp)
- {
- Pfm_List::add();
- replace(emp);
- }
-
- void PayList::replace(employee &emp)
- {
- Pfm_List::replace("First",emp.first);
- Pfm_List::replace("Last",emp.last);
- Pfm_List::replace("Pay",emp.pay_rate);
- Pfm_List::replace("Days",emp.days_worked);
- empBuffer = emp;
-
- }
-
- void PayList::get(employee &emp)
- {
- Pfm_List::get("First",emp.first);
- Pfm_List::get("Last",emp.last);
- Pfm_List::get("Pay",emp.pay_rate);
- Pfm_List::get("Days",emp.days_worked);
- }
-
- /* End of File */
-
-
- Listing 9 Test for the Pinnacle Database List Class
- /////////////////////////////////////////////////
- // Copyright 1991, David Brumbaugh
- /////////////////////////////////////////////////
- #include "paylist.h"
- #include <iostream.h>
-
- main()
- {
-
- // Test the more abstract Class
-
- Pfm_List db("payroll.db","Employees");
-
- // Create Some Records
- db.add();
- db.replace("First","John");
- db.replace("Last", "Jones");
- db.replace("Pay", 11.25);
- db.replace("Days",245L);
-
-
- db.add();
- db.replace("First","Ben");
- db.replace("Last", "Franklin");
- db.replace("Pay", 111.25);
- db.replace("Days",3L);
-
- db.add();
- db.replace("First","George");
- db.replace("Last", "Washington");
- db.replace("Pay", 1111.25);
- db.replace("Days",4L);
-
- // Then Retreive Them
- char first[10], last[20];
- long days;
- double amount;
-
- db.top();
- do
- {
- cout << db.get("First",first) <<" "<<
- db.get("Last",last) <<" "<<
- db.get("Pay",amount) <<" ";
- db.get("Days",&days);
- cout << days << '\n';
- db.next();
- } while(! db.at_end());
- cout << '\n';
- amount = 100.00;
- if (db.find("Pay",">",&amount))
- {
- do
- {
- cout << db.get("First",first) <<" "
- << db.get("Last",last) <<" "
- << db.get("Pay",amount) <<" ";
- db.get("Days",&days);
- cout << days << '\n';
-
- } while(db.findnext());
- }
- amount = 11.25;
- cout << '\n';
- if (db.find(&amount))
- {
- cout << db.get("First",first) <<" "
- << db.get("Last",last) <<""
- << db.get("Pay",amount) <<" ";
- db.get("Days",&days);
- cout << days << '\n';
-
- }
-
- // Now Test the application specific class
- // Create Some More Records
- employee emp[] = {{"Kirk","James",17.01,5},
- {"Solo","Han",12.34,4},
- {"Hammer","Mike",36.24,36},
- {"Hill","Dixon",19.46,30}},
-
- *empPtr, empBuffer;
-
- PayList pl(db. DBHandle());
- for(int x=0; x < 3; ++x)
- {
- pl .add (emp [x] );
- }
-
- // Then Retrieve Them Again
- pl.top();
- do
- {
- pl.get(empBuffer);
- cout << empBuffer.first <<" "<<
- empBuffer.last <<" "
- << empBuffer.pay_rate <<" ";
- cout << empBuffer.days_worked << '\n';
- pl .next();
- } while(! pl.at_end());
-
- cout << "\n Done \n";
- }
-
- // End of File
-
-
- Listing 10 Code to Create Database Table in PFM
- PAYROLL.INC:
- remove payroll.db
- create payroll.db
- addtab * Employees "List of Employees"
- addcol * * Last "Last Name" "%s" NULL "String NoNulls"
- addcol * * First "1st Name" "%s" NULL "String NoNulls"
- addkey * * LastFirst +Last+First Unique
- addcol * * Pay "Pay Rate" "%6.2f" NULL "Real NoNulls"
- addcol * * Days "Days Worked" "%2d" NULL Integer
- exit
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Portable VMS-Style Input Line Routine
-
-
- Robert Bybee
-
-
- Robert Bybee is Senior Electrical Engineer at Scientific Games, Atlanta
- Georgia, where he is involved with hardware and software design of
- point-of-sale terminals used in state lottery systems. He was previously with
- Chromatics, a manufacturer of color graphic computer systems, and Telecorp
- Systems, which builds voice response and telephone call processing computers.
- He has a BSEE from the University of Virginia and is currently seeking an MBA
- at Georgia State University. Robert has been programming computers for 23
- years, with 10 years of C experience, and has been designing hardware for over
- 15 years. He can be contacted at 5011 Brougham Court, Stone Mountain GA 30087.
-
-
- When a program needs to read a line of text input, the programmer often
- resorts to using a standard C library function, such as gets(). Depending on
- the operating system, this function will permit only minimal input editing,
- perhaps limited to the backspace key.
- The DEC VAX-VMS operating system, however, provides far more sophisticated
- facilities to edit command-line input. The MS-DOS world has produced similar
- facilities, such as the public-domain DOSEDIT, Peter Norton's NDE, and the
- DOS-KEY program in MS-DOS 5.0. Of course, if you're writing code for embedded
- systems or non-DOS machines, you may not be able to use any of these.
- The get_str() routine presented here duplicates the functions found in these
- systems. It provides a way to gather commands or input strings from the user,
- emulating most of the VMS keystrokes. You can change the code easily to
- respond to other keystrokes if you don't like VMS-style editing.
-
-
- Command Line Recall
-
-
- get_str()'s most useful feature is its ability to scroll through previously
- entered commands, edit them, and transmit them again. This functionality comes
- in handy when you make a typing error and must retype almost the same command.
- Moreover, when running test programs, I find myself retyping the same series
- of commands, time and time again. With the touch of a few cursor keys,
- get_str() redisplays these earlier commands for you to enter. The number of
- lines in its "history buffer" is a compile-time decision.
- When entering a command, or after recalling a previous command, you often need
- more than a simple backspace key to edit the command. get_str() supports left
- and right cursor motion, moving to the start and end of the line, and both
- insert (push-right) and overtype modes of text entry.
-
-
- Display Dependencies
-
-
- I wrote get_str() to support a "least common denominator" output device. The
- routine will run on a PC display screen, a VT100 terminal connected to a
- serial port, or nearly any other type of terminal or display, provided the
- display can backspace (move the cursor left) without erasing the character
- that the cursor lands on. get_str() makes no other assumptions about the
- control-codes accepted by its output device.
-
-
- Keyboard Dependencies
-
-
- The next problem is the type of keyboard device to support. It is possible to
- avoid all keyboard dependencies by forcing the operator to type
- CTRL-characters for all editing operations. However, people accustomed to
- using the cursor keys will find such a solution unacceptable.
- Most terminals have at least a backspace key and four cursor arrow keys. VMS
- lets the left and right arrow keys move the cursor in the current line. It
- uses the up and down arrows to move forward and backward through previous
- commands. And it uses the DELETE key on a VT100 to delete one character to the
- left of the cursor. Under VMS, all other command-line editing functions are
- performed using control keys. The key assignments are:
- CTRL-A switches between insert
- and overtype mode
- CTRL-H moves to the beginning
- of the line
- CTRL-E moves to the end of the line
- CTRL-R recalls the last line entered
- CTRL-X erases the line
- get_str() duplicates all of these, except CTRL-H. The backspace key on a VT100
- terminal and PC keyboard both generate this character. There are probably
- historical reasons why VMS uses this key to move the cursor to the start of
- the line, and uses DELETE to erase one character.
- All too often, I find myself hitting the BACKSPACE key instead of DELETE when
- I want to delete a character under VMS. I can't fix VMS, but I can fix
- get_str(). In this code, CTRL-B moves the cursor to the beginning of the line,
- and both DELETE and BACKSPACE erase one character. If you want true VMS
- emulation in this regard, change the value CTRL ('B') to BACKSPACE_KEY (line
- 100), and remove the reference to BACKSPACE_KEY on line 79.
-
-
- Code Description
-
-
- I wrote the code in this article to compile under either Turbo C 2.0 or
- Borland C++ 2.0. If you port the code to a different compiler or environment,
- you need to change only the sys_getchar() and sys_putchar() functions. To
- support a different keyboard, the get_char_esc() routine would also require
- attention.
- Listing 1 contains the get_str() function and supporting routines. Lines 22-25
- define four constants, UP_ARROW, DOWN_ARROW, RIGHT_ARROW, and LEFT_ARROW,
- which pass the notion of a cursor movement between get_chr_esc() and
- get_str(). I defined these values to be the same as the PC keyboard scan codes
- for the four arrow keys, but they could really be any four non-ASCII constants
- if you don't need to read input from a PC keyboard.
- Lines 30-33 declare the size of the history list, called lastlines, which
- stores previously entered commands. The first and last entries in this list
- must be blank, so declaring MAX_RECALL as 22 allows for the two blank strings
- plus 20 prior commands. The RECSUB macro generates a subscript into the list
- and handles modulo arithmetic so that the subscript is always between 0 and
- MAX_RECALL minus one.
- RECALL_LEN defines the longest line you can type into the get_str() routine.
- You can make it as long as you like. You can also expand MAX_RECALL to permit
- more than 20 commands in your buffer. All it costs is memory.
- The VMS command interpreter does not treat its history list as a circular
- buffer. You can't scroll around in one direction indefinitely. You can use the
- up-arrow until you reach the oldest command in the list, then you get a blank
- line and the up-arrow stops working. The down-arrow takes you one step toward
- the most recently entered command, and it too stops working at the end of the
- list.
- The get_str() routine begins on line 60. You pass it the address of the buffer
- in which to store the input line, and the length of that buffer. On line 73,
- get_str() goes into a "forever" loop, where it gathers up characters and
- performs the editing functions. It breaks out of the loop when RETURN is
- pressed.
- After calling the routine get_chr_esc(), which gets a character from the user,
- get-str() enters a large if statement that handles all of the cursor keys and
- control-characters. If the character isn't anything special, the function
- stores it in the buffer.
- The other functions in Listing 1 are fairly simple. cursor_right() moves the
- cursor one position to the right by sending out the character that is already
- under the cursor. This operation moves the cursor without relying on any
- terminal-dependent control or escape codes.
- cursor_left ()moves the cursor one position to the left. It depends upon the
- terminal responding to CTRL-H as a backspace. In a string in C, the character
- '\b' is a backspace, CTRL-H, hex value 08. Note that this value does not erase
- the character to the left of the cursor. To do that, as in the function
- clear_line(), a three-character string is sent out: backspace, space,
- backspace.
- get_str() calls the final function in Listing 1, get_char_esc(), to get a
- character. This function in turn calls sys_getchar() to get a character from
- the user, and passes most characters through unchanged. If, however,
- get_char_esc() detects an escape (ESC), it looks at the next two characters to
- see if they represent a VT100 terminal's arrow keys. A VT100 puts out the
- following escape codes when you press a cursor key:
- up ESC [ A
-
- down ESC [ B
- right ESC [ C
- left ESC [ D
- get_char_esc() translates these three-character sequences into a single
- integer, so get_str() can process it easily. For simplicity, these integers
- are the same four integer values the BIOS produces when a PC's arrow keys are
- struck. If you plan to use get_str() with a terminal whose arrow keys don't
- produce the VT100 escape sequences above, you must modify get_char_exc().
-
-
- sys getchar And sys_putchar
-
-
- get_str() uses these two functions for all of its input and output. I isolated
- them in this fashion for portability, since these functions were voted "most
- likely to change" when the boss says, "By the way, we're going to OS/2
- tomorrow."
- On lines 37-47 of Listing 2, sys_putchar() sends one character to the display.
- Its only frill is that it expands a line-feed character, '\n', to a
- carriage-return and line-feed pair. Depending on how you use get_str(), you
- may want to prevent this translation.
- sys_getchar(), found on lines 50-63, waits for a character from the user. In
- the MS-DOS environment, the function calls the Turbo C bioskey() function to
- wait for the next keystroke. bioskey() returns a 16-bit integer, whose high
- byte is the PC keyboard scan code, and the low byte is the ASCII value of that
- character (if any), or 00 if it's a non-ASCII key. sys_getchar() checks that
- the key is ASCII, and if so, strips off the scan code before returning it.
- If you need to do background processing while waiting for the next character,
- you could modify sys_getchar() to call an idle function until a key is ready.
- Listing 2 also contains a simple main() routine for testing the code. It reads
- an input line using get_str() and prints the results in quotes so you can see
- what was received. To leave the program, type quit.
-
-
- Storing More Commands
-
-
- VMS stores the most recently entered command into the bottom of the history
- buffer, so that a single up-arrow keystroke will recall it. If this command
- matches the previously entered command, get_str() doesn't store it, since
- doing so would only waste space in the buffer.
- As the program stands, if you enter two or more commands repeatedly,
- EDIT XX
- RUN XX
- these two commands will eventually occupy the entire history list. You might
- consider improving the code here: before entering a command into the list,
- remove the command if it already exists anywhere in the list. While this would
- allow the list to hold more unique commands, it would be the non-VMS thing to
- do.
-
-
- Limiting The Search
-
-
- Under VMS and in get_str(), striking an up-arrow or down-arrow immediately
- replaces the input line with the previous (or next) entry from the history
- list. If you accidentally hit an up-arrow while typing a command, everything
- you've typed is lost.
- Some MS-DOS keyboard enhancers have a different philosophy. If you type one or
- two characters and then press the up-arrow, you will scroll through a subset
- of the history list. Your subset is limited to those entries beginning with
- the characters you have already typed. If you wanted to recall a command that
- began with the letter D, you would simply type "D" and a few up-arrows until
- the command appeared.
- This feature resembles the "command completion" functionality built into some
- flavors of UNIX. On those systems, you can type a partial filename or command,
- press ESC, and the system will fill in the remainder of the name if it can.
- You could modify get_str() to include this functionality. The code that
- handles the up and down arrow keys, lines 120-142 of Listing 1, would skip any
- entries that didn't match the partial input line. You would also keep a copy
- of that partial line, otherwise the next up-arrow keystroke would overwrite
- it.
- Something as simple as an input-line reader can really enhance the usefulness
- of your programs. At the time I wrote this code, I was working for a die-hard
- VAX person, who was very pleased to see a VMS-like front-end attached to our
- embedded-system diagnostics. If your users are familiar with VMS, the
- get_str() routine will warm their hearts, too.
-
- Listing 1
- /*
- * Get input string, with VMS-style input line editing
- * and previous-command scrolling.
- *
- * Written for Turbo C 2.0 / Borland C++ 2.0
- * Bob Bybee, 2/91
- */
- #include <stdio.h>
- #include <string.h>
-
- /* ASCII key definitions */
- #define ESC_KEY 0x1b
- #define DELETE_KEY 0x7f
- #define BACKSPACE_KEY 0x08
- #define RETURN_KEY 0x0d
- #define CTRL(x) ((x) & 0x1f)
-
- /* Arbitrary values for tracking cursor key entry.
- * These happen to match PC BIOS scan codes, but any
- * unique values would work.
- */
- #define UP_ARROW 0x4800
- #define DOWN_ARROW 0x5000
-
- #define RIGHT_ARROW 0x4d00
- #define LEFT_ARROW 0x4b00
-
- /* MAX_RECALL is two greater than the number of lines
- * we want in the "lastlines" recall buffer.
- */
- #define MAX_RECALL 22
- #define RECSUB(x) (((x) + MAX_RECALL) % MAX_RECALL)
- #define RECALL_LEN 100
-
- static char lastlines[MAX_RECALL][RECALL_LEN];
- static int num_got; /* # chars in input buffer */
- static int cursor_pos; /* cursor position on line */
- static int last_ptr = 0; /* ptr to last line entered */
- static char erase_one[] = "\b \b"; /* erase one character */
- static char *str_ptr; /* ptr to current input string */
-
-
- /* prototypes for this file */
- static void clear_line( void );
- static int cursor_right( void );
- static int cursor_left( void );
- static int get_char_esc( void );
- static void put_str( char *str );
-
- /* external functions (see listing2.c) */
- void sys_putchar( char ch );
- int sys_getchar( void );
-
-
-
- /*
- * get_str() is called by main() to get a line of input.
- * The input line is placed in "str" and will be no
- * more than "len" characters.
- */
- void get_str( char *str, int len )
- {
- int i, c, curptr, insert_mode = 1;
-
- num_got = 0;
- cursor_pos = 0;
- str_ptr = str; /* copy the buffer pointer */
- curptr = RECSUB(lastptr + 1);
- lastlines[curptr][0] = '\0';
- lastlines[RECSUB(curptr + 1)][0] = '\0';
- if (len > RECALL_LEN - 1) /* limit len to RECALL_LEN */
- len = RECALL_LEN - 1;
-
- while (1)
- {
- c = get_char_esc();
-
- if (c == RETURN_KEY)
- break;
- else if (c == DELETE_KEY c == BACKSPACE_KEY)
- {
- if (cursor_left())
- {
-
- ++cursor_pos;
- for (i = cursor_pos; i < num_got; ++i)
- {
- str[i - 1] = str[i];
- sys_putchar(str[i]);
- }
- sys_putchar(' ');
- for(i = cursor_pos; i <= num_got; ++i)
- sys_putchar('\b');
- -num_got;
- -cursor_pos;
- }
- }
- else if (c == CTRL('X')) /* erase line? */
- clear_line();
- else if (c == CTRL('A')) /* insert/overtype? */
- insert_mode ^= 1;
- else if (c == CTRL('B')) /* beginning-of-line? */
- {
- while (cursor_left())
- ;
- }
- else if (c == CTRL('E')) /* end-of-line? */
- {
- while (cursor_right())
- ;
- }
- else if (c == CTRL('R')) /* recall last line? */
- {
- clear_line();
- strcpy(str, lastlines[lastptr]);
- if ((num_got = strlen(str)) > 0)
- {
- put_str(str);
- break;
- }
- }
- else if (c == UP_ARROW)
- {
- clear_line();
- if (lastlines[curptr][0] != '\0'
- lastlines[RECSUB(curptr - 1)][0] !: '\0')
- {
- curptr = RECSUB(curptr - 1);
- strcpy(str, lastlines[curptr]);
- put_str(str);
- cursor_pos = num_got = strlen(str);
- }
- }
- else if (c == DOWN_ARROW)
- {
- clear_line();
- if (lastlines[curptr][0] != '\0'
- lastlines[RECSUB(curptr + 1)][0] != '\0')
- {
- curptr = RECSUB(curptr + 1);
- strcpy(str, lastlines[curptr]);
- put_str(str);
- cursor_pos = num_got = strlen(str);
-
- }
- }
- else if (c == LEFT_ARROW)
- {
- if (cursor_pos > 0)
- {
- sys_putchar('\b');
- -cursor_pos;
- }
- }
- else if (c == RIGHT_ARROW)
- cursor_right();
- else if (' ' <= c && c < 0x7f && num_got < len - 1)
- {
- if (insert_mode)
- {
- /* Move right, all the characters
- * to the right of cursor_pos.
- */
- for (i = num_got; i > cursor_pos; -i)
- str[i] = str[i - 1];
- str[cursor_pos] = c;
- for (i = cursor_pos; i <= num_got; ++i)
- sys_putchar(str[i]);
- for (i = cursor_pos; i < num_got; ++i)
- sys_putchar('\b');
- ++num_got;
- ++cursor_pos;
- }
- else /* insert is off, use overtype mode */
- {
- str[cursor_pos] = c;
- sys_putchar(c);
- if (cursor_pos == num_got)
- ++num_got;
- ++cursor_pos;
- }
- }
- }
-
- str[num_got] = '\0';
- sys_putchar('\n');
-
- /* If this line is non-empty, and different
- * from the last one accepted, store it into
- * the recall buffer.
- */
- if (num_got > 0 && strcmp(str, lastlines[lastptr]) ! = 0)
- {
- lastptr = RECSUB(lastptr + 1);
- strcpy (lastlines[lastptr], str);
- }
- }
-
-
- /*
- * Move the cursor right one position, by echoing the
- * character it's currently over.
- * Return 1-OK, 0-can't move.
-
- */
- static int cursor_right( void )
- {
- if (cursor_pos < num_got)
- {
- sys_putchar(str_ptr[cursor_pos]);
- ++cursor_pos;
- return (1);
- }
- return (0);
- }
-
- /*
- * Move the cursor left one position, by echoing
- * a backspace. Return l-OK, 0-can't move.
- */
- static int cursor_left( void )
- {
- if (cursor_pos > 0)
- {
- sys_putchar('\b');
- -cursor_pos;
- return (1);
- }
- return (0);
- }
-
-
- /*
- * Erase all characters on the current line.
- */
- static void clear_line( void )
- {
- while (cursor_right())
- ; /* move right, to end of line */
- cursor_pos = 0;
- while (num_got > 0)
- {
- put_str(erase_one); /* then, erase to left */
- -num_got;
- }
- }
-
-
- /*
- * Get a character, with escape processing.
- * Handles special sequences like "ESC [ A" for up-arrow.
- * This function would need to be modified to handle
- * keyboards that are neither PC's nor VT-100's.
- */
- static int get_char_esc( void )
- {
- int ch;
-
- ch = sys_getchar();
- if (ch != ESC_KEY)
- return (ch);
-
- ch = sys_getchar();
-
- if (ch != '[')
- return (ch);
-
- ch = sys_getchar();
- if (ch == 'A')
- return (UP_ARROW); /* was ESC [ A */
- else if (ch == 'B')
- return (DOWN_ARROW); /* was ESC [ B */
- else if (ch == 'C')
- return (RIGHT_ARROW); /* was ESC [ C */
- else if (ch == 'D')
- return (LEFT_ARROW); /* was ESC [ D */
- else
- return (ch);
- }
-
-
- /*
- * Put a string to sys_putchar().
- */
- static void put_str( char *str )
- {
- while (*str != '\0')
- sys_putchar(*str++);
- }
- /* End of File */
-
-
- Listing 2
- /*
- * Listing 2: main() routine and test code for get_str().
- * Includes the OS-dependent routines sys_getchar()
- * and sys putchar().
- */
- #include <stdio.h>
- #include <stdlib.h>
- #include <bios.h>
- #include <string.h>
-
- #define INBUFSIZ 70
- char inbuf[INBUFSIZ + 1];
-
- void get_str( char *str, int len );
-
- void main( void )
- {
-
- while (1)
- {
- printf("\ntype 'quit' to quit.\nprompt> ");
- get_str(inbuf, INBUFSIZ);
-
- if (stricmp(inbuf, "quit") == 0)
- break;
-
- printf(" Got: \"%s\"\n", inbuf);
- }
- }
-
-
-
- /*********************************************************
- * The following two routines will need to be changed,
- * in order to use get_str() in a different environment.
- ********************************************************/
-
- /*
- * Put a character to the output device.
- * Expand \n to \r\n.
- */
- void sys_putchar( char ch )
- {
-
- putchar(ch);
- if (ch == "\n")
- putchar("\r");
- }
-
-
- /*
- * Get a character from the input device.
- * Use the BIOS call so we can detect arrow keys.
- */
- int sys_getchar( void )
- {
- int ch;
-
- ch=bioskey(0); /* wait and get a key */
-
- if ((ch & 0xff) != 0) /* if not an extended key, */
- ch &= 0xff; /* use only the
- ASCII part */
- return (ch);
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- An OS/2 MIDI Device Driver
-
-
- Carl M. Benda
-
-
- Mr. Benda is a programmer in the Charlotte Lab of the Services Sector Division
- of IBM. He holds an MS in computer science from the University of North
- Carolina - Charlotte, and an MS in materials engineering from Worcester
- Polytechnic Institute.
-
-
- The market currently offers many excellent software packages that include
- device drivers for the MPU-401, the standard PC Musical Instrument Digital
- Interface (MIDI). Almost all of the packages, however, run under MS-DOS, while
- almost none runs under OS/2. No commercial driver interface at the time of
- this writing is available for OS/2 2.0. (The 32-bit version of OS/2 currently
- in beta-test.) This article partially fills the gap by describing a simple
- device driver written mostly in C for handling interrupt-driven I/O to and
- from the Roland MPU-401.
-
-
- Writing Device Drivers For OS/2
-
-
- There are several reasons why so few commercial MIDI device drivers exist for
- OS/2. First, OS/2 drivers are more complex than their MS-DOS counterparts. To
- understand OS/2 device drivers, it is necessary to look at the structure of
- the operating system and see where the device driver fits into the operating
- system (Figure 1).
- In MS-DOS, any program can read and write to any portion of the RAM, whereas
- in OS/2, device drivers are required to handle hardware addresses and
- interrupt handling. OS/2 device drivers exist in a multitasking environment
- and so must handle requests from multiple processes and threads of execution
- within a single process. Before the driver can complete a request from one
- process, it may have to handle additional requests from a different process.
- OS/2 device drivers also have the special responsibility of being well behaved
- in terms of not disrupting other portions of the system. Since drivers run at
- the RING 0 execution level, they have ultimate access to all of the system's
- memory and resources. If an OS/2 device driver does not have a proper
- initialization routine, the system will crash at boot time.
- OS/2 device drivers in most implementations consist of two segments. The first
- segment is for data, and the second is for code. This scheme presents two
- problems when writing the code for the device driver entirely in C.
- First, the C compiler for OS/2 puts the code segment first in the program and
- the data segment last. An OS/2 device driver, however, must have the data
- segment first in the program. In this data segment, the first item must be the
- device header. The device header contains startup information that is critical
- to how the operating system treats that device driver. Second, it is necessary
- to prevent the C compiler from inserting the C startup code before the device
- header. I accomplish this by linking in a small assembly language function.
-
-
- Implementing The Device Driver
-
-
- This article presents all the necessary pieces for generating a MIDI device
- driver. This OS/2 device driver handles interrupts from the Roland MPU-IMC,
- which is a PS/2 Micro Channel adapter card that generates interrupts using IRQ
- 9. When the card generates an interrupt, the device driver gathers the
- information from the adapter's addresses: 0x331h for the commands and status,
- and 0x330h for any data bytes coming from other MIDI devices. From these
- addresses the device driver places the data into a shared memory buffer that
- may be accessed by an application program.
- The assembly code in Listing 1 serves as the startup code to replace the C
- compiler's normal startup code. The remaining portions for a minimal MIDI OS/2
- device driver appear in Listing 2, Listing 3, Listing 4, and Listing 5.
- The OS/2 device driver must be linked with a definitions file which has the
- name of the MIDI library as the entry point into the device driver. For this
- device driver, a definitions file would contain the lines
- LIBRARY MIDI
- PROTMODE
- The OS/2 config.sys file must reference the subdirectory and filename of the
- device driver so that at boot time, the OS/2 kernel can load the device driver
- and run the Init routine.
- OS/2 is much like DOS in the way it handles interrupts. When the hardware
- generates a level 9 interrupt, the operating system immediately runs the code
- entry point named by the SetIRQ routine. Thus OS/2 responds to the hardware in
- more of a real-time fashion.
-
-
- Conclusion
-
-
- The next logical step from this device driver would be to write an application
- that uses its interface.
- MIDI Data Management
- MIDI data is transmitted by sending and receiving single byte pieces of
- information. MIDI commands are composed of one, two, or three bytes of data
- arranged and transmitted one after another. The first byte is called the
- status byte, the following one or two bytes represent which note to be played
- and at what velocity, respectively. A protocol at the heart of the MIDI
- structure facilitates writing software designed to handle MIDI data. In the
- protocol, the command byte's most significant bit is used to determine if that
- particular byte is a command byte (bit set to 1), or a data byte (bit set to
- 0). By interpreting the first bit, a MIDI device could continuously play a
- note (note on status), until a new command byte was encountered.
- Figure 1
- -----------------------------------
- Your Application
- R ------------------------------
- I File Manager
- N R ---------------------
- I OS/2 KERNEL R
- 3 N --------------- I
- G Device Driver N
- --------------- G
- ____________________O
- 2
- -----------------------------
-
- ----------------------------------
-
- Listing 1 (mididrv.asm)
-
- ;
- ; Musical Instrument Digital Interface
- ; Assembler routines for an OS/2 Device
- ; Driver written in C.
- ;
- ; Copyright Carl M. Benda 1991
-
- EXTRN _main:near
- EXTRN _DevHlp:dword
- EXTRN _interrupt_handler:near
- PUBLIC _STRAT
- PUBLIC _INT_HNDLR
- PUBLIC _SetIRQ
- PUBLIC _ReadBytes
- PUBLIC _WriteBytes
- PUBLIC _int3
- PUBLIC _acrtused
-
- _DATA segment word public 'DATA'
- _DATA ends
-
- CONST segment word public 'CONST'
- CONST ends
-
- _BSS segment word public 'BSS'
- _BSS ends
-
- DGROUP group CONST, _BSS,_DATA
-
- _TEXT segment word public 'CODE'
-
-
-
-
- ASSUME cs:_TEXT, ds:DGROUP, es:NOTHING, ss:NOTHING
-
- .286P
- ;
- ; C startup routine for mididrv.c
- ; _Strat is called by 0S/2 during
- ; boot time.
- ;
-
- _STRAT proc far
- __acrtused: ;prevent startup C
- push 0 ;used as the dev value
- push es ;send request packet address
- push bx
- call _main ;call driver main function
- pop bx ;restore bx
- pop es ;restore es
- add sp,2 ;get rid of pushed 0
- retf
-
- _STRAT endp
-
-
- _INT_HNDLR proc far
- pusha ;save registers
-
- push ds
- push es
- call _interrupt_handler ;handle interrupts
- mov al,9h ;int value ;interrupt number.
- mov dl,31h ;devhlp EOI ;DevHlp End of int
- call [_DevHlp] ;call DevHlp entry
- pop es
- pop ds
- popa ;restore everything
- retf ;return to C function.
-
- _INT_HNDLR endp
-
- ;
- ; These next routines are used by the C code
- ; because these procedures need to call special
- ; device driver helper routines requiring
- ; hardware registers of specific values.
- ;
-
- _SetIRQ proc near
- push bp
- mov bp,sp
- ; flag located at bp + 8
- ; fnc located at bp + 6
- ; irq irq number at + 4
-
- mov bx,WORD PTR [bp+4] ;irq number
- mov ax,WORD PTR [bp+6] ;fnc entry
- mov dh,BYTE PTR [bp+8] ;share flag
- mov dl,1Bh ;SetIRQ DevHlp
- call [_DevHlp]
- leave
- ret
-
- _SetIRQ ENDP
-
- _ReadBytes proc near
- push bp
- mov bp,sp
- pusha
- push es
- push ds
-
- ; Physical Offset = 4
- ; Physical Segment = 6
- ; Device Driver = 8
- ; count = 12
-
- mov ax,WORD PTR [bp+6]
- mov bx,WORD PTR [bp+4]
- mov dl,15h ;PhysToVirt
- mov dh,00h ;result in es:di
- call [_DevHlp] ;useable addr.
-
- lds si,DWORD PTR [bp+ 8] ;source
- mov cx, WORD PTR [bp+12] ;bytes to move
- rep movsb ;copy data!
-
-
- mov dl,32h ;Must UnPhys!
- call [_DevHlp]
-
- pop ds
- pop es
- popa
- leave
- ret
-
- _ReadBytes ENDP
-
- _WriteBytes proc near
- push bp
- mov bp,sp
- pusha
- push es
- push ds
-
- ; Physical Offset = 4
- ; Physical Segment = 6
- ; Device Driver = 8
- ; count = 12
-
- mov ax,WORD PTR [bp+6]
- mov bx,WORD PTR [bp+4]
- mov dl,15h ;PhysToVirt
- mov dh,00h ;result in ds:si
- call [_DerHlp] ;useable addr.
-
- les di,DWORD PTR [bp+ 8] ;source
- mov cx, WORD PTR [bp+12] ;bytes to move
- rep movsb ;copy data!
-
- mov dl,32h ;Must UnPhys!
- cal1 [_DevH1p]
-
- pop ds
- pop es
- popa
- leave
- ret
-
- _WriteBytes ENDP
-
- _int3 proc near
- int 3
- ret
- _int3 endp
-
- _TEXT ends
- end
-
- ; End of File
-
-
- Listing 2 (midic.c)
- /****************************************************/
- /* */
- /* Musical Instrument Digital Interface 0S/2 */
-
- /* Device Driver, primarily written in Microsoft */
- /* C (tm). */
- /* */
- /* Copyright IBM 1991. */
- /* */
- /* */
- /****************************************************/
-
-
-
- #include "midic.h"
-
- /****************************************************/
- /* */
- /* Device Header must be the FIRST data area */
- /* */
- /****************************************************/
-
- DeviceHeader devhdr = {
- (void far *) -1,
- (DAW_CHR DAW_OPN DAW_LEVEL),
- {void near *) STRAT,
- {void near *) 0,
- {'M','I','D','I','$',' ',' ',' '},
- {0}
- };
-
- /****************************************************/
- /* */
- /* These next variables must be initialized all to */
- /* zero. Also, the MsgData is used not only to */
- /* display the Device Driver message during the */
- /* boot process, but is also used to write the end */
- /* of data offset into the Request Header. */
- /* */
- /****************************************************/
-
- unsigned far *DevHlp=0L;/* Storage area for DevHlps */
- unsigned char rx_queue[10]={0}; /* receiver queue */
- unsigned char tx_queue[10]={0}; /* transmitter queue*/
-
- InitExit far *InitExt=OL; /* pointer to InitExit */
- DeviceRead far *DevRead=OL; /* points to read strc */
- DeviceWrite far *DevWrite=OL;/* points to write str */
-
- unsigned char MsgData[] = "Midi Device Driver \r\n";
-
- /*********************************/
- /* */
- /* Device Initialization Routine */
- /* is called only once during */
- /* the OS/2 BOOT process. The */
- /* address of the DevHlp */
- /* */
- /*********************************/
-
- void Init(InitEntry far *InitPtr, int dev)
- {
-
-
- DevHlp = (unsigned far *)InitPtr->DevHlp;
- /* callable DevHlp */
- /* entry point.. */
-
-
-
- /*********************************/
- /* */
- /* During Initialization we can */
- /* install the interrupt handler */
- /* which will allow OS/2 to know */
- /* where the code is that will */
- /* run, each time the interrupt */
- /* 9 happens. */
- /* */
- /*********************************/
-
- DOSPUTMESSAGE(1, 8, devhdr.name);
- DOSPUTMESSAGE(1,strlen(MsgData),MsgData);
-
-
- SetIRQ(9,(void near *)INT_HNDLR,0);
-
-
- /* output initialization message */
-
- /* send back our cs and ds end values to os/2 */
-
- InitExt = (InitExit far *)InitPtr;
- InitExt->code_off = (unsigned short)last_code;
- InitExt->data_off = (unsigned short)MsgData+
- strlen(MsgData);
-
- return;
- }
-
- /* common entry point for calls to strat routines */
-
- void main(ReqHeader far *rp, int dev )
- {
-
- switch(rp->req_cmd)
- {
- case INIT:
-
- Init((InitEntry far *)rp,dev);
-
- rp->req_stat = DONE;
- return;
-
- case OPEN:
-
- /****************************/
- /* */
- /* During OPEN, place MPU */
- /* into "DUMB" mode. */
- /* */
- /****************************/
-
-
- outp(MIDI_CMD,DUMB_MODE);
-
- rp->req_stat = DONE;
- return;
-
- case CLOSE:
-
- /****************************/
- /* */
- /* During CLOSE, reset MPU */
- /* mode. */
- /* */
- /****************************/
-
- outp(MIDI_CMD,MPU RESET);
- rp->req_stat = DONE;
- return;
-
- case READ:
-
-
- rx_queue[0] = inp(MIDI_DATA);
- DevRead = (DeviceRead far *)rp;
-
- ReadBytes((unsigned long)DevRead->buff_addr,
- (unsigned long)rx_queue,
- 1);
-
- rp->req_stat = DONE;
- return;
-
-
- case WRITE:
-
- DevWrite = (DeviceWrite far *)rp;
-
- WriteBytes((unsigned long)DevWrite->buff_addr,
- (unsigned long)tx_queue,
- 1);
-
- rp->req_stat = DONE;
- return;
-
- case IOCTL:
- rp->req_stat = DONE;
- return;
-
- default:
- rp->req_stat = DONE;
- return;
-
- }
- }
-
- /***************************/
- /* */
- /* This interrupt handler */
- /* is called by _INT_HNDLR */
- /* from the assembler pro- */
-
- /* cedure. After return- */
- /* ing, the DevHlp End of */
- /* Interrupt function. */
- /* */
- /***************************/
-
- void near interrupt_handler ()
- {
- rx_queue[0] = inp(0x0330);
- }
-
- /*************************/
- /* */
- /* Last code offset.... */
- /* */
- /*************************/
-
- void last_code() {}
-
- /* End of File */
-
-
- Listing 3 (midic.h)
- /************************************/
- /* */
- /* MIDIC - 0S/2 device driver */
- /* Header File copyright */
- /* IBM 1991 */
- /* */
- /* */
- /* */
- /* This header file contains the */
- /* required OS/2 device driver */
- /* structures for the Drive header */
- /* the Request Header, the INIT_IN */
- /* packet, the INIT_OUT packet,and */
- /* for this particular device */
- /* driver, also the ReadWrite pack- */
- /* et. */
- /* */
- /************************************/
-
- void near last_code();
-
-
- extern void near STRAT();
- extern void near ReadBytes(unsigned long,
- unsigned long,
- unsigned short);
- extern void near WriteBytes(unsigned long,
- unsigned long,
- unsigned short);
- extern void near int3();
- extern void near SetIRQ(unsigned short,
- void near *,
- unsigned short);
- extern void near INT_HNDLR();
- extern int pascal far DOSPUTMESSAGE(unsigned int,
- unsigned int,
-
- unsigned char far *);
-
- #define MIDI_DATA 0x0330
- #define MIDI_CMD 0x0331
- #define DUMB_MODE 0x003f
- #define MPU_RESET 0x00ff
-
- #define DAW_CHR 0x8000
- #define DAW_IDC 0x4000
- #define DAW_IBM 0x2000
- #define DAW_SHR 0x1000
- #define DAW_OPN 0x0800
- #define DAW_LEVEL 0x0080
- #define DAW_GIO 0x0040
- #define DAW_CLK 0x0008
- #define DAW_NUL 0x0004
- #define DAW_SCR 0x0002
- #define DAW_KBD 0x0001
-
- #define ERR 0x8000
- #define DEV 0x4000
- #define BUSY 0x0200
- #define DONE 0x0100
-
- #define INIT 0x00
- #define MEDIA_CHECK 0x01
- #define BUILD_BPB 0x02
- #define READ 0x04
- #define READ_NO_WAIT 0x05
- #define INPUT_STATUS 0x06
- #define INPUT_FLUSH 0x07
- #define WRITE 0x08
- #define WRITE_VERIFY 0x09
- #define OUTPUT_STATUS 0x0a
- #define OUTPUT_FLUSH 0x0b
- #define OPEN 0x0d
- #define CLOSE 0x0e
- #define REMOVABLE 0x0f
- #define IOCTL 0x10
- #define RESET 0x11
- #define GET_DRIVE_MAP 0x12
- #define SET_DRIVE_MAP 0x13
- #define DEINSTALL 0x14
- #define PARTITIONABLE 0x16
- #define GET_FIXED_MAP 0x17
-
- typedef struct DeviceHdr {
- struct DeviceHdr far *PtrNextHdr; /* -1, no more */
- unsigned short HdrAttr; /* hdr attribute */
- void near *Strat; /* offset of_strat */
- void near *IDC; /* offset of IDC */
- unsigned char name[8]; /* name of driver */
- char reserved[8]; /* reserved. */
- } DeviceHeader;
-
- typedef struct RQpacket {
- unsigned char req_pac_len; /* length of packet */
- unsigned char req_unit; /* use for BLOCK DD */
- unsigned char req_cmd; /* which command... */
-
- unsigned short req_stat; /* returned status. */
- unsigned long req_resrvd; /* for use by OS/2. */
- unsigned long req_link; /* used for queueing*/
- } ReqHeader;
-
- typedef struct InitEnt {
- ReqHeader InitEntHdr; /* space for header */
- unsigned char units; /* number of units. */
- unsigned char far *DevHlp; /* Address of DevHlp*/
- unsigned char far *InitCmd; /* ptr to commands. */
- unsigned short drive; /* drive # block DD */
- } InitEntry;
-
- typedef struct InitExt {
- ReqHeader InitExtHdr; /* space for header */
- unsigned char units; /* number of units. */
- unsigned short code_off; /* offset of code. */
- unsigned short data_off; /* offset of data. */
- unsigned char far *BPBptr; /* address of BPB. */
- } InitExit;
-
- typedef struct Read {
- ReqHeader ReadHdr; /* space for header */
- unsigned char media_des; /* media descriptor */
- unsigned long buff_addr; /* physical address */
- unsigned short count; /* number of bytes */
- unsigned long start_sel; /* starting sel # */
- unsigned short reserved; /* for use by OS/2 */
- } DeviceRead;
-
- typedef struct Write {
- ReqHeader WriteHdr; /* space for header */
- unsigned char media_des; /* media descriptor */
- unsigned long buff_addr; /* physical address */
- unsigned short count; /* number of bytes */
- unsigned long start_sel; /* starting sel # */
- unsigned short reserved; /* for use by OS/2 */
- } DeviceWrite;
-
- /* End of File */
-
-
- Listing 4 (midic.def)
- LIBRARY MIDIC
- PROTMODE
-
-
- Listing 5 (makefile)
- # makefile for sample OS/2 device driver
-
- midic.sys: mididrv.obj midic.obj
- link /nod /noi /map
- mididrv+midic,midic.sys,midic,doscalls+slibce,midic.def;
-
-
- mididrv.obj: mididrv.asm
- masm -Mx -t -N -l mididrv.asm;
-
- midic.obj: midic.c midic.h
-
- cl -c -Asnw -Gs -G2 -Zl -Zp -Ox -Fc midic.c
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Image Processing
-
-
- Part 6: Advanced Edge Detection
-
-
-
-
- Dwayne Phillips
-
-
- Dwayne Phillips works as a computer and electronics engineer with the United
- States Department of Defense. He has a Ph.D. in electrical and computer
- engineering at Louisiana State University. His interests include computer
- vision, artificial intelligence, software engineering; and programming
- languages.
-
-
-
-
- Introduction
-
-
- This is the sixth in a series of articles on images and image processing. The
- first five articles in the series discussed reading and writing images (TIFF
- format), displaying them, printing them, histograms and histogram
- equalization, and basic edge detection. This article will cover some advanced
- techniques in edge detection.
- There are many different methods of edge detection. Image Processing Part 5:
- Writing Images to Files and Basic Edge Detection (CUJ, October 1991) discussed
- some basic techniques. This article discusses some unusual and advanced ideas
- and looks at four edge detectors. The first two do not use the convolution
- operation -- they use only subtraction. The third edge detector can vary the
- level of detail of the edges it will detect. The fourth edge detector will
- detect edges in unevenly lit images. Then an edge detector is used to enhance
- the appearance of an original image. Finally, these new operators are
- integrated into the C Image Processing System (CIPS). Photograph 1 shows the
- original image used by all the operators.
- The first edge detector is the homogeneity operator[1] which uses subtraction
- to find an edge. Figure 1 shows an example of this operator. The operator
- subtracts each of the pixels next to the center of a 3x3 area from the center
- pixel. The result is the maximum of the absolute value of these subtractions.
- Subtraction in a homogeneous region (one that is a solid gray level) produces
- zero and indicates an absence of edges. A region containing sharp edges, such
- as area 2 of Figure 1, has a large maximum.
- The first section of Listing 1 shows the homogeneity function. This function
- is similar in form to the edge detectors discussed in the previous article of
- this series. First, the function checks to see if the output image exists. If
- the output image does not exist, the homogeneity function creates it by
- calling create_allocate_tiff_file. The homogeneity function reads in the image
- array and goes into the processing loop.
- In the loop over ROWS and COLS, the code performs the subtraction and finds
- the maximum absolute value of the subtractions. The homogeneity operator
- requires thresholding (which you can specify). A perfectly homogeneous 3x3
- area is rare in an image. If you do not threshold, the result looks like a
- faded copy of the original. Thresholding at 30 to 50 for a 256 gray level
- image gives good results.
- Photograph 2 shows the result of the homogeneity operator. This operator gives
- a good rendition of the edges in the original house image. This is a quick
- operator that performs only subtraction -- eight operations per pixel -- and
- no multiplication.
- The next edge detector is the difference operator, another simple operator
- that uses subtraction. Recall that edge detection is often called image
- differentiation (detecting the slope of the gray levels in the image). The
- difference operator performs differentiation by calculating the differences
- between the pixels that surround the center of a 3x3 area.
- Figure 2 shows an example of the difference operator. The difference operator
- finds the absolute value of the differences between opposite pixels, the upper
- left minus lower right, upper right minus lower left, left minus right, and
- top minus bottom. The result is the maximum absolute value. The results shown
- in Figure 2 are similar but not exactly equal to those from the homogeneity
- operator in Figure 1.
- The second part of Listing 1 shows the difference_edge function, which is
- similar to the homogeneity function. The routine creates an output image file
- if necessary and reads in the input image array. The difference_edge function
- loops over the input image array and calculates the absolute values of the
- four differences. You can specify that difference_edge threshold the output.
- As in the homogeneity case, the difference operator requires thresholding.
- Photograph 3 shows the result of the difference edge detector. This result is
- similar to the shown in Photograph 2. The difference edge detector did detect
- more of the brick and mortar lines than the homogeneity operator. You can
- choose between the two edge detectors depending on how much detail you want.
- The difference operator is faster than the homogeneity operator. The
- difference operator uses only four integer subtractions per pixel, while the
- homogeneity operator uses eight subtractions per pixel. These compare to the
- nine multiplications and additions for the convolution-based edge detectors
- discussed in the fifth article.
- The next operator to examine is the difference of Gaussians edge detector,
- which allows you to vary the width of a convolution mask and adjust the detail
- in the output[2,3]. The results in Photograph 2 and Photograph 3 are good.
- Suppose, however, we wanted to detect only the edges of the large objects in
- the house image (door, windows, etc.) and not detect the small objects
- (bricks, leaves, etc.).
- You can use convolution masks and vary the width of the mask to eliminate
- details. If a mask is wide, then convolving an image will smooth out details,
- much like averaging. If you look at stock market prices by the minute, you see
- many variations. If you look at the average stock market price over each hour,
- the variations begin to disappear. If you look at the average stock market
- price over each week, then the variations disappear and you see general trends
- in prices. If you convolve an image with a wide, constant mask, you smooth the
- image. If you use a narrower, varying mask, the details remain.
- Figure 3 shows two example masks. These masks are difference of Gaussians or
- Mexican hat functions. The center of the masks is a positive peak (16 in the
- 7x7 masks -- 19 in the 9x9 mask). The masks slope downward to a small negative
- peak (-3 in both masks) and back up to zero. The curve in the 9x9 mask is
- wider than that in the 3x3 mask. Notice how the 9x9 mask hits its negative
- peak 3 pixels away from the center while the 7x7 masks hits its peak 2 pixels
- away from the center. Also, notice these masks use integer values. Most edge
- detectors of this type use floating point numbers that peak at +1. Using
- integers greatly increases the speed; my 80286 machine does not have a math
- co-processor and numerous floating point calculations are out of the question.
- Figure 4 illustrates how the narrower mask will detect small edges the wide
- mask misses. Each area in Figure 4 has a small pattern similar to the brick
- and mortar pattern in the house image. This pattern has small objects (bricks)
- with many edges. If you convolve the 7x7 mask in Figure 3 with the 7x7 area in
- Figure 4, the result is +40; the 7x7 mask detected an edge at the center of
- the 7x7 area. If you convolve the 9x9 mask in Figure 3 with the 9x9 area in
- Figure 4, the result is -20; the 9x9 mask did not detect any edges. The "hat"
- in the 9x9 mask was wide enough to smooth out the edges and not detect them.
- The first section of Listing 2 shows the two masks and the function
- gaussian_edge. gaussian_edge has the same form as the other edge detectors.
- The routine creates an output image file if necessary and reads the input
- image array. An additional size parameter (either 7 or 9) specifies mask
- width. The inner loop over a and b varies with this parameter. The processing
- portion is the same as the other convolution mask edge detectors presented in
- "Image Processing, Part 5." With gaussian_edge, you can use thresholding to
- produce a clear edge image or you can leave it off to show the strength of the
- edges.
- Photograph 4 shows the result of edge detection using the narrower 7x7 mask
- and Photograph 5 shows the result of the wider 9x9 mask. The narrower mask
- (Photograph 4) detected all the edges of the bricks, roof shingles, and
- leaves. The wider mask (Photograph 5) did not detect the edges of small
- objects, only edges of the larger objects. Photograph 4 may be too cluttered
- for your application, so you would use the wider mask. If you want fine
- detail, then the narrower mask is the one to choose.
- You can modify other edge detectors to detect edges on different size objects.
- The homogeneity operator can take the difference of the center pixel and a
- pixel that is two or three pixels away. The difference edge detector can take
- the difference of opposite pixels in a 5x5 or 7x7 area instead of a 3x3 area.
- The quick mask (see Part 5) can change from 3x3 to 5x5 with the center value
- equal to 4 and the four corners equal to -1. Try these changes as a homework
- project. You need the code in gaussian_edge that changes the size of the
- convolution and the call to the fix_edges function.
- One problem with detecting edges involves uneven lighting in images. The
- contrast-based edge detector[4] helps take care of this problem. In well lit
- areas of an image the edges have large differences in gray levels. If the same
- edge occurs in a poorly lit area of the image, the difference in gray levels
- is much smaller. Most edge detectors result in a strong edge in the well-lit
- area and a weak edge in the poorly-lit area.
- The contrast-based edge detector takes the result of any edge detector and
- divides it by the average value of the area. This division removes the effect
- of uneven lighting in the image. You find the average value of an area by
- convolving the area with a mask containing all 1s and dividing by the size of
- the area.
- Figure 5 illustrates the contrast-based edge detector. You can use almost any
- edge detector as the basis for this operation. Figure 5 uses the quick edge
- detector from the last article (Part 5). The edge in the well-lit area is an
- obvious and strong edge. Convolving the quick mask with this area yields 100.
- The edge in the poorly-lit area is easy to see, but convolving with the quick
- mask results in 10, a weak edge that thresholding would probably eliminate.
- This distinction should be avoided. The well-lit and poorly-lit edges are very
- similar; both change from one gray level to another gray level that is twice
- as bright.
- Dividing by the average gray level in the area corrects this discrepancy.
- Figure 5 shows the smoothing mask that calculates the average gray level.
- Convolving the well-lit area yields an average value of 83. Convolving the
- poorly-lit area yields an average value of 8. Dividing (integer division) the
- strong edge in the well-lit area by 83 yields 1. Dividing the weak edge by 8
- also result of 1. The two edges from unevenly-lit areas yield the same answer
- and you have consistent edge detection.
- The last section of Listing 1 shows the contrast_edge function that follows
- the same steps as the other edge detector functions. The difference is in the
- processing loop over a and b, which calculates two convolutions: sum_n (the
- numerator or quick edge output) and sum_d (the smoothing output). After the
- loops over a and b, divide sum_d by 9 and divide sum_n by this result. The
- e_mask at the beginning of Listing 1 replaces the quick mask (from Part 5),
- with every element in the quick mask multiplied by 9. You need the larger
- values -- dividing by the average gray level could reduce the strength of all
- edges to zero.
- Photograph 6 shows the result of the regular quick edge detector. Photograph 7
- shows the result of dividing the quick edge result by the average value to
- produce contrast-based edge detection. Notice the inside of the upstairs and
- downstairs windows. Photograph 6 (quick edge) shows edges inside the
- downstairs windows, but not inside the upstairs windows. Photograph 7
- (contrast-based) shows details inside the downstairs and upstairs windows.
- Refer to the original image (Photograph 1). The downstairs windows are shaded
- and the upstairs windows are not. Contrast-based edge detection gives better
- results in uneven lighting.
- You can use contrast-based edge detection with any edge detector. As a
- project, try modifying the homogeneity edge detector by dividing its result by
- the average gray level. But first multiply the result of homogeneity by a
- factor (9 or more) so dividing does not reduce edge strengths to zero. Modify
- any of the other edge detectors in a similar manner.
- A good application of edge detectors is to enhance edges and improve the
- appearance of an original image. Detect the edges in an image and then overlay
- these edges on top of the original image to accent its edges. The last section
- of Listing 2 shows the enhance_edges function, which repeats the quick_edge
- function from the last article with a few added lines of code. Examine the
- code right after the loops over a and b. If the result of convolution (the sum
- variable) is greater than a user-chosen threshold, then the output image is
- assigned that value. If not, then the output image is assigned the value from
- the input image. The result is the input image with its strong edges accented.
- Photograph 8 shows the result of enhancing the edges of Photograph 1. The
- edges of the bricks, the siding in the left, and the lines on the garage door
- are all sharper.
- You can use any edge detector to enhance the edges in an input image. You just
- add the option of taking the edge detector result or a value from the input
- image. An interesting project would be to use the 9x9 Gaussian mask to detect
- the edges of large objects and use these edges to enhance the input image.
- Some new code incorporates these edge detectors into the C Image Processing
- System (CIPS). Listing 3 shows the changes to the main program file cips.c. I
- modified case 8 to include the new types of edge detection (detect_type == 5,
- 6, 7, or 8). I also added case 9 for edge enhancement. The last part of
- Listing 3 shows the new version of show_menu that allows the user to select
- case 8 or 9.
- Listing 4 shows the new version of get_edge_options (file edge.c from Part 5).
- The new version allows you to select from among eight available edge
- detectors. You can select thresholding, specify a threshold value, and specify
- 7x7 or 9x9 Gaussian edge detection.
- Listing 4 shows the new version of the mainedge application program discussed
- in Part 5. You can run an edge detector over all of the 100x100 sections of an
- image without interacting with CIPS; instead mainedge uses command-line
- parameters. There are seven command-line parameters -- I can never remember
- the correct order. Just type mainedge <return> and the program will remind you
- of the order. mainedge loops over the 100x100 blocks and calls the specified
- edge detector function. The edge detector does the rest.
- Try these edge detectors and do a few of the projects. There a countless
- combinations to try and you can no doubt invent an edge detector tailored for
- your application.
- References
- 1. "Recognizing Objects in a Natural Environment: A Contextual Vision System
- (CVS)," Martin A. Fischler, Thomas M. Strat, Proceedings Image Understanding
- Workshop, pp. 774-796, Morgan Kaufman Publishers, May 1989.
- 2. Digital Image Processing, Kenneth R. Castleman, Prentice-Hall, 1979.
- 3. Vision in Man and Machine, Martin D. Levine, McGraw-Hill, 1985.
- 4. Contrast Based Edge Detection, R.P. Johnson, Pattern Recognition, Vol. 23,
- No. 3/4, pp. 311-318, 1990.
- Figure 1
- Area 1:
-
-
- 1 2 3
- 4 5 6
- 7 8 9
-
- Output of homogeneity edge detector is:
- max of {
- 5 - 1 5 - 2 5 - 3
-
- 5 - 4 5 - 6 5 - 7
-
- 5 - 8 5 - 9
- } = 4
-
-
- Area 2:
-
- 10 10 10
- 10 10 10
- 10 10 1
-
- Output of homogeneity edge detector is:
- max of {
- 10 - 10 10 - 10 10 - 10
-
- 10 - 10 10 - 10 10 - 10
-
- 10 - 10 10 - 1
- } = 9
-
-
- Area 3:
-
- 10 5 3
- 10 5 3
- 10 5 3
-
- Output of homogeneity edge detector is:
- max of {
- 5 - 10 5 - 5 5 - 3
-
- 5 - 10 5 - 3 5 - 10
-
- 5 - 5 5 - 3
- } = 5
-
- Figure 2
- Area 1:
-
- 1 2 3
- 4 5 6
- 7 8 9
-
- Output of difference edge detector is:
- max of {
- 1 - 9 7 - 3
-
- 4 - 6 2 - 8
- } = 8
-
-
-
- Area 2:
-
- 10 10 10
- 10 10 10
- 10 10 1
-
- Output of difference edge detector is:
- max of {
- 10 - 1 10 - 10
-
- 10 - 10 10 - 10
- } = 9
-
-
- Area 3:
-
- 10 5 3
- 10 5 3
- 10 5 3
-
- Output of difference edge detector is:
- max of {
- 10 - 3 10 - 3
-
- 10 - 3 5 - 5
- } = 7
-
- Figure 3
- 7x7 mask
- 0 0 -1 -1 -1 0 0
- 0 -2 -3 -3 -3 -2 0
- -1 -3 5 5 5 -3 -1
- -1 -3 5 16 5 -3 -1
- -1 -3 5 5 5 -3 -1
- 0 -2 -3 -3 -3 -2 0
- 0 0 -1 -1 -1 0 0
-
- 9x9 mask
- 0 0 0 -1 -1 -1 0 0 0
- 0 -2 -3 -3 -3 -3 -3 -2 0
- 0 -3 -2 -1 -1 -1 -2 -3 0
- -1 -3 -1 9 9 9 -1 -3 -1
- -1 -3 -1 9 19 9 -1 -3 -1
- -1 -3 -1 9 9 9 -1 -3 -1
- 0 -3 -2 -1 -1 -1 -2 -3 0
- 0 -2 -3 -3 -3 -3 -3 -2 0
- 0 0 0 -1 -1 -1 0 0 0
-
- Figure 4
- 7x7 area with lines
-
- 0 10 0 10 0 10 0
- 0 0 0 0 0 0 0
- 10 0 10 0 10 0 10
- 0 0 0 0 0 0 0
- 0 10 0 10 0 10 0
- 0 0 0 0 0 0 0
-
- 10 0 10 0 10 0 10
-
- result of convolution with 7x7 mask = 40
-
-
- 9x9 area with lines
- 0 0 0 0 0 0 0 0 0
- 10 0 10 0 10 0 10 0 10
- 0 0 0 0 0 0 0 0 0
- 0 10 0 10 0 10 0 10 0
- 0 0 0 0 0 0 0 0 0
- 10 0 10 0 10 0 10 0 10
- 0 0 0 0 0 0 0 0 0
- 0 10 0 10 0 10 0 10 0
- 0 0 0 0 0 0 0 0 0
-
- result of convolution with 9x9 mask = -20
-
- Figure 5
- Edge Detector Mask
- -1 0 -1
- 0 4 0
- -1 0 -1
-
- Edge in well lit area
-
- 50 100 100
- 50 100 100 convolution with edge mask yields:
- 50 100 100 400 - 50 - 50 - 100 - 100 = 100
-
- Edge in poorly lit area
-
- 5 10 10
- 5 10 10 convolution with edge mask yields:
- 5 10 10 40 - 5 - 5 - 10 - 10 = 10
-
-
- Smoothing mask
-
- 1 1 1
- 1/9 * 1 1 1
- 1 1 1
-
- convolution of smoothing mask with edge in well lit area
- yields:
- 50+50+50+100+100+100+100+100+100 / 9 = 750/9 = 83
-
- convolution of smoothing mask with edge in poorly lit
- area yields:
- 5+5+5+10+10+10+10+10+10 / 9 = 75/9 = 8
-
- dividing original convolution by the smoothing mask
- result:
-
- edge in well lit area: 100 / 83 = 1
-
- edge in poorly lit area: 10 / 8 = 1
-
- Photo 1 House Image
-
- Photo 2 Result of Homogeneity Edge Detector
- Photo 3 Result of Difference Edge Detector
- Photo 4 Result of Narrow Gaussian Edge Detector
- Photo 5 Result of Wide Gaussian Edge Detector
- Photo 6 Result of Quick Edge Detector
- Photo 7 Result of Contrast-Based Edge Detector
- Photo 8 Result of Edge Enhancement
-
- Listing 1 (edge2.c)
- /***************************************************
- *
- * Functions: This file contains
- * homogeneity
- * difference_edge
- * contrast_edge
- *
- * Purpose:
- * These functions implement several
- * types of advanced edge detection.
- *
- * External Calls:
- * wtiff.c - does_not_exist
- * round_off_image_size
- * create_allocate_tiff_file
- * write_array_into_tiff_image
- * tiff.c - read_tiff_header
- * rtiff.c- read_tiff_image
- * numcvrt.c - get_integer
- * edge.c - fix_edges
- *
- * Modifications:
- * 26 March 1991 - created
- *
- *****************************************************/
-
-
-
- #include "d:\cips\cips.h"
-
-
-
- short e_mask[3][3] = {
- {-9, 0, -9}
- { 0, 36, 0},
- {-9, 0, -9} };
-
- short contrast[3][3] = {
- { 1, 1, 1},
- { 1, 1, 1},
- { 1, 1, 1}};
-
-
- /**************************************************
- *
- * homogeneity(...
- *
- * This function performs edge detection by looking
- * for the absence of an edge. The center of a
- * 3x3 area is replaced by the absolute value of
-
- * the max difference between the center point
- * and its 8 neighbors.
- *
- *****************************************************/
-
-
- homogeneity(in_name, out_name, the_image, out_image,
- il, ie, ll, le, threshold, high)
- char in_name[], out_name[];
- int high, il, ie, ll, le, threshold;
- short the_image[ROWS][COLS], out_image[ROWS][COLS];
- {
- int a, b, absdiff, absmax, diff, i, j,
- length, max, max_diff, new_hi, new_low, width;
-
- struct tiff_header_struct image_header;
-
- if(does_not exist(out_name)){
- printf("\n\nHOMO> output file does not exist %s",
- out_name);
-
- read_tiff_header(in_name, &image_header);
- round_off_image_size(&image_header,
- &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate_tiff_fi1e(out_name, &image_header,
- out_image);
- } /* ends if does_not_exist */
-
- read_tiff_header(in_name, &image_header);
-
- new_hi = 250;
- new_low = 16;
- if(image_header.bits_per_pixel == 4){
- new_hi = 10;
- new_low = 3;
- }
-
- max = 255;
- if(image_header.bits_per_pixel == 4)
- max = 16;
- read_tiff_image(in_name, the_image, il, ie, ll, le);
-
- for(i=0; i<ROWS; i++)
- if((i%10) == 0) printf("%3d", i);
- for(j=0; j<COLS; j++)
- out_image[i][j] = 0;
-
- for(i=1; i<ROWS-1; i++){
- for(j=1; j<COLS-1; j++){
-
- max_diff = 0;
- for(a=-1; a<=1; a++){
- for(b=-1; b<=1; b++){
-
- diff = the_image[i][j] - the_image[i+a][j+b];
- absdiff = abs(diff);
- if(absdiff > max_diff) max_diff = absdiff;
-
-
- } /* ends loop over b */
- } /* ends loop over a */
-
- out_image[i][j] = max_diff;
- } /* ends loop over j */
- } /* ends loop over i */
-
-
- /* if desired, threshold the output image */
- if(threshold == 1){
- for(i=0; i<ROWS; i++){
- for(j=0; j<COLS; j++){
- if(out_image[i][j] > high){
- out_image[i][j] = new_hi;
- }
- else{
- out_image[i][j] = new_low;
- }
- }
- }
- } /* ends if threshold == 1 */
-
- fix_edges(out_image, 1);
-
- write_array_into_tiff_image(out_name, out_image,
- il, ie, ll, le);
-
- } /* ends homogeneity */
-
- /**************************************************
- *
- * difference_edge(...
- *
- * This function performs edge detection by looking
- * at the differences in the pixels that surround
- * the center point of a 3x3 area. It replaces the
- * center point with the absolute value of the
- * max difference of:
- * upper left - lower right
- * upper right - lower left
- * left - right
- * top - bottom
- *
- *********************************************/
-
- difference_edge(in_name, out_name, the_image, out_image,
- il, ie, ll, le, threshold, high)
- char in_name[], out_name[];
- int high, il, ie, ll, le, threshold;
- short the_image[ROWS][COLS], out_image[ROWS][COLS];
- }
- int a, b, absdiff, absmax, diff, i, j,
- length, max, max_diff, new_hi, new_low, width;
-
- struct tiff_header_struct image_header;
-
-
- if(does_not_exist(out_name)){
-
- printf("\n\nDIFF> output file does not exist %s",
- out_name)
- read_tiff_header(in_name, &image_header);
- round_off_image_size(&image_header,
- &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate_tiff_file(out_name, &image_header,
- out_image);
- } /* ends if does_not_exist */
-
- read_tiff_header(in_name, &image_header);
-
- new_hi = 250;
- new_low = 16;
- if(image_header.bits_per_pixel == 4){
- new_hi = 10;
- new_low = 3;
- }
-
- max = 255;
- if(image_header.bits_per_pixel == 4)
- max = 16;
-
- read_tiff_image(in_name, the_image, il, ie, ll, le);
-
- for(i=0 i<ROWS; i++)
- for(j=0; j<COLS; j++)
- out_image[i] [j] = 0;
-
- for(i=1; i<ROWS-1; i++){
- if(i%0) == 0) printf("%3d", i);
- for(j=1; j<COLS-1; j++){
-
- max_diff = 0;
- absdiff = abs(the_image[i-1][j-1] -
- the_image[i+1][j+l]);
- if(absdiff > max_diff) max_diff = absdiff;
-
- absdiff = abs(the_image[i-1][j+1] -
- the_image[i+1][j-1]);
- if(absdiff > max_diff) max_diff = absdiff;
-
- absdiff = abs(the_image[i][j-l] -
- the_image[i][j+1] );
- if(absdiff > max_diff) max_diff = absdiff;
-
- absdiff = abs(the_image[i-1][j] -
- the_image[i+1][j]);
- if(absdiff > max_diff) max_diff = absdiff;
-
- out_image[i][j] = max_diff;
-
- } /* ends loop over j */
- } /* ends loop over i */
-
- /* if desired, threshold the output image */
- if(threshold == 1){
- for(i=0; i<ROWS; i++){
-
- for(j=0; j<COLS; j++){
- if(out_image[i][j] > high){
- out_image[i][j] = new_hi;
- }
- else{
- out_image[i][j] = new_low;
- }
- }
- }
- } /* ends if threshold == 1 */
-
-
-
- fix_edges(out_image, 1);
- write_array_into_tiff_image(out_name, out_image,
- il, ie, ll, le);
-
-
- } /* ends difference_edge */
-
-
- /*************************************************
- *
- * contrast_edge(...
- *
- * The edge detector uses the basic quick edge
- * detector mask and then divides the result
- * by a contrast smooth mask. This implements
- * Johnson's contrast based edge detector.
- *
- **************************************************/
-
- contrast_edge(in_name, out_name, the_image, out_image,
- il, ie, ll, le, threshold, high)
- char in_name[], out_name[];
- int high, il, ie, ll, le, threshold;
- short the_image[ROWS][COLS], out_image[ROWS][COLS];
- {
- int ad, d;
- int a, b, absdiff, absmax, diff, i, j,
- length, max, new_hi, new_low, sum_d, sum_n, width;
-
- struct tiff_header_struct image_header;
-
-
- if(does_not_exist(out_name)){
- printf("\n\nCONTRAST> output file does not exist %s",
- out_name);
- read_tiff_header(in_name, &image_header);
- round_off_image_size(&image_header,
- &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate_tiff_file(out_name, &image_header,
- out_image);
- } /* ends if does_not_exist */
- read_tiff_header(in_name, &image_header);
-
- new_hi = 250;
-
- new_low = 16;
- if(image_header.bits_per_pixel == 4){
- new_hi = 10;
- new_low = 3;
- }
-
- max = 255;
- if(image_header.bits_per_pixel == 4)
- max = 16;
-
- read_tiff_image(in_name, the_image, il, ie, ll, le);
-
- for(i=0; i<ROWS; i++)
- for(j=0; j<COLS; j++)
- out_image[i][j] = 0;
-
- for(i=1; i<ROWS-1; i++){
- if((i%10) == 0) printf("%3d", i);
- for(j=1; j<COLS-1; j++){
-
- sum_n = 0;
- sum_d = 0;
-
- for(a=-1; a<2; a++){
- for(b=-1; b<2; b++){
- sum_n = sum_n + the_image[i+a][j+b] *
- e_mask[a+1][b+1];
- sum_d = sum_d + the_image[i+a][j+b] *
- contrast [a+1][b+1];
- }
- }
-
- d = sum_d / 9;
- if(d ==0)
- d = 1;
-
- out_image[i] [j] = sum_n/d;
-
- if(out_image[i][j] > max) out_image[i][j] = max;
- if(out_image[i][j] < 0) out_image[i][j] = 0;
-
- } /* ends loop over j */
- } /* ends loop over i */
-
-
- /* if desired, threshold the output image */
- if(threshold == 1){
- for(i=0; i<ROWS; i++){
- for(j=0; j<COLS; j++){
- if(out_image[i][j] > high){
- out_image[i][j] = new_hi;
- }
- else {
- out_image[i] [j] = new low;
- }
- }
- }
- } /* ends if threshold == 1 */
-
-
-
- fix_edges(out_image, 1);
- write_array_into_tiff_image(out_name, out_image,
- il, ie, ll, le);
-
- } /* ends contrast_edge */
-
- /* End of File */
-
-
- Listing 2 (edge3.c)
- /***********************************************
- *
- * file d:\cips\edge3.c
- *
- * Functions: This file contains
- * gaussian_edge
- * enhance_edges
- *
- * Purpose:
- * These functions implement several
- * types of advanced edge detection.
- *
- * External Calls:
- * wtiff.c - does_not_exist
- * round_off_image_size
- * create_allocate_tiff_file
- * write_array_into_tiff_image
- * tiff.c-read tiff header
- * rtiff.c-read_tiff_image
- * numcvrt.c-get_integer
- * edge.c-fix_edges
- *
- * Modifications:
- * 26 March 1991 - created
- *
- ***********************************************/
-
-
- #include "d:\cips\cips.h"
-
-
- short enhance_mask[3][3] = {
- {-1, 0, -1},
- { 0, 4, 0},
- {-1, 0, -1} };
-
-
- short g7[7][7] = {
- { 0, 0, -1, -1, -1, 0, 0},
- { 0, -2, -3, -3, -3, -2, 0},
- { -1, -3, 5, 5, 5, -3, -1},
- { -1, -3, 5, 16, 5, -3, -1},
- { -1, -3, 5, 5, 5, -3, -1},
- { 0, -2, -3, -3, -3, -2, 0},
- { 0, 0, -1, -1, -1, 0, 0}};
-
- short g9[9] [9] = {
- { 0, 0, 0, -1, -1, -1, 0, 0, 0},
-
- { 0, -2, -3, -3, -3, -3, -3, -2, 0},
- { 0, -3, -2, -1, -1, -1, -2, -3, 0},
- { -1, -3, -1, 9, 9, 9, -1, -3, -1},
- { -1, -3, -1, 9, 19, 9, -1, -3, -1},
- { -1, -3, -1, 9, 9, 9, -1, -3, -1},
- { 0, -3, -2, -1, -1, -1, -2, -3, 0},
- { 0, -2, -3, -3, -3, -3, -3, -2, 0},
- { 0, 0, 0, -1, -1, -1, 0, 0, 0}};
-
-
-
- /******************************************************************
- *
- * gaussian_edge(...
- *
- *
- *******************************************************************/
-
- gaussian_edge(in_name, out_name, the_image, out_image,
- il, ie, ll, le, size, threshold, high)
- char in_name[], out_name[];
- int high, il, ie, ll, le, size, threshold;
- short the_image[ROWS] [COLS], out_image[ROWS] [COLS];
- {
-
- char response[80];
- long sum;
- int a, b, absdiff, absmax, diff, i, j,
- length, lower, max, new_hi, new_low,
- scale, start, stop, upper, width;
-
- struct tiff_header_struct image_header;
-
- if(does_not_exist(out_name)){
- printf("\n\nGAUSSIAN> output file does not exist %s",
- out_name);
- read_tiff_header(in_name,&image_header);
- round_off_image_size(&image_header,
- &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate_tiff_file(out_name, &image_header,
- out_image);
- } /* ends if does_not_exist */
-
- read_tiff_header(in_name, &image_header);
-
- new_hi = 250;
- new_low = 16;
- if(image_header.bits_perpixel == 4){
- new_hi = 10;
- new_low = 3;
- }
-
- max = 255;
- if(image_header.bits_per_pixel == 4)
- max = 16;
-
- if(size == 7){
-
- lower = -3;
- upper = 4;
- start = 3;
- stop = ROWS-3;
- scale = 2;
- }
-
- if(size == 9){
- lower = -4;
- upper = 5;
- start = 4;
- stop = ROWS-4;
- scale = 2;
- }
-
-
- read_tiff_image(in_name, the_image, il, ie, ll, le);
-
- for(i=0; i<ROWS; i++)
- for(j=0; j<COLS; j++)
- out_image[i] [j] = 0;
-
-
- for(i=start; i<stop; i++){
- if ((i%10) == 0) printf(" i=%d", i);
- for(j=start; j<stop; j++){
-
- sum = 0;
-
- for(a=lower; a<upper; a++){
- for(b=lower; b<upper; b++){
- if(size == 7)
- sum = sum + the_image[i+a] [j+b] *
- g7 [a+3] [b+3];
- if(size == 9)
- sum = sum + the_image[i+a] [j+b] *
- g9 [a+4] [b+4];
- } /* ends loop over a */
- } /* ends loop over b */
-
- if(sum < 0) sum = 0;
- if(sum > max) sum = max;
- out_image[i] [j] = sum;
-
- } /* ends loop over j */
- } /* ends loop over i */
-
- /* if desired, threshold the output image */
- if(threshold == 1){
- for(i=0; i<ROWS; i++){
- for(j=0; j<COLS; j++){
- if(out_image[i] [j] > high){
- out_image[i][j] = new_hi;
- }
- else{
- out_image[i] [j] = new_low;
- }
- }
- }
-
- } /* ends if threshold == 1 */
-
-
- fix_edges(out_image, size/2);
-
- write_array_into_tiff_image(out_name, out_image,
- il, ie, ll, le);
-
- } /* ends gaussian_edge */
-
-
- /*********************************************
- *
- * enhance_edges(...
- *
- * This function enhances the edges in an
- * input image and writes the enhanced
- * result to an output image. It operates
- * much the same way as detect_edges
- * except it uses only one type of mask.
- *
- * The threshold and high parameters perform
- * a different role in this function. The
- * threshold parameter does not exist. The
- * high parameter determines if the edge is
- * strong enough to enhance or change the
- * input image.
- *
- *******************************************/
-
-
- enhance_edges(in_name, out_name, the_image, out_image,
- il, ie, ll, le, high)
- char in_name[], out_name[];
- int high, il, ie, ll, le;
- short the_image[ROWS][COLS], out_image[ROWS][COLS];
-
- {
- int a, b, i, j, k,
- length, max, new_hi,
- new_lo, sum, width;
- struct tiff_header_struct image_header;
-
-
- if(does_not_exist(out_name)){
- printf("\n\nEE> output file does not exist %s",
- out_name);
- read_tiff_header(in_name, &image_header);
- round_off_image_size(&image_header,
- &length, &width);
- image_header.image_length = length*ROWS;
- image_header.image_width = width*COLS;
- create_allocate_tiff_file(out_name, &image_header,
- out_image);
- } /* ends if does_not_exist */
-
- read_tiff_header(in_name, &image_header);
-
- max = 255;
-
-
- if(image_header.bits_per_pixel == 4)
- max = 16;
-
-
- read_tiff_image(in_name, the_image, il, ie, ll, le);
-
- /* Do convolution over image array */
- for(i=1; i<ROWS-1; i++){
- if((i%10) == 0) printf("%d ", i);
- for(j=1; j<COLS-1; j++){
- sum = 0;
- for(a=-1; a<2; a++){
- for(b=-1; b<2; b++){
- sum = sum +
- the_image[i+a] [j+b] *
- enhance_mask [a+1] [b+1];
-
- }
- }
- if(sum < 0) sum = 0;
- if(sum > max) sum = max;
- if(sum > high)
- out_image[i] [j] = max;
- else
- out_image[i][j] = the_image[i][j];
- } /* ends loop over j */
- } /* ends loop over i */
-
- fix_edges(out_image, 1);
-
-
- write_array_into_tiff_image(out_name, out_image,
- il, ie, ll, le);
- } /* ends enhance_edges */
- /* End of File */
-
-
- Listing 3
- case 8: /* perform edge detection */
- printf("\nCIPS> Enter input image name\n");
- get_image_name(name);
- printf("\nCIPS> Enter output image name\n");
- get_image_name(name2);
- get_parameters(&il, &ie, &ll, &le);
- get_edge_options(&detect_type, &detect_threshold,
- &high, &size);
- if(detect_type == 1
- detect_type == 2
- detect_type == 3)
- detect_edges(name, name2, the_image, out_image,
- il, ie, ll, le, detect_type,
- detect_threshold, high);
- if(detect_type == 4)
- quick_edge(name, name2, the_image, out_image,
- il, ie, ll, le, detect_threshold,
- high);
- if(detect_type == 5)
- homogeneity(name, name2, the_image, out_image,
-
- il, ie, ll, le, detect_threshold,
- high);
- if(detect_type == 6)
- difference_edge(name, name2, the_image, out_image,
- il, ie, ll, le, detect_threshold,
- high);
- if(detect_type == 7)
- contrast_edge(name, name2, the_image, out_image,
- il, ie, ll, le, detect_threshold,
- high);
- if(detect_type == 8)
- gaussian_edge(name, name2, the_image, out_image,
- il, ie, ll, le, size,
- detect_threshold, high);
- break;
-
- case 9: /* edge enhancement */
- printf("\nCIPS> Enter input image name\n");
- get_image_name(name);
- printf("\nCIPS> Enter output image name\n");
- get_image_name(name2);
- get_parameters(&il, &ie, &ll, &le);
- printf("\nCIPS> Enter high threshold parameter");
- printf(" \n\t__\b\b\b");
- get_integer(&high);
- enhance_edges(name, name2, the_image, out_image,
- il, ie, ll, le, high);
- break;
-
- .
- .
- .
-
- /**************************************************
- *
- * show_menu(..
- *
- * This function displays the CIPS main menu.
- *
- ***************************************************/
- show_menu()
- {
-
- printf("\n\n\nWelcome to CIPS");
- printf("\nThe C Image Processing System");
- printf("\nThese are you choices:\n");
- printf("\n\t1. Display image header");
- printf("\n\t2. Show image numbers");
- printf("\n\t3. Print image numbers");
- printf("\n\t4. Display image (VGA & EGA only)");
- printf("\n\t5. Display or print image using halftoning");
- printf("\n\t6. Print graphics image using dithering");
- printf("\n\t7. Print or display histogram numbers");
- printf("\n\t8. Perform edge detection");
- printf("\n\t9. Perform edge enhancement");
- printf("\n\t20. Exit system");
- printf("\n\nEnter choice _\b");
-
- } /* ends show_menu */
-
-
- /* End of File */
-
-
- Listing 4
- /******************************************************
- *
- * get_edge_options(...
- *
- * This function queries the user for the
- * parameters need to perform edge
- * detection.
- *
- *****************************************************/
-
-
- get_edge_options(detect_type, threshold, high, size)
- int *detect_type, *high, *size, *threshold;
- {
- int not_finished, response;
- not_finished = 1;
- while(not_finished){
-
- printf("\nThe Edge Detector options are:\n");
- printf("\n\t1. Type of edge detector is %d", *detect_type);
- printf("\n\t (recall 1=Prewitt 2=Kirsch");
- printf("\n\t 3=Sobel 4=quick");
- printf("\n\t 5=homegeneity 6=difference");
- printf("\n\t 7=contrast 8=gaussian");
- printf("\n\2. Threshold output is %d (0=off 1=on)", *threshold);
- printf("\n\t3. High threshold is %d", *high);
- printf("\n\t4. Size is %d (gaussian only)", *size);
- printf("\n\nEnter choice (0 = no change) _\b");
-
-
- get_integer(&response);
-
- if(response == 0){
- not_finished = 0;
- }
-
-
- if(response == 1){
- printf("\n\nEnter type of edge detector");
- printf("\n\t (recall 1=Prewitt 2=Kirsch");
- printf("\n\t 3=Sobel 4=quick");
- printf("\n\t 5=homogeneity 6=difference");
- printf("\n\t 7=contrast 8=gaussian");
- printf("\n _\b");
- get_integer(detect_type);
- }
-
- if(response == 2){
- printf("\n\nEnter threshold output (0=off 1=on)");
- printf("\n _\b");
- get_integer(threshold);
- }
-
- if(response == 3){
-
- printf("\n\nEnter high threshold");
- printf("\n _\b");
- get_integer(high);
- }
-
- if(response == 4){
- printf("\n\nEnter size for gaussian (7 or 9)");
- printf("\n _\b");
- get_integer(size);
- }
- } /* ends while not_finished */
-
- } /* ends get_edge_options */
- /* End of File */
-
-
- Listing 5 (mainedge.c)
- /***********************************************
- *
- * Purpose:
- * This file contains the main calling
- * routine in an edge detection program.
- *
- * External Calls:
- * gin.c - get_image_name
- * numcvrt.c - get_integer
- * int convert
- * tiff.c - read_tiff_header
- * edge.c - quick_edge
- * detect_edges
- * edge2.c - homogeneity
- * difference_edge
- * contrast_edge
- * edge3.c - gaussian_edge
- * enhance_edges
- *
- *
- * Modifications:
- * 2 February 1991 - created
- *
- ***********************************************/
-
- #include "d:\cips\cips.h"
-
-
-
- short the_image[ROWS][COLS];
- short out_image[ROWS][COLS];
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
-
- char name[80], name2[80];
-
- int count, i, ie, il, j, le, length, ll, size,
- t, type, v, width;
-
-
-
- struct tiff_header_struct image_header;
-
- _setvideomode(_TEXTC80); /* MSC 6.0 statements */
- _setbkcolor(1);
- _settextcolor(7);
- _clearscreen(_GCLEARSCREEN);
-
- if(argc < 7){
- printf("\n\nNot enough parameters:");
- printf("\n");
- printf("\n usage: mainedge in-file out-file type ");
- printf(" threshold? threshold-value size");
- printf("\n recall type: 1-Prewitt 2-Kirsch 3-Sobel");
- printf("\n 4-quick 5-homogeneity 6-difference");
- printf("\n 7-contrast 8-gaussian 9-enhance");
- printf("\n threshold? 1-threshold on 2-threshold off\n");
- exit(0);
- }
-
- strcpy(name, argv[1]);
- strcpy(name2, argv[2]);
- int_convert(argv[3], &type);
- int_convert(argv[4], &t);
- int_convert(argv[5], &v);
- int_convert(argv[6], &size);
-
- il = 1;
- ie = 1;
- ll = ROWS+1;
- le = COLS+1;
-
- read_tiff_header(name, &image_header);
-
- length = (90 + image_header.image_length)/ROWS;
- width = (90 +image_header.image_width)/COLS;
- count = 1;
- printf("\nlength=%d width=%d", length, width);
-
- for(i=0; i<length; i++){
- for(j=0; j<width; j++){
- printf("\nrunning %d of %d", count, length*width);
- count++;
- if(type == 9)
- enhance_edges(name, name2, the_image, out_image,
- il+i*ROWS, ie+j*COLS, ll+i*ROWS,
- le+j*COLS, v);
- if(type == 8)
- gaussian_edge(name, name2, the_image, out_image,
- il+i*ROWS, ie+j*COLS, ll+i*ROWS,
- le+j*COLS, size, t, v);
- if(type == 7)
- contrast_edge(name, name2, the_image, out_image,
- il+i*ROWS, ie+j*COLS, ll+i*ROWS,
- le+j*COLS, t, v);
- if(type == 6)
- difference_edge(name, name2, the_image, out_image,
- il+i*ROWS, ie+j*COLS, ll+i*ROWS,
- le+j*COLS, t, v);
-
- if(type == 5)
- homogeneity(name, name2, the_image, out_image,
- il+i*ROWS, ie+j*COLS, ll+i*ROWS,
- le+j*COLS, t, v);
- if(type == 4)
- quick_edge(name, name2, the_image, out_image,
- il+i*ROWS, ie+j*COLS, ll+i*ROWS,
- le+j*COLS, t, v);
- if(type == 3 type == 2 type == 1)
- detect_edges(name, name2, the_image, out_image,
- il+i*ROWS, ie+j*COLS, ll+i*ROWS,
- le+j*COLS, type, t, v);
- }
- }
-
- } /* ends main */
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Practical Use For Multiple Threads
-
-
- Steve Halladay and Mike Wiebel
-
-
- Steve Halladay and Mike Wiebel currently work for Storage-Tek in Louisville,
- CO. Steve and Mike have over ten years of multi-threaded programming
- experience. Steve holds an M.S. in computer science from Brigham Young
- University. He is also the author of the multi-threaded software Multi-C and
- CSIM. Mike has a B.S. in computer science from Colorado State University. You
- can contact Steve or Mike at (303) 673-6683.
-
-
- Though trade journals have given significant attention to multi-threaded
- programming, especially the use of C's setjmp and longjmp facilities, there
- remains a great deal of confusion in the practical application of
- multi-threaded techniques. In fact, the examples used to demonstrate
- multi-threaded functionality are often contrived or unrealistic.
- Multi-threaded programming can in fact be applied to everyday programming
- problems. This article presents one such problem and shows how multi-threaded
- programming simplifies the program solution.
-
-
- Parallel Recursive Data Structure Access
-
-
- Several years ago we undertook the building of a large text retrieval system.
- The retrieval system would read the text of documents and classify them in
- terms of subject matter. The process of reading through the documents was
- processor intensive and normally run in batches after hours.
- As the retrieval system reads the text, it creates a recursive data structure
- representing the subject matter of each document. After reading a batch of
- documents, the system would traverse all the data structures to write them to
- disk as though they were a single virtual data structure.
- The nature of the structure made it desirable to use recursion to traverse the
- data structure. Recursion, however, would prevent traversing a single data
- structure concurrently with the others. We combined multi-threaded constructs
- with a simple recursive algorithm to traverse many data structures
- concurrently.
- Consider, for example, the problem of concurrently traversing multiple binary
- trees. An individual binary tree is a data structure that lends itself to
- recursive traversal. However, multiple binary trees make using recursion
- difficult. In Figure 1, the pre-order traversal of the first binary tree
- results in visiting nodes 1, 5, 7 and 9. Pre-order traversal of the second
- binary tree would visit 3, 6, 8 and 10. To traverse both trees as if they were
- one virtual tree, you would want to visit the nodes as follows: 1, 3, 5, 6, 7,
- 8, 9 and 10.
-
-
- The Single Threaded Approach
-
-
- The program in Listing 1 constructs multiple binary trees that contain random
- values and then traverses the trees as if they were one virtual tree. Listing
- 1 contains both the single threaded and multiple threaded examples. The
- programmer selects the single thread version by compiling the program with the
- SINGLE_THREAD macro defined.
- The main function calls BuildTree to create each random binary tree. Binary
- trees consist of NODEs, each of which has left and right sub-tree pointers,
- and a key. Once created, the trees array has the address of the root of each
- tree in the array.
- BuildTree creates the random binary trees by generating random keys. BuildTree
- inserts the random keys into the tree by calling AddKey.AddKey then performs a
- standard recursive binary tree insertion by traversing left or right depending
- upon the magnitude of the key value.
- Once main creates the random binary trees, MergeTrees traverses them. In the
- single threaded example, MergeTrees starts at the root node of each tree and
- moves iteratively from node to node. The single threaded example first moves
- to the left-most node, pushing those nodes onto a stack. The for and while
- loops within MergeTrees perform the initial traversal to the left-most node of
- each tree.
- After the single threaded version locates the left-most node of each tree,
- MergeTrees iteratively finds and prints the lowest key until it has visited
- all nodes of all trees. To find the lowest key, MergeTrees calls
- DisplayLowest, which compares the key of the node from each tree and prints
- the lowest of the keys. Once DisplayLowest prints the lowest key, MergeTrees
- calls TraverseRightSub.
- TraverseRightSub locates the next lowest key within the tree. It moves as far
- left as possible from the current node's right sub-tree. If no right sub-tree
- exists, TraverseRightSub will at least pop the stack, causing the parent node
- to be on the top of the stack. In any event, the effect of TraverseRightSub is
- to leave the stack with the next lowest value on the top of the stack. This
- traversal allows DisplayLowest to continue to operate as previously described.
-
-
- A Multi-Threaded Example
-
-
- The multi-threaded approach to traversing many concurrent binary trees is a
- simple extension to traversing a standard single tree. The idea is to traverse
- each tree individually using the standard recursive algorithm. A separate
- thread traverses each tree. Before a node in a tree can be visited, its key
- must be compared with the keys of all other trees about to be visited. The
- nodes with the lower keys are visited first.
- Listing 1 also contains a multi-threaded approach. The #else section of the
- listing contains this example. Compiling the p rogram with the SINGLE_THREAD
- macro not defined yields the multi-threaded example. This example uses the
- Multi-C library, which supports portable, non-preemptive multi-threaded
- constructs such as thread creation, scheduling, inter-thread communication,
- etc.
- The multi-threaded solution creates the random binary trees using the same
- procedures as in the single threaded version. However, it uses a different
- implementation of MergeTrees.
- In the multi-threaded example MergeTrees creates a separate thread for each
- binary tree by calling MtCCoroutine, a Multi-C function that creates a new
- thread of execution. Each new thread begins executing in the PrintTree
- routine. When the new thread completes the PrintTree routine, it will
- terminate execution and yield the processor to other threads. The original
- thread that created the sub-tree threads will continue to execute to
- completion.
- When the original thread completes by returning from main, the sub-tree
- threads will begin execution in PrintTree, a standard recursive binary tree
- traversal routine. The first thread created will be the first to start
- executing. It first traverses the left sub-tree, then visits the current node
- by calling PrintNode. Finally PrintTree traverses the right sub-tree.
- PrintNode guarantees that the node about to be visited has the smallest key of
- any of the trees' current nodes. PrintNode compares its current key to the
- other trees' keys using the static variable CurrentKey. When using the Multi-C
- library, each thread has its own stack, so auto variables are thread-specific.
- The remainder of memory is accessible to all threads. Therefore threads share
- static variables (though they are local to a procedure) and can use them as a
- simple means of inter-thread communication. An alternative solution to
- inter-thread communication would be to use Multi-C's thread mailboxes. Thread
- mailboxes allow threads to receive information from other currently executing
- threads.
- CurrentKey is initialized with a number that is the largest integer so that
- all keys will be less than it. As threads execute PrintNode, each thread
- compares its key to CurrentKey. If the thread's key is less than CurrentKey,
- the thread makes its key the CurrentKey and calls MtCYield. MtCYield is a
- Multi-C library function that causes the thread to suspend execution and
- allows another thread to begin. The yielding thread resumes execution when
- MtCYield returns.
- As threads yield, the Multi-C scheduler reschedules them for execution using a
- round robin algorithm. CurrentKey will equal the thread's current key when all
- other threads have executed and it is the lowest of the threads' keys. Threads
- wait for this condition to occur while executing a loop within PrintNode.
- PrintNode finally visits the node when the executing thread's key is equal to
- the CurrentKey. Then recursion within the thread's tree continues.
-
-
- Comparing Complexities
-
-
- To compare the complexities of the two approaches quantitatively, we refer to
- work initiated several years ago by Maurice Halstead at Purdue. Dr. Halstead
- started a calculus he called "Software Science," in which he defines a measure
- of program volume as:
- V = N log2 n
- N = N1 + N2
- n = n1 + n2
- Where:
- V = the program volume
- N = the program length
- N1 = the total number of operators
-
- N2 = the total number of operands
- n = the vocabulary size
- n1 = the unique operator count
- n2 = the unique operand count
- The volume program metric allows us to compare relative complexities of the
- two approaches. Table 1 contains the empirical and calculated values for each
- approach. While the rules used for counting operators and operands are not
- necessarily the same as those used by Halstead, they are at least consistent
- for both examples. We counted only the operators and operands in the code for
- MergeTrees, since it contains the only differences in the two approaches.
- Table 1 shows the volume for each implementation of MergeTrees. Program volume
- is an estimate of effort required to understand the workings of a program. The
- other metrics in Table 1 are used to calculate the program volume. The volume
- of the single threaded example is slightly greater than three times the
- multi-threaded example. This implies the single threaded example is three
- times more complex than the multi-threaded example.
- We also counted the number of lines of code for each example, shown in Table
- 1, as another measure of program complexity. We defined the number of lines of
- code in the examples as the total number of lines minus blank lines and lines
- only containing comments. It is interesting to notice that this measure also
- shows the single threaded approach is more complex by roughly three times.
-
-
- Conclusion
-
-
- The program described here is a tightly coupled multi-threaded program. In
- such programs each thread performs the same task, but on different data.
- Loosely coupled multi-threaded programs have threads that perform nearly
- unrelated activities. Examples of these programs include communications file
- servers, AI applications, simulations, and games (see the sidebar entitled
- Loosely Coupled Multi-threaded Programs).
- Multi-threaded programming is not a cure-all. It is something that every
- programmer should have in her or his bag of tricks. Since multi-threaded
- programming is portable, it is applicable without machine dependent
- restrictions.
- Like other programming techniques, multi-threaded programming presents a
- learning curve. As the programming community becomes familiar with
- multi-threading techniques, we will discover more ways to use it to simplify
- our programming lives.
- Loosely Coupled Multi-Threaded Programming
- Many popular applications of multi-threaded programs are loosely coupled.
- Loosely coupled programs consist of multiple unrelated threads of execution.
- Loosely coupled multi-threaded applications are appealing because they allow
- the programmer to create each thread independently as if it were a separate
- program. After the programmer has coded and tested each thread, he/she easily
- integrates the threads to create a sophisticated program.
- Games are a common use of loosely coupled multi-threaded programs. Recently
- Steve Halladay built a take-off program on the old space invaders game using
- Multi-C. Those familiar with the game will remember that the game has armies,
- space ships, bombs, bullets and guns. Each of these objects operates
- independently of the other objects (unless a bomb or bullet contacts another
- object).
- Steve created the game by building each object independently. The first object
- to be built was the space ship that flies across the top of the screen in
- random directions. The space ship was thoroughly tested independently of the
- other objects.
- The next objects built were the gun and its bullets. The arrow keys and the
- space bar control the gun (i.e., the arrows move the gun right and left, and
- the space bar fires the gun). Loosely coupled multiple threads allowed the gun
- to be built independently. Integrating the gun thread with the space ship
- thread made it possible to test the ability to shoot down the space ship.
- The loosely coupled threads allowed each additional object to be built,
- developed, and tested independently. The next step integrated the independent
- threads with the previously existing objects. The multi-threaded capabilities
- simplified the integration process considerably. Most of the integration was
- little more than linking in the new objects.
- Once the game was complete, he wanted to create a demo that would play the
- game by itself. Demo development consisted of developing a thread to simulate
- keyboard activity and integrating the thread into the existing program. By
- using an additional thread to perform this function, it was simple and yet
- interesting to develop strategies for automating the game playing.
- If you have further interest in loosely coupled multi-threaded programming or
- this example program, you can contact Steve Halladay for a copy of its source
- and executable. The program uses character graphics (via Aspen Scientific's
- version of curses) and requires less than 100K bytes to run. It uses an
- interrupt to continuously read the keyboard that may not be supported by some
- older versions of BIOS. The program and its source can be freely copied, but
- support is not available.
- Figure 1
- Table 1 Complexity Measures of Examples
- Example N1 N2 N n1 n2 n V Lines of Code
- ------------------------------------------------------
- Single 86 99 185 24 31 55 1069 78
- Multiple 33 28 61 15 13 28 293 23
-
- Listing 1
- /*
- * Traversal of multiple binary trees
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <assert.h>
-
- #define BIGINT (~(1 << ((8*sizeof(int))-1))) /* maximum integer */
- #define NNODES 25 /* the number of nodes per tree */
- #define NTREES 10 /* the number of binary trees to traverse */
-
- /* binary tree node definition */
- typedef struct node {
- struct node *left; /* left sub-tree */
- struct node *right; /* right sub-tree */
- int key; /* current node value */
- } NODE;
-
- /***********************************************************************
- * the following section is the single threaded example
- ***********************************************************************/
- #ifdef SINGLE_THREAD
-
- /* stack frame definition */
- typedef struct frame {
- struct node *data; /* the node being stacked */
- struct frame *next; /* the next stack entry */
-
- } FRAME;
-
- /* push a tree node onto the stack */
- void Push(FRAME **stackp, NODE *treep) {
- FRAME *temp;
- assert(treep != NULL);
- temp = malloc(sizeof(FRAME));
- assert(temp != NULL);
- temp->data = treep;
- temp->next = *stackp;
- (*stackp) = temp;
- }
-
- /* get the top data item from the stack (without popping) */
- int Top(FRAME *stackp) {
- NODE *tempt;
-
- assert(stackp != NULL);
- temp = stackp->data;
- return(temp->key);
- }
-
- /* print the lowest value of all N trees */
- void DisplayLowest(FRAME *stackp[], int *lowest) {
- int current;
- int topvalue;
- int temp=BIGINT;
- int i;
-
- /* for each tree in the list... */
- for (i=0; i < NTREES; i++) {
- /* if the tree has not been completely traversed, */
- if (stackp[i] != NULL) {
- /* get the current key of this tree */
- topvalue = Top(stackp[i]);
- /* compare the current key with the other trees */
- if (topvalue < temp) {
- /* save the current tree number and its key */
- current = i;
- temp = topvalue;
- }
- }
- }
- printf("key = %d\n", temp);
- *lowest = current;
- }
-
- /* removes and returns the top entry from the stack */
- NODE *Pop(FRAME **stackp) {
- FRAME *temp;
- NODE *data;
-
- temp = *stackp;
- *stackp = (*stackp)->next;
- data = temp->data;
- free(temp);
- return(data);
- }
-
-
- /* traverse the right sub-tree */
- void TraverseRightSub(FRAME **stackp) {
- NODE *temp;
- FRAME *sframe;
-
- /* find the right sub-tree of the current node */
- temp = Pop(stackp);
- temp = temp->right;
-
- /* traverse the sub tree as far left as possible */
- while (temp != NULL) {
- Push(stackp, temp);
- temp = temp->left;
- }
- }
-
- /* determine if there are unvisited nodes */
- int StacksExist(FRAME *stackp[]) {
- int i;
-
- for (i = 0; i < NTREES; i++){
- if (stackp[i] != NULL)
- return(1);
- }
- return(0);
- }
-
- void MergeTrees(NODE *treep[]) {
- FRAME *stackp[NTREES];
- int i;
-
- /* preload all stacks */
- for (i = 0; i < NTREES; i++) {
- stackp[i] = NULL;
- while (treep[i] != NULL) {
- Push(&stackp[i], treep[i]);
- treep[i] = treep[i]->left;
- }
- }
- do {
- DisplayLowest(stackp, &i);
- TraverseRightSub(&stackp[i]);
- } while(StacksExist(stackp));
- }
-
- /*****************************************************************
-
- * the following section is the multi threaded example
-
- *****************************************************************/
-
- #else
-
- #include <multi_c.h>
-
- /* print the contents of a node (wait until it
- is the lowest key) */
- void PrintNode(NODE *tree) {
- static int CurrentKey = BIGINT;
-
-
- /* wait until this node contains the lowest key value */
- do {
- if (CurrentKey > tree->key)
- CurrentKey = tree->key;
- MtCYield();
- } while (CurrentKey != tree->key);
- /* reinitialize the current key */
- CurrentKey = BIGINT;
- printf("key = %d\n", tree->key);
- }
-
- /* recursively visit the nodes in a tree */
- void PrintTree(NODE *tree) {
-
- if (tree != NULL) {
- PrintTree(tree->left);
- PrintNode(tree);
- PrintTree(tree->right);
- }
- }
-
- /* traverse multiple binary trees */
- void MergeTrees(NODE **trees) {
- int i;
-
- /* create a thread for each tree... */
- for (i = 0; i < NTREES; i++) {
- MtCCoroutine(PrintTree(&trees[i]));
- }
- }
-
- #endif
-
- /* recursively add a node to the tree */
- void AddKey(NODE **treep, int key) {
-
- if ((*treep) == NULL) {
- (*treep) = malloc(sizeof(NODE));
- assert(*treep != NULL);
- (*treep)->left = NULL;
- (*treep)->right = NULL;
- (*treep)->key = key;
- } else {
- if (key < (*treep)->key) {
- AddKey(&((*treep)->left), key);
- } else {
- AddKey(&((*treep)->right), key);
- }
- }
- }
-
- /* build a tree of NNODES random keys */
- void BuildTree(NODE **treep) {
- int i;
-
- for (i = 0; i < NNODES; i++) {
- AddKey(treep, rand());
- }
-
- }
-
-
- void main() {
- NODE *trees[NTREES];
- int i;
-
- /* randomly build the trees */
- for (i = 0; i < NTREES; i++) {
- trees[i] = NULL;
- BuildTree(&trees[i]);
- }
- /* traverse the trees as a single tree */
- MergeTrees(trees);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Console Stream Class For Borland C++
-
-
- AI Williams
-
-
- Al is the author of DOS 5: A Developer's Guide (M&T Press). He can be reached
- at 310 Ivy Glen Ct., League City, TX 77573; or on Compuserve at 72010, 3574.
-
-
- For my first programming job under Borland C++, I wanted to write a program
- that used both stream I/O and text windows to handle I/O to and from the
- screen. Most C++ programmers prefer using stream I/O instead of the
- printf/scanf functions. To implement the text windows, I set out to develop a
- simple C++ window manager. I planned to use Borland's window() function, but
- soon found that the streams library did not support console I/O (analogous to
- the functions in CONIO.H).
-
-
- Stream I/O
-
-
- Some examples of stream output in Borland C++ are:
- cout << "Hello World\n";
- cout << "Your age is " << ur_age;
- Both examples use cout, a predefined output stream corresponding to stdout.
- The first example sends the cliche string "Hello World\n" to cout. The second
- example prints a string and a numeric variable (ur_age).
- The << operator, while usually the shift left operator, has been overloaded to
- return a stream or a stream reference. The operator used in this sense allows
- running multiple I/O statements together, as in the second example. The
- compiler interprets the statement as:
- (cout << "Your age is ") << ur_age;
- Since the first << operator returns a stream (cout, in fact), the statement is
- equivalent to:
- cout << "Your age is ";
- cout << ur_age;
- Stream input is similar:
- cin >> ur_age;
- cin >> name >> phone_number;
- C++ predefines the cin stream to be the same as stdin. Unlike scanf(), stream
- input doesn't require pointers to the input variables. In the previous
- examples, ur_age is just an unsigned integer variable. The equivalent scanf()
- calls are:
- scanf ("%d" ,&ur_age);
- scanf("%s %s",name,phone_number);
- The C++ library defines input and output operators for the predefined types.
- It is simple to add custom << operators to print user-defined types. Listing 1
- shows a date class and an operator to print it on an output stream. Listing 1
- also includes an overloaded >> operator to input a date. Of course, you can
- use the same techniques to add stream I/O for structures and unions, too.
- The ostream class that Listing 1 uses is the base class of all output streams.
- The istream class is the base for input streams. These stream classes, which
- are subclasses of the ios class, supply methods for formatted and unformatted
- I/O.
- Each stream has a buffer that sends and receives characters. For example, cout
- uses a buffer that sends characters to the stdout device. These buffers are
- either instances of the streambuf class, or instances of a subclass of
- streambuf. The buffer classes handle all the problems of managing a buffer and
- interfacing with the stream class. You are free to derive subclasses of the
- streambuf class that talk to different devices.
- Since the new class will inherit methods from streambuf, you don't have to
- rewrite any of the buffer management methods that already exist. You must only
- describe how the new stream differs from its base class. This technique is
- known as "design by difference." Design by difference is part of what makes
- object-oriented programming so attractive.
- The C++ library also supplies subclasses of streambuf. The filebuf class
- allows you to create buffers to access files. The strstreambuf class works
- with streams that operate on strings, similar to C's sscanf() and sprintf()
- functions.
-
-
- Console Streams
-
-
- VSTREAM.H and VSTREAM.CPP (Listing 2 and Listing 3) provide a console output
- stream called conout for your programs. These files create a class, Conbuf,
- that is a subclass of streambuf.
- The only methods that must be defined for Conbuf are do_sputn()and overflow().
- The methods that manage stream output don't change. The ostream class calls
- do_sputn() to output one or more characters. The overflow() method handles
- buffer overflow. Since the console stream does not require a buffer, the
- overflow() method simply prints the offending character using do_sputn().
- VSTREAM.CPP creates a single instance of the Conbuf class (conbuf), and stores
- a pointer to it in the global variable conout. You can then use conout as cout
- was used in the earlier examples.
- Deriving Conbuf is a good example of the design by difference technique. You
- don't have to worry about buffer management, formatting, and other
- idiosyncrasies of stream output. You only have to describe the difference
- between console output and the normal output streams.
-
-
- The Window Manager
-
-
- The window manager's design meets some simple goals:
- Windows can be as large as the screen, but no larger.
- Windows can have frames, or not.
- When the manager removes a window, it restores any windows that become
- visible.
- Windows can be forced to the top of the window stack.
- Only the top window can be accessed.
- The window manager supports streams.
- The region class, shown in Listing 4 and Listing 5, is the base class for
- windows. This class simply saves and restores portions of the screen using
- C++'s dynamic memory allocation.
-
- The win class, built on top of the region class, provides all the basic window
- functions. The manager defines windows with the Borland library function
- window(). Therefore, all the output calls in CONIO.H and the new conout stream
- will automatically write to the top window.
- The boxwin class is another example of design by difference. Containing just
- one function to draw a box, boxwin reuses all the code of its base class, win.
- Listing 6 and Listing 7 contain the window class library. Listing 8 is a demo
- program of the windows in action. Thanks to C++ constructors, you can simply
- define a window in your program, and it appears on the screen. When your
- program deallocates the window or it goes out of scope, the window vanishes
- just as easily.
-
-
- Inside The Window Class
-
-
- To understand the window class, you must first start with the class named
- region. The region class simply saves and restores a specified area of the
- screen. The constructor for this class takes four arguments that specify the x
- and y coordinates of the screen region to use. If the fifth argument, save, is
- zero, the constructor will set up the region, but not save the screen area.
- Otherwise, the region reads the contents of the screen (the default action).
- Setting save to zero is useful for programs like the window class that require
- finer control over the timing of region's actions.
- The region class has three public methods besides the constructor and
- destructor methods. The reinit method causes the region to read the screen,
- discarding the previous contents of the region (if any). The restore method
- causes the region to redisplay the saved area of the screen. This call also
- discards the contents of the region. Finally, a call to discard will destroy
- the region's contents without changing the screen.
- The window class win maintains a stack of windows. The class variable topwin
- points to the current top window. Notice that Listing 6 declares topwin as a
- static member of the class. This declaration allows all instances of win to
- share the same topwin variable. A similar variable, lastwin, points to the
- last window in the stack. Pointers thread the stack together in both
- directions. The pointers next (toward the bottom of the stack) and prev
- (toward the top of the stack) contain links to adjacent windows. Of course,
- these pointers are not static - each window has its own next and prev pointer.
- The remaining data members of win are not static. They store the cursor
- location, the window's color, a pointer to the next window, and a margin value
- used for bordered windows.
- The top window's region buffer remains empty. Only windows that are inactive
- store their contents in the region. This is also true of the cursor location
- values.
- The win class has only one public method (excepting the constructor and
- destructor). This method, maketop, forces the given window to the top of the
- stack. If the window is already at the top, no action results.
-
-
- More Fun With Windows
-
-
- You will probably want to add functionality to the windows package. For
- instance, you might want different box types, or to be able to move and resize
- the windows. For serious use, you could add some simple methods to change
- various private members, such as a setcolor method to change a window's color.
- The built-in stream classes can take manipulators that control certain
- formatting attributes. The standard headers IOSTREAM.H and IOMANIP.H declare
- these manipulators. The following statement, for example, uses the hex
- manipulator:
- cout << hex << 16;
- This statement prints 16 in hex (10) instead of decimal.
- Adopting this syntax for managing the console stream would be quite natural.
- You could write
- conout << color_red << "RED" << color_white <<
- " ALERT";
- or, alternately:
- conout << concolor(0x70) << "Text";
- Other manipulators could duplicate functions found in CONIO.H. For example,
- you could use a manipulator to set the cursor's location. Although this would
- be an ambitious addition, it would make the stream class much more natural to
- use.
-
- Listing 1 (dates.cpp) Simple Date Class
- #include <iostream.h>
- #include <iomanip.h>
-
- // Crude date class to demonstrate
- // customized stream I/O -- no error checking attempted
-
- class date
- {
- unsigned int year; // 0-99
- unsigned int mon; // 1-12
- unsigned int day; // 1-31
- // Allow the I/O stream operators to access the private members
- // of the date class. You can't define these as members
- // because the first argument is a stream, not a date.
- friend ostream & operator <<(ostream &s,date dt);
- friend istream & operator >>(istream &s,date &dt);
- public:
- // constructor
- date(int _mon=1,int _day=1,int _year=0)
- { mon=_mon; day=_day; year=_year; }
- };
-
- // Output a date as: MM/DD/YY
- // No attempt was made to pad the elements with zeros
- ostream & operator <<(ostream &s,date dt)
- {
- s << dt.mon << "/" << dt.day << "/" << dt.year;
- return s;
- }
-
-
- // Input a date in the format: MM/DD/YY -- allow any character
- // to seperate the elements (i.e., MM-DD-YY, MM,DD,YY, etc.)
- // Notice that the date is a reference (&dt) so we modify
- // the actual date -- not a copy passed by value.
- istream & operator >>(istream &s,date &dt)
- {
- int m,d,y;
- char dummy; // this char holds the seperator
- s >> m >> dummy >> d >> dummy >> y;
- dt.mon=m;
- dt.day=d;
- dt.year=y;
- return s;
- }
-
- // Simple demo for dates. Notice how the stream I/O operators
- // have been overloaded to accept the date class.
- main()
- {
- date jan1(1,1,70);
- date bday;
- cout << "Enter your birthday (MM/DD/YY): ";
- cin >> bday;
- cout << "The first date is " << jan1 << "\n";
- cout << "Your birthday is " << bday << "\n";
- cout << 1;
- }
-
- // End of File
-
-
- Listing 2 (vstream.h) Video Stream Class
- #ifndef _VSTREAMDEF
- #define _VSTREAMDEF
-
- #include <iostream.h>
-
- extern class Conbuf : public streambuf
- {
- int do_sputn(const char *s,int n);
- int overflow(int=EOF);
- } conbuf;
-
- extern ostream conout;
-
- #endif
-
- /* End of File */
-
-
- Listing 3 (vstream.cpp) Video Stream Package
- #include <iostream.h>
- #include <conio.h>
-
-
- class Conbuf : public streambuf
- {
- int do_sputn(const char *s,int n);
-
- int overflow(int=EOF);
- } conbuf;
-
- int Conbuf::overflow(int c)
- {
- do_sputn((char *)&c,1);
- return 1;
- }
-
- int Conbuf::do_sputn(const char *s,int n)
- {
- int n1=n;
- while (n1--)
- {
- putch(*s);
- if (*s++=='\n')
- {
- putch('\r');
- clreol();
- }
- }
- return n;
- }
-
- ostream conout=&conbuf;
-
- // End of File
-
-
- Listing 4 (region.h) Header for Screen Regions
- #ifndef _REGIONDEF
- #define _REGIONDEF
- #include <stddef.h>
- #include <conio.h>
-
- // This class saves and releases a region of the screen
- class region
- {
- protected:
- // Screen coordinates
- int left;
- int top;
- int right;
- int bot;
- // Storage area
- char *buf;
- public:
- // Methods:
-
- // Constructor -- if save is 0, the screen region isn't saved.
- // You'd save it later with the reinit() method.
- region(int x0,int y0,int x1,int y1,int save=1);
-
- // Destructor
- ~region();
-
- // Force the region to reread its screen area and save it
- void reinit(void);
-
-
- // Restore screen data and destroy it
- void restore(void);
-
- // Destroy screen data with out restoring it
- void destroy(void);
- };
-
- #endif
-
- /* End of File */
-
-
- Listing 5 (region.cpp) Screen Region Package
- #include "region.h"
- region::region(int x0, int y0, int x1, int y1, int save)
- {
- left=x0;
- top=y0;
- right=x1;
- bot=y1;
- buf=NULL;
- if (save)
- reinit();
- }
-
- void region::reinit(void)
- {
- if (buf) delete buf;
- buf=new char[2*(1+right-left)*(1+bot-top)];
- gettext(left,top,right,bot,buf);
- }
-
- void region::restore(void)
- {
- if (buf)
- {
- puttext(left,top,right,bot,buf);
- destroy();
- }
- }
-
-
- region::~region()
- {
- restore();
- }
-
- void region::destroy(void)
- {
- if (buf)
- {
- delete buf;
- buf=NULL;
- }
- }
-
- // End of File
-
-
-
- Listing 6 (window.h)
- #ifndef _WINCLASSDEF
- #define _WINCLASSDEF
-
- #include <stddef.h>
- #include <conio.h>
- #include "region.h"
-
-
- // The basic window class
- extern class win : public region
- {
- protected:
-
- static win *topwin; // Class variable holds top window
- static win *lastwin; // Last window
-
- // Cursor location when window isn't on top
- int oldx;
- int oldy;
-
- // Default screen color
- unsigned int color;
-
- // Pointer to next window on stack
- win *next; // Pointer to next window
- win *prev; // Previous window
-
- int margin; // Margins support borders on the windows
-
- // Private method to register top window
- void settop(void);
-
- public:
- // Methods:
- // Constructor:
- win(int x0=1,int y0=1,int x1=80,int y1=25,
- unsigned int clr=7,int mar=0);
-
- // Destructor. This is virtual to support boxwindows, etc.
- virtual ~win();
-
- // Force window to top of stack
- void maketop();
- };
-
- // Windows with borders
- extern class boxwin : public win
- {
- public:
- boxwin(int x0=2,int y0=2,int x1=79,int y1=24,
- unsigned int clr=7,int boxt=0);
- };
-
- // General purpose box drawing routine
- void draw_box(int type,int x0,int y0,int x1,int y1);
-
- #endif
-
-
- /* End of File */
-
-
- Listing 7 (window. cpp)
- #include "window.h"
- #include "vstream.h" // console stream header
-
- // TC++ 1.0 didn't define _wscroll in conio.h
- #ifndef__BORLANDC______LINEEND____
- extern int_wscroll;
- #endif
-
- // Initialize class variable. Note you can't do this in the
- // Definition itself.
- win * win::topwin=NULL;
- win * win::lastwin=NULL;
-
- win::win(int x0,int y0,int x1,int y1,unsigned int clr,int mar):
- region(x0,y0,x1,y1,0)
- {
- if (!topwin) // first window
- {
- textattr(7); // reset screen
- clrscr();
- lastwin=this;
- }
- else
- {
- // save window contents & cursor
- topwin->reinit();
- topwin->oldx=wherex();
- topwin->oldy=wherey();
- }
- margin=mar;
- color=clr;
- prev=NULL;
- if (topwin) topwin->prev=this;
- next=topwin;
- topwin=this;
- window(x0,y0,x1,y1);
- gotoxy(1,1);
- textattr(clr);
- clrscr();
- }
-
- void win::maketop(void)
- {
- win *gpw;
- // return if already at top
- if (this==topwin) return;
- // force top window to save
- topwin->reinit();
- topwin->oldx=wherex();
- topwin->oldy=wherey();
- // patch link list
- if (lastwin==this) lastwin=prev;
- if (prev) prev->next=next;
- if (next) next->prev=prev;
- prev=NULL;
-
- topwin->prev=this;
- next=topwin;
- topwin=this;
- settop();
- restore(); // Draw our screen contents
- }
-
- void win::settop(void)
- {
- window(
- topwin->left+topwin->margin,
- topwin->top+topwin->margin,
- topwin->right-topwin->margin,
- topwin->bot-topwin->margin);
- textattr(topwin->color);
- gotoxy(topwin->oldx,topwin->oldy);
- }
-
-
-
- win::~win()
- {
- this->maketop(); // force us on top
- // just in case there is a margin
- window(left,top,right,bot);
- textattr(7);
- clrscr();
- destroy();
-
- if (next) next->prev=NULL;
- topwin=next;
- if (!topwin)
- {
- window(1,1,80,25);
- clrscr();
- }
- else
- {
- for (win *i=lastwin;i;i=i->prev)
- {
- i->restore();
- if (i!=topwin) i->reinit();
- }
- settop();
- }
- }
-
- // boxwin methods
- boxwin::boxwin(int x0,int y0,int x1,int y1,unsigned int clr, int boxt) :
- win(x0-1,y0-1,x1+1,y1+1,clr,1)
- {
- draw_box(boxt,1,1,x1-x0+3,y1-y0+3);
- window(x0,y0,x1,y1);
- }
-
- // General purpose box drawing function
- // Type 0: single line box
- // Type 1: double line box
- // Other types are easily added
-
- void draw_box(int type,int x0,int y0,int x1,int y1)
- {
- int oldscroll; // old value for _wscroll
- int i;
- int hline;
- int vline;
- int cl,c2,c3,c4;
- int xlen;
- int ylen;
- if (type<0type>1) return; // change value to add more types
- xlen=x1-x0;
- ylen=y1-y0;
- if (type==0)
- {
-
- // Constants for a "normal" box
- hline=196;
- vline=179;
- c1=218;
- c2=191;
- c3=192;
- c4=217;
- }
- else if (type==1)
- {
- hline=205;
- vline=186;
- c1=201;
- c2=187;
- c3=200;
- c4=188;
- }
- oldscroll= _wscroll;
- _wscroll=0;
- gotoxy(x0+1,y0);
- for (i=1;i<xlen;i++) putch(hline);
- gotoxy(x0+1,y0+ylen);
- for (i=1;i<xlen;i++) putch(hline);
- gotoxy(x0,y0);
- putch(cl);
- gotoxy(x0+xlen,y0);
- putch(c2);
- gotoxy(x0,ylen+y0);
- putch(c3);
- gotoxy(xlen+x0,ylen+y0);
- putch(c4);
- for (i=y0;i<ylen;i++)
- {
- gotoxy(x0,i+1);
- putch(vline);
- gotoxy(xlen+x0,i+1);
- putch(vline);
- }
- _wscroll=oldscroll;
- }
-
- // End of File
-
-
-
- Listing 8 (wintest.cpp)
- #include "window.h"
- #include <ctype.h>
- #include "vstream.h"
-
- // Make sure user really wants to quit
- int cfmexit(void)
- {
- int c;
- boxwin promptwin(30,12,50,14,0x70,1);
- conout<<"Really quit? (Y/N)";
- while (1)
- {
- c:getche();
- if (!c) getch(); // ignore Function keys
- c:toupper(c);
- if (c=='Y') return 1;
- if (c=='N') return 0;
- }
- }
-
-
- // Main routine
- main()
- (
- /* make main window */
- boxwin mainwindow(2,20,78,23,0x70);
- win *w[4];
- conout<<"Welcome to the WINDOWS++ demo.\n";
- conout<<"Initializing windows...\n";
- w[3]=new boxwin(60,2,78,10,0x70);
- conout<<"Window #4";
- w[2]=new boxwin(40,2,70,10,0x3F);
- conout<<"Window #3";
- w[1]=new boxwin(20,2,50,10,0x17);
- conout<<"Window #2";
- w[0]=new boxwin (2,2,30,10,7);
- conout<<"Window #1";
- mainwindow.maketop();
- while (1)
- {
- int c;
- conout<<
- "Press 1-4 to select window or <Esc> to quit\n";
- c=getch();
- if (c==27)
- if (cfmexit()) break; else continue;
- if (c<'l' c>'4')
- {
- conout<<"Unknown window!\n";
- continue;
- }
- conout<<"Activating window "<<(char)c<<'\n';
- w[c-'l']->maketop();
- mainwindow.maketop();
- }
- for (int i:0=i<4;i++) delete w[i];
- }
-
-
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- Implementing <stdio.h>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
- ANSI C standards committee, X3J11, and convenor of the ISO C standards
- committee, WG14. His latest book is The Standard C Library, published by
- Prentice-Hall. You can reach him at PJP@wsa.oz; or uunet!munnari!wsa.oz!pjp.
-
-
-
-
- Introduction
-
-
- The header <stdio.h> is far and away the largest one in the Standard C
- library. Only <stdlib.h> comes close in size, and that one is a collection of
- several unrelated groups of functions. By contrast, the header <stdio.h>
- focuses exclusively on one topic -- performing input and output.
- I have discussed many aspects of this header in the past:
- "Evolution of the C I/O Model," CUJ August, 1989.
- "Streams," CUJ September/ October, 1989.
- "Formatted Output," CUJ November, 1989.
- "Formatted Input," CUJ December/January, 1990.
- If you don't have access to back issues of CUJ, take heart. You will find most
- of these words recycled in Chapter 12: <stdio.h> of The Standard C Library. I
- am not about to repeat them yet again here.
- I am also not going to follow my usual practice of quoting the relevant
- portion of the C Standard. That would take a whole column in its own right. I
- have no qualms about getting paid in part for quoting from the standard -- I
- feel I contributed significantly to developing those words. I just don't
- believe that such an extensive quote best serves the goal of this column -- to
- broaden your understanding of Standard C.
- Instead I will go right to the really new stuff. I describe how I implemented
- the functions in <stdio.h.> The challenges are:
- to keep as much code as possible portable
- to ensure that the system-dependent code is implementable efficiently on many
- systems
- to keep performance up
- to keep the code as simple and readable as possible despite the above
- requirements
- Remember, what I show here is just one possible implementation. Different
- approaches can be better, depending on circumstances. My purpose in showing a
- particular implementation is to illustrate how <stdio.h> can work, not how it
- must work.
- Two design decisions are critical to the implementation of <stdio.h>:
- the contents of the FILE data structure
- the low-level primitives that interact with the operating system to perform
- the actual input/output
- I begin by discussing the first of these two topics in detail. You can then
- appreciate how the portable low-level I/O functions work. I save the
- primitives for later.
-
-
- Data Structures
-
-
- Listing 1 shows the file stdio.h. By now you should be familiar with my use of
- the internal header <yvals.h> to supply implementation-dependent parameters.
- Here are the parameters defined in <yvals.h> that affect <stdio.h>, with some
- reasonable values for them:
- #define _NULL (void *)0/* value for NULL */
- #define _FNAMAX 64 /* value for FILENAME_MAX */
- #define _FOPMAX 32 /* value for FOPEN_MAX */
- #define _TNAMAX 16 /* value for TMP_MAX */
- The file stdio.h contains a few other mysteries which shall become clear in
- time. For now, I concentrate on the type definition FILE. Its members are:
- _Mode -- a set of status bits for the stream, defined below
- _Handle -- the handle, or file descriptor, returned by the operating system
- for the opened file
- _Buf -- a pointer to the start of the stream buffer, or a null pointer if no
- buffer has been allocated
- _Bend -- a pointer to the first character beyond the end of the buffer,
- undefined if_Buf is a null pointer
- _Next -- a pointer to the next character to read or write, never a null
- pointer
- _Rend -- a pointer to the first character beyond the end of data to be read,
- never a null pointer
- _Rsave -- holds_Rend if characters have been pushed back
- _Wend -- a pointer to the first character beyond the end of where data can be
- written, never a null pointer
- _Back -- a stack of pushed-back characters
-
- _Cbuf -- a one-character buffer to use when no other buffer is available
- _Nback -- a count of the number of pushed-back characters
- _Tmpnam -- a pointer to the name of a temporary file to be removed when the
- file is closed, or a null pointer
- The design of the FILE data structure is driven by the needs of the macros
- getc and putc (and their companions getchar and putchar). Each of these
- expands to a conditional expression that either accesses the stream buffer
- directly or calls the underlying function. The predicate (test expression)
- part of the conditional expression must be simple and always safe to execute.
- Thus, str->_Next < str->_Rend is always true if characters that can be read
- are in the buffer for the stream pointed at by str. And str->_Next <
- str->_Wend is always true if space is available in the buffer to write
- characters to the stream. An expression such as str->_Wend = str->_Buf, for
- example, disallows writes to the buffer from these macros.
- The functions that you call to read and write streams make more extensive
- tests. A read function, for example, distinguishes a variety of conditions
- such as: characters are available, buffer currently exhausted, end-of-file
- encountered, buffer not yet allocated, reading currently disallowed, and
- reading never allowed. The functions rely heavily on the various indicators in
- the member _Mode to make those distinctions.
- Only functions within the Standard C library need be privy to the meaning of
- these indicators. For that reason, and others, I created the internal header
- "xstdio.h". All the functions described in this chapter include "xstdio.h". It
- defines macros for the stream-mode indicators. It includes <stdio.h> and
- declares all the internal functions used to implement the capabilities of
- <stdio.h>. It also defines a number of macros and types of interest only to
- the formatted input and output functions.
- Unlike <stdio.h>, the header "xstdio.h" contains too many distractions to
- present at this point. I show you what goes into it only as the need arises.
- Here, for example, are the macro names for the various indicators in the
- member _Mode. Each is defined as a value with a different bit set, as in 0x1,
- 0x2, 0x4, 0x8, and so on. The actual values are unimportant, so I omit them
- here:
- _MOPENR -- set if file is open for reading
- _MOPENW -- set if file is open for writing
- _MOPENA -- set if all writes append to end of file
- _MTRUNC -- set if existing file was truncated on open (not used after open)
- _MCREAT -- set if a new file can be created on open (not used after open)
- _MBIN -- set if stream is binary, not set if stream is interpreted as text
- _MALBUF -- set if the buffer must be freed on close
- _MALFIL -- set if the FILE data object must be freed on close
- _MEOF -- the end-of-file indicator
- _MERR -- the error indicator
- _MLBF -- set if line buffering is in effect
- _MNBF -- set if no buffering should occur
- _MREAD -- set if a read has occurred since last file-positioning operation
- _MWRITE -- set if a write has occurred since last file-positioning operation
- These macros have private names -- beginning with an underscore and an
- uppercase letter -- even though they don't have to. As I developed the
- library, I found myself moving them in and out of <stdio.h>. Some versions of
- the macros visible to user programs used these macro names, later versions did
- not. In the end, I left the names in this form as insurance. You may find
- occasion to introduce macros that manipulate the indicators in the member
- _Mode.
- The indicators are actually the union of two sets. One is the set of
- indicators that determines how to open a file. The other is the set of
- indicators that helps record the state of the stream. Since the two sets
- partially overlap, I chose to keep them all in one "space" of bit encodings. A
- tidier implementation might well choose to separate the two uses. You might
- also want to define two sets of values if you are starved for bits in _Mode.
- In either case, you must add code to translate between the two
- representations.
-
-
- Opening And Closing Files
-
-
- The best way to see how the library uses a FILE data object is to track one
- through its lifetime. Listing 2 shows the file fopen.c. It defines the
- function fopen that you call to open a file by name. That function first looks
- for an idle entry in the static array of FILE pointers called _Files. It
- contains FOPEN_MAX elements. If all of these point to FILE data objects for
- open files, all subsequent open requests fail.
- Listing 3 shows the file xfiles.c that defines the _Files data object. It
- defines static instances of FILE data objects for the three standard streams.
- Each is initialized to be open with appropriate parameters. I have wired in
- the handles 0 for standard input, 1 for standard output, and 2 for standard
- error. This is a widely used convention, inherited from UNIX. You may have to
- alter or map these values.
- Elements beyond the first three in _Files are initialized to null pointers.
- Should fopen discover one of these, the function allocates a FILE data object
- and marks it to be freed on close. fopen discovers a closed standard stream by
- observing a non-null element of _Files that points at a FILE data object whose
- member _Mode is zero.
- fopen calls on the internal function _Foprep to complete the process of
- opening a file. Listing 4 shows the file freopen.c. The function freopen also
- calls this internal function. Note how it records the state of the indicator
- _MALFIL until after fclose has closed the file currently associated with the
- stream. The one operation that freopen does not want fclose to perform is to
- free the FILE data object.
- You may as well see fclose too, at this point. Listing 5 shows the file
- fclose.c. It undoes the work of the file-opening functions in a fairly obvious
- fashion. The one bit of magic is where it calls the function _Fclose to close
- the file associated with the stream.
- Listing 6 shows the file xfoprep.c that defines the function _Foprep. It
- parses the mods (second) argument to fopen or freopen, at least as much as it
- can understand, and initializes members of the FILE data object accordingly.
- In the end, however, it must call on some outside agency to finish the job of
- opening the file. _Foprep passes on the file name, the encoded indicators, and
- whatever is left of mods to a function called _Fopen.
-
-
- Primitives
-
-
- _Fclose and _Fopen are two of several low-level primitives that stand between
- <stdio.h> and the outside world. Each must perform a standardized function for
- the Standard C library. Each must also be reasonably easy to tailor for the
- divergent needs of different operating systems. This implementation has nine
- functions in <stdio.h> that must be tailored to each operating system.
- By implementing these interface primitives, you can use this library in
- conjunction with several popular operating systems. I have cobbled up versions
- that work with:
- Turbo C on PC compatibles
- Sun UNIX on Sun 3 workstations
- ULTRIX on DEC VAX minicomputers
- I say "cobbled" because my versions cut an occasional corner. They may, for
- example, call functions that violate the name-space caveats of the C Standard.
- (I may call unlink instead of writing an assembly-language equivalent called
- _Unlink.) Or they may not deal with all the nonstandard ways that a carriage
- return can appear within an MS-DOS file.
- Nevertheless, I'm comfortable that these primitives are reasonable and
- workable. Next month, I'll discuss the I/O primitives in detail. I'll also
- show you an example of one way to float <stdio.h> atop an operating system.
- This article is excerpted in part from P.J. Plauger, The Standard C Library,
- (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
-
- Listing 1 (stdio.h)
- /* stdio.h standard header */
- #ifndef _STDIO
- #define _STDIO
- #ifndef _YVALS
- #include <yvals.h>
- #endif
- /* macros */
- #define NULL _NULL
- #define _IOFBF 0
- #define _IOLBF 1
-
- #define _IONBF 2
- #define BUFSIZ 512
- #define EOF -1
- #define FILENAME_MAX _FNAMAX
- #define FOPEN_MAX _FOPMAX
- #define L_tmpnam _TNAMAX
- #define TMP_MAX 32
- #define SEEK_SET 0
- #define SEEK_CUR 1
- #define SEEK_END 2
- #define stdin _Files[0]
- #define stdout _Files[1]
- #define stderr _Files[2]
- /* type definitions */
- #ifndef _SIZET
- #define _SIZET
- typedef _Sizet size_t;
- #endif
- typedef struct {
- unsigned long _Off; /* system dependent */
- } fpos_t;
- typedef struct {
- unsigned short _Mode;
- short _Handle;
- unsigned char *_Buf, *_Bend, *_Next;
- unsigned char *_Rend, *_Rsave, *_Wend;
- unsigned char _Back[2],_Cbuf, _Nback;
- char *_Tmpnam;
- } FILE;
- /* declarations */
- void clearerr(FILE *);
- int fclose(FILE *);
- int feof(FILE *);
- int ferror(FILE *);
- int fflush(FILE *);
- int fgetc(FILE *);
- int fgetpos(FILE *, fpos_t *);
- char *fgets(char *, int, FILE *);
- FILE *fopen(const char *, const char *);
- int fprintf(FILE *, const char *, ...);
- int fputc(int, FILE *);
- int fputs(const char *, FILE *);
- size_t fread(void *, size_t, size_t, FILE *);
- FILE *freopen(const char *, const char *, FILE *);
- int fscanf(FILE *, const char *, ...);
- int fseek(FILE *, long, int);
- int fsetpos(FILE *, const fpos_t *);
- long ftell(FILE *);
- size_t fwrite(const void *, size_t, size_t, FILE *);
- int getc(FILE *);
- int getchar(void);
- char *gets(char *);
- void perror(const char *);
- int printf(const char *, ...);
- int putc(int, FILE *);
- int putchar(int);
- int puts(const char *);
- int remove(const char *);
- int rename(const char *, const char *);
-
- void rewind(FILE *);
- int scanf(const char *, ...);
- void setbuf(FILE *, char *);
- int setvbuf(FILE *, char *, int, size_t);
- int sprintf(char *, const char *, ...);
- int sscanf(const char *, const char *, ...);
- FILE *tmpfile(void);
- char *tmpnam(char *);
- int ungetc(int, FILE *);
- int vfprintf(FILE *, const char *, char *);
- int vprintf(const char *, char *);
- int vsprintf(char *, const char *, char *);
- long _Fgpos(FILE *, fpos_t *);
- int_Fspos(FILE *, const fpos_t *, long, int);
- extern FILE *_Files[FOPEN_MAX];
- /* macro overrides */
- #define fgetpos(str, ptr) (int)_Fgpos(str, ptr)
- #define fseek(str, off, way) _Fspos(str, _NULL, off, way)
- #define fsetpos(str, ptr) _Fspos(str, ptr, 0L, 0)
- #define ftell(str) _Fgpos(str, _NULL)
- #define getc(str) ((str)->_Next < (str)->_Rend \
- ? *(str)->_Next++ : (getc)(str))
- #define getchar() \
- (_Files[0]->_Next < _Files[0]->_Rend \
- ? *_Files[0]->_Next++ : (getchar)())
- #define putc(c, str) \
- ((str)->_Next < (str)->_Wend \
- ? (*(str)->_Next++ = c) : (putc)(c, str))
- #define putchar(c) \
- (_Files[1]->_Next <_Files[1]->_Wend \
- ? (*_Files[1]->_Next++ = c) : (putchar)(c))
- #endif
- /* End of File */
-
-
- Listing 2 (fopen.c)
- * fopen function */
- #include <stdlib.h>
- #include "xstdio.h"
-
- FILE *(fopen)(const char *name, const char *mods)
- { /* open a file */
- FILE *str;
- size_t i;
-
- for (i = 0; i < FOPEN_MAX; ++i)
- if (_Files[i] == NULL)
- { /* setup empty _Files[i] */
- str = malloc(sizeof (FILE));
- if (str == NULL)
- return (NULL);
- _Files[i] = str;
- str->_Mode = _MALFIL;
- break;
- }
- else if (_Files[i]-> _Mode == 0)
- { /* setup preallocated
- _Files[i] */
- str = _Files[i];
-
- break;
- }
- if (FOPEN_MAX <= i)
- return (NULL);
- return (_Foprep(name, mods, str));
- }
- /* End of File */
-
-
- Listing 3 (xfiles.c)
- /* _Files data object */
- #include "xstdio.h"
-
- /* standard error buffer */
- static unsigned char ebuf[80];
-
- /* the standard streams */
- static FILE sin = { /* standard input */
- _MOPENR, 0,
- NULL, NULL, &sin._Cbuf,
- &sin._Cbuf, NULL, &sin._Cbuf, };
- static FILE sout = { /* standard output */
- _MOPENW, 1,
- NULL, NULL, &sout._Cbuf,
- &sout._Cbuf, NULL, &sout._Cbuf, };
- static FILE serr = { /* standard error */
- _MOPENW_MNBF, 2,
- ebuf, ebuf + sizeof (ebuf), ebuf,
- ebuf, NULL, ebuf, };
-
- /* the array of stream pointers */
- FILE *_Files[FOPEN_MAX] = {&sin, &sout, &serr};
- /* End of File */
-
-
- Listing 4 (freopen.c)
- /* freopen function */
- #include <stdlib.h>
- #include "xstdio.h"
-
- FILE *(freopen)(const char *name, const char *mods, FILE *str)
- { /* reopen a file */
- unsigned short mode = str->_Mode & _MALFIL;
-
- str->_Mode &=~_MALFIL;
- fclose(str);
- str-> _Mode = mode;
- return (_Foprep(name, mods, str));
- }
- /* End of File */
-
-
- Listing 5 (fclose.c)
- /* fclose function */
- #include <stdlib.h>
- #include "xstdio.h"
- #include "yfuns.h"
-
- int (fclose)(FILE *str)
-
- { /* close a stream */
- int stat = fflush(str);
-
- if (str-> _Mode & _ MALBUF)
- free(str->_Buf);
- str->_Buf = NULL;
- if (0<= str->_Handle && _Fclose(str))
- stat = EOF;
- if (str->_Tmpnam)
- { /* remove temp file */
- if (remove(str->_Tmpnam))
- stat = EOF;
- free(str->_Tmpnam);
- str->_Tmpnam = NULL;
- }
- str->_Mode = 0;
- str->_Next = &str->_Cbuf;
- str->_Rend = &str->_Cbuf;
- str->_Wend = &str->_Cbuf;
- str->_Nback = 0;
- if (str->_Mode & _MALFIL)
- { /* find _Files[i] entry and free */
- size _t i;
-
- for (i = 0; i < FOPEN_MAX; ++i)
- if (_Files[i] == str)
- { /* found entry */
- _Files[i] = NULL;
- break;
- }
- free(str);
- }
- return (stat);
- }
- /* End of File /*
-
-
- Listing 6 (xfoprep.c)
- /* _Foprep function */
- #include "xstdio.h"
-
- /* open a stream */
- FILE *_Foprep(const char *name, const char *mods,
- FILE *str)
- { /* make str safe for fclose, macros */
- str->_Handle = -1;
- str->_Tmpnam = NULL;
- str->_Buf = NULL;
- str->_Next = &str->_Cbuf;
- str->_Rend = &str->_Cbuf;
- str->_Wend = &str->_Cbuf;
- str->_Nback = 0;
- str->_Mode = (str->_Mode & _MALFIL)
- (*mods == 'r' ? _MOPENR
- : *mods == 'w' ? _MCREAT_MOPENW_MTRUNC
- : *mods == 'a' ? _MCREAT_MOPENW_MOPENA
- : 0);
- if ((str->_Mode & (_MOPENR_MOPENW)) == 0)
- { /* bad mods */
-
- fclose(str);
- return (NULL);
- }
- while (*++mods== 'b' *mods == '+')
- if (*mods == 'b')
- if (str->_Mode & _MBIN)
- break;
- else
- str->_Mode = _MBIN;
- else
- if ((str->_Mode &
- (_MOPENR_MOPENW))
- == (_MOPENR_MOPENW))
- break;
- else
- str->_Mode =
- _MOPENR_MOPENW;
- str->_Handle = _Fopen(name, str->_Mode, mods);
- if (str->_Handle < 0)
- { /* open failed */
- fclose(str);
- return (NULL);
- }
- return (str);
- }
-
- /* End of File /*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On The Networks
-
-
- Where Have All The Sources Gone
-
-
-
-
- Sydney S. Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, lecturer, author,
- professor, and president of Datacomp Systems, Inc., a consulting and contract
- programming firm specializing in databases, data presentation and windowing,
- transaction processing, networking, testing and test suites, and device
- management for UNIX and MS-DOS. He can be contacted care of Datacomp Systems,
- Inc., 3837 Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail
- on the Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
- Internet addressing).
-
-
- I used to blame the dearth of source postings on the summer doldrums, or on
- spring break, or other such college holidays. However, it is now late
- September, and the wires have been exceedingly idle. The sources in the
- mainstream news groups are very slow in coming. Has everyone gone off to learn
- X? The X Window System source news group has had considerable action. I
- normally don't review items in that group, as CUJ is not a pure UNIX journal.
- Should I?
- I currently keep my ears tuned and my eyes wandering over the doings in the
- comp.sources.games, comp.sources.misc, comp.sources.reviewed,
- comp.sources.unix and alt.sources groups. There are others. Most of those
- relate to a specific computer system, such as the Acorn, Amiga, Apple2, Atari,
- Mac or HP48 (yes, the HP48 hand-held calculator has its own news group). The
- only other source news group with considerable traffic of nontrivial programs
- is comp. sources.x.
- I leave it to your decision. Send me electronic mail indicating whether you
- think I should cover the X news group. If you cannot send electronic mail,
- send your letter to the address listed in my biography. Please mark the
- outside of the envelope that this is a vote on the X news group issue.
-
-
- Rich?
-
-
- Part of what has led me to wonder where everyone has gone is that there have
- been no postings at all in comp.sources.unix since June. Rich's honeymoon has
- long since been over, and the last comment from him in regards to the queue of
- postings is also several months old. This probably explains the increase in
- traffic in comp.sources.misc compared to before Rich took his extended hiatus.
-
-
- Slow Reviews
-
-
- Even comp.sources.reviewed is slow to produce, although from its status
- reports the problem is in getting corrected packages back from their authors.
- The only posting was an updated version of deliver, Chip Salzenberg's system
- to handle incoming electronic mail. It can forward the mail based on content,
- store it in a set of folders, reply with requested information to the sender,
- or anything that can be described as a shell script. It is extremely flexible
- and portable. It runs under almost any UNIX system and even runs on UNIX
- clones, including Coherent. It does require mail transport software that can
- support delivery of messages to a process. This includes Smail 2.x, Smail 3.x,
- Sendmail, or the SCO XENIX mail system. It currently does not support MMDF.
- F105MIdeliver, Version 2.1.06 is Volume 1, Issues 10-14, with Patch 7 to take
- it to version 2.1.07 in Volume 1, Issue 15.
-
-
- Misc Still Abounds
-
-
- What appears to be happening is that everyone is fed up with the tested source
- groups, and going to the immediate post source group, comp.sources.misc.
- Unfortunately, this also leads to more patches being posted, as the testing
- now occurs in public with everyone seeing the bugs instead of in private with
- only the author and the group reviewers. However, even the traffic in this
- group is down. Approximately six megabytes of posting of about 40 packages
- were made over the past two months. Some of the highlights are:
- Dave Mack <csu@alembic.acs.com> posted compress v4.1 as Volume 20, Issues 64
- and 65. However this started some controversy. It appears that the bug fixes
- to compress v4.0 never found their way into compress v4.1, and there is a
- competing version of compress v4.3. Someone is supposed to post a merged
- version of compress v4.1 and compress v4.3, along with checking for the bug
- fixes, but it has not happened yet. I'll keep an eye out for it. In the
- meantime, compress is the main tool UNIX systems use to shrink files via LZW
- compression. Unlike the MS-DOS arc style programs, it does not keep a
- directory of what it compresses, it just compresses the file itself. It then
- renames it to the file name plus a .Z suffix. The compression table size is
- tunable from 12 to 16 bits to achieve the best compression. compress is
- portable to many systems, and I use a version on MS-DOS that supports the same
- 16-bit tables as my UNIX versions. A patch was issued for compress to fix some
- problems as Volume 20, Issue 74.
- Do you need to mix Fortran and C code in the same program? Ever try and call a
- Fortran routine from C? Burkhard Burow <burow@cernvax.cern.ch> must have had
- to often enough that he developed a tool to make the task easier and machine
- independent. His tool, cfortran, was posted as Volume 20, Issues 66 and 67. It
- includes a header file to allow for easy prototyping of Fortran functions in C
- and helps generate C wrappers for C functions so they can be called from
- Fortran. It also includes test and demonstration programs.
- In a local area network of machines, it is important that they all keep the
- same time of day clock. Otherwise, the machine with the slow clock might
- modify a file, and the machine with the fast clock will think that the file is
- not new compared to some other file it recently modified. This can cause all
- kinds of problems with file sharing, NFS, make files, and other tasks that
- require time stamps for synchronization. On the Internet, we use a special
- purpose program called ntp (Network Time Protocol) to set the clocks of all
- machines to within several milliseconds of "standard time." This same program
- then keeps the clocks tracking to that tolerance the entire time the machines
- are up and running. Clarence Dold <dold@mitisft.convergent.com> thinks this is
- overkill for small isolated local networks and has developed a tool that
- allows for slaving several systems to a master with accuracy of a couple of
- seconds instead of milliseconds. The master is kept accurate by whatever
- method is appropriate (nothing, calling NBS on the phone periodically,
- checking it by hand periodically). The rest use his remtime, Volume 20, Issue
- 69, to set their clocks at boot time and periodically resync them to the
- master system while running.
- Jonas Yngvesson <jonas-y@isy.liu.se> has provided supp v2.1, a library for
- rendering scenes with 3-D objects for Volume 21, Issues 26-33. supp v2.1
- includes more rendering modes (Phong, Gourard and line), support for rendering
- into other places than files (including pixmaps), larger oversampling to
- reduce aliasing, two new object primitives (cone and prism), two new shaders
- (strauss and wood), a full polygon clipper, and the ability to remove
- subobjects and surfaces from objects.
- Another simple menu system, supporting text menus, was contributed by Ted
- Wisniewski <ted@oz.plymouth.edu> for Volume 22, Issues 98 and 99. This is an
- updated release of the PSCmenu package. It replaces one released last month
- that replaced one that was last released as Volume 16, Issue 99. The menus are
- kept in simple text flies and can support execution of any UNIX command.
- Want to brag about how fast your new computer really is? One of the
- traditional benchmarks for supercomputers is Lawrence Livermore National
- Laboratory's Livermore Loops program in Fortran. As part of a research project
- to compare Fortran and C for numerical computation, Martin Fouts translated
- the benchmark into C for the NASA Ames Research Center. Cloops was contributed
- for Volume 21, Issues 36-38. It implements the 24 loop version of the
- Livermore Fortran Kernels as described in The Livermore Fortran Kernels: A
- Computer Test of the Numerical Performance Range by Frank H. McMahon (LLNL
- UCRL-53745).
- One of the features of the Berkeley Software Distribution (BSD)-derived UNIX
- systems is the support in the operating system for a feature that determines
- which program will execute a shell file by reading the first line of the file.
- If this line contains #! as the first two characters, then the rest of the
- line is the name of the interpreter to execute this script. AT&T-derived UNIX
- systems before SVR4 do not support this feature, but it can be emulated by
- changes to the exec family of functions. David J. MacKenzie <djm@eng.umd.edu>
- has done just that with libiexec for Volume 21, Issue 39. He has provided
- replacements for the exec family of functions that will check the contents of
- the first two bytes of the file to be executed, and invoke the proper
- interpreter if the first two bytes match the #! magic characters.
- Brandon S. Allbery <allbery@ncoast.org> has updated his malloc debugging
- package and reissued it for Volume 21, Issue 41. The last release was in 1987.
- It now catches writes to either side of malloc'd memory, and checks the pool
- for consistency on each call to malloc, free, realloc, and calloc. It also
- supports a traceback printout of the stack when a malloc check fails, traps
- bus and segmentation violations as if they were bad pointer references and
- dumps the malloc pool, and uses environment variables to control many features
- of the debugging session.
- Warren Tuckers's <wht@n4hgf.mtpark.ga.us> extended call utility package, ECU,
- underwent extensive changes recently. ECU is an asynchronous communications
- program for UNIX and XENIX systems. It incorporates a rich procedural language
- and several file transfer protocols. Revision 3.10 adds support for gcc,
- non-ANSI consoles, and xterms as well as a better configuration system. It was
- issued in 37 parts as Volume 21, Issues 53-89. In addition several patches
- were released. Patch 1, Volume 22, Issues 19-21, enhances the support for ISC
- UNIX. Patch 2, Volume 22, Issue 22, fixes a problem with the nap () system
- call. Patch 3, Volume 22, Issues 70-72, adds support for SunOS 4.1 on Sparcs
- and SVR4. Patch 4, Volume 22, Issues 77 and 78, fixes some SVR4 related
- problems. Patch 5, Volume 22, Issues 90-94, enhances ECU's portability even
- further and adds supports for the fas drivers. Patch 6, Volume 22, Issue 101,
- is just a bug fix for some release problems. A new manual was issued for
- version 3.10 as Volume 21, Issues 90-93. This manual documents the current
- version and runs over 100 pages in five chapters. Now at version 3.16, by way
- of the six patches, ECU now supports many different platforms, not just the
- SCO UNIX/XENIX platforms. With ECU, your UNIX system can have a
- telecommunications program to rival those on the PCs.
- Oishii Ichigo <whiz@well.sf.ca.us> submitted midilib, a library of routines to
- read and write files writing in the MIDI Manufacturer's Association standard
- format. It runs on UNIX, Macs and PCs and may be portable to other systems. It
- can give a verbose textual listing of a MIDI file, convert format 1 multitrack
- files to format 0, and assist in writing MIDI files. It was contributed for
- Volume 21, Issues 96 and 97.
- Benson I. Margulies <benson@odi.com> modified the original BSD indent program,
- which understood only C sources, to understand how to pretty-print C++ sources
- as well. His modified indent was issued as Volume 21, Issues 98-100.
- Making changes to binary files can be difficult. Help is available via the
- Binary Editor and Viewer from Peter Reilley <pvr@wang.com>. BEAV allows for
- changes, insertion, and deletion (yes, changing the size of the file). It can
- enter or display data in hex, octal, decimal, binary, ASCII, or EBCDIC
- formats. It can group by bytes, words, or long words in either little-endian
- or big-endian byte ordering. BEAV is Volume 22, Issues 10-18.
- A much shorter program is prtscrn from Chip Rosenthal
- <chip@chinacat.unicom.com>. It captures the screen contents of an SCO Console
- MultiScreen and sends it to stdout. prtscrn is Volume 22, Issue 27.
- I discussed in a prior column the archie service from archie.mcgill.ca. This
- service provides a database of who is archiving what sources via ftp. Brendan
- Kehoe <brendan@cs.widener.edu> has written a Prospero client to access the
- archie databases without using an interactive process on the remote machine.
- This provides faster, and lower overhead, access to the database for those
- sites that are on the Internet. Archie, version 1.1, is Volume 22, Issues
- 35-39.
- In my April 1990 column I introduced the popi digital darkroom software from
- Rich Burridge <richb@aus.sun.com>. Rich has updated the software and
- re-released it for Volume 22, Issues 40-48. Popi allows arbitrary
- transformations to be interactively applied to digital images. New to this
- release are use of the PBM/PGM/PPM graphics formats, use of Floyd/Steinberg
- dithering for monochrome screens, support for 24-bit color, and many bug
- fixes.
- A new program on the scene is causing many system administrators to pull their
- hair out. Crack v3.2a is a program that performs dictionary searches on the
- UNIX password file. It reports all accounts that have passwords that can be
- compromised by this method. Its intent is twofold. First, to inform the
- average system manager of which accounts could easily be compromised, and,
- second, to weaken the complacency among inexperienced and experienced UNIX
- systems administrators about how secure their passwords are. Crack v3.2a, from
- Alec David Muffett <aem@aber.ac.uk>, is Volume 22, Issues 49-52.
- Raymond Chen <rjc@math.princeton.edu> has contributed wp2x, his program to
- convert WordPerfect version 2.x files to any text-based formatting language.
- The distribution includes configuration files for Tex, LaTex, SCRIPT/GML, and
- troff. WordPerfect 5.x files must be saved in 4.2 format before they can be
- converted by the program.wp2x is Volume 22, Issues 55-57.
- Lutz Prechelt <prechelt@i41s14.ira.uka.de> has developed crefine, a tool to
- add an additional language construct called "refinement," which allows further
- decomposition with symbolic names inside functions for C and C++. crefine,
- Volume 22, Issues 63-66, supports any system that supports C and stdio. It is
- a translator of C source files.
- In one posting, Christian Schlichtherle <chris@attron.ruhr.de> has obsoleted
- three of his own programs. He has released a new, improved version of his
- comment-annotated directory listing program. The new one, with a name that is
- configurable, although he called the package XlsX, obsoletes list, dls, and
- vls. It allows for tieing long description names to the file names and then
- displaying those along with the ls output. XlsX is Volume 22, Issues 83-84.
- Note: this is not an X Window program, even though the name starts with an X.
- Patches were also released to prior packages. Mike McGann's
- <mwm@hasler.ascom.ch> gnuchess 3.1 submission had patch 3, Volume 22, Issue 2,
- issued to improve handling of the opening book.
- Brad Appleton <brad@ssd.csd.harris.com> updated his parseargs package with
- patch 8, Volume 22, Issue 24, to clean up some comments and fix a couple of
- memory leaks.
-
- David Skoll <dfs@doe.carleton.ca> has issued patch 3 for his remind package as
- Volume 22, Issue 102. remind will notify you of events, appointments, or other
- things you feel you should be reminded about. It can signal you or just send
- mail. Patch 3 adds support for the -f option to support foreground execution
- of queued reminders -- very useful for those running remind from a windowing
- system.
- Parag Patel's <parag@hpsdeb.sde.hp.com> waccoLL(1) parser generator had patch
- 2 issued as Volume 21, Issue 44. It fixes a bug where code fragments in {}
- larger than 1024 bytes caused a dump.
- The SC spreadsheet, (last release by Jeff Buhrt <prslnk!buhrt>) had two
- patches. The first, patch 1, Volume 22, Issues 95 and 96, added cell locking,
- some portability changes, and support for MS-DOS. The second, patch 2, Volume
- 22, Issue 104, fixed a null pointer dereferencing problem. It also added a
- high speed data entry mode, new date formats, better support for labels and
- long strings, saving the last cell accesses for the next access, and several
- additional functions.
- Bill Norcott <norcott@databs.enet.dec.com> again completely reissued his
- iozone benchmark as Volume 22, Issue 29. The changes in V1.10 were to increase
- portability to more platforms.
- Fred Walter <grwalter@watfun.waterloo.edu> has also re-released his newsbreak
- program that automatically unpacks postings in the comp.sources and
- comp.binaries news groups. Version 1.14, Volume 22, Issue 53, now supports 286
- systems and ISC UNIX, and fixes several bugs.
-
-
- Games Quiet
-
-
- Even the games group is very quiet. Are computers and their usage now getting
- more serious? Or are the students just too busy studying?
- Streets and Alleys solitaire, reviewed last time, was patched in Volume 12,
- Issue 88. John Ramsdell <ramsdell@linus.mitre.org> added two improvements. The
- first adds command aliases for those without a numeric keypad. The second adds
- the ability to save and restore games. This allows for trying things and then
- returning to the saved place if it didn't work.
- Not to be outdone by another, Raymond Chen <rjc@math.princeton.edu> also
- submitted an anagram generator. In October's column I reviewed Morten
- Ronseth's <morten@dcs.qmw.ac.uk> ag2. Raymond's anagram2 uses a user-supplied
- dictionary (such as/usr/dict/words) to limit its output to valid words. Both
- an ANSI C version and a Perl script to convert it to a K&R C version are
- supplied in Volume 12, Issue 89.
- Pacman still lives. Rich Burridge <richb@aus.sun.com> has updated his sidtool,
- a Pacman like game from SunView to Xview. sidtool allows for specifying
- alternate mazes, including oneway paths and tunnels. A very complete version,
- with lots of bells and whistles, sidtool is Volume 12, Issues 90-96.
-
-
- Previews From alt.sources
-
-
- Even alt.sources is showing the effects of the decline in postings. It's not
- down much, but it is down. Once again, only the highlights -- many more
- programs were posted.
- Hinted at in October, with the release of booz, was the upcoming release of a
- new version of Rahul Dhesi's <dhesi@bsu-cs.bsu.edu> Zoo archiver. Version 2.1
- was released on July 10, 1991, in fifteen parts. It includes improved
- compression, better online help, VAX/VMS file timestamp preservation, faster
- uncompression, and other features. It works on almost any system that supports
- C, including VAX/VMS, UNIX, and MS-DOS.
- For those with Mac format archives, Dik T. Winter <dik@cwi.nl> has provided a
- Mac archive unpacker for unpack, PackIt, StuffIt, Compactor, and most
- StuffItClassic/ StuffItDeluxe archives. It does not deal with password
- protected archives, multifile archives, or compression methods 6 and 8. Unpack
- was posted on July 14, 1991, in two parts.
- Panos Tsirigotis <panos@cs.colorado.edu> has contributed a small socket
- programming library that aids in using Berkeley sockets. It takes care of many
- of the little details in allocating, binding, and connecting sockets. It was
- posted on July 21, 1991, in one part.
- UNIX lacks a queued batch processing system. It has at/atrun and batch, but
- those don't implement a queued system. Alan Saunders <tharr!alan>has
- contributed qbatch, a queued system, on July 23, 1991, in four parts.
- Want to run a mail server? If deliver, mentioned above, is not to your liking,
- try mail-server (or even combine its use with deliver). Posted by Jan-Piet
- Mens <logixwi!jpm> on August 12, 1991, it allows access to a set of files via
- a mail response program. It includes permission lists to restrict which users
- can access which flies.
- Printing PostalNet bar codes on envelopes and labels is made easier with Todd
- Merriman's <toolz!todd> barcode program posted on August 13, 1991. It converts
- zip codes in ASCII to HP Laser Jet graphics commands to print the bar code.
- The ever popular UNIX Pcal, a postscript printer calendar generator, has again
- been updated to version 4.1 by Joe Brownlee <jbr@cblph.att.com> on August 19,
- 1991, in six parts. It now includes better date functionality, better quality
- moon phase calculation, better understanding of where to find calendar event
- files under UNIX, and portability changes.
- The changes required to port GCC 1.40, GAS 1.38.1, and GDB 3.5 from the GNU
- project to SCO XENIX were posted on August 27, 1991, in four parts by Steve
- Blezard <Steve.Bleazard@robobar.co.uk>. This port works on SCO XENIX 386 and
- produces files in the native Microsoft/Intel OMF format. The GNU binutils are
- not used, so the port is compatible with the standard library files.
- If you are stuck with a K&R C compiler and need to compile ANSI C source with
- prototypes, you need unproto from Wietse Venema <wietse@wzv.tue.nl>. Posted on
- September 1, 1991, it will leave K&R C alone, but de-ansify the prototypes of
- any C programs it is passed. It is designed to sit as a pass between the C
- preprocesser and the next stage of the compiler.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- Operator Overloading, Part 1
-
-
-
-
- Dan Saks
-
-
- Dan Saks is the owner of Saks & Associates, which offers consulting and
- training in C, C++ and Pascal. He is also a contributing editor of
- Windows/DOS. He serves as secretary of the ANSI C++ committee and is a member
- of the ANSI C committee. Readers can write to him at 287 W. McCreight Ave.,
- Springfield, OH 45504 or by email at dsaks@wittenberg.edu.
-
-
- No programming language can be all things to all programers. No matter how
- many features a language has, there's always someone who wants just one more.
- A language like FORTRAN that caters to numerical applications provides integer
- and real numbers in a variety of precisions, and even throws in complex
- numbers, but does nothing for programmers who want rational (exact fractional)
- numbers or set algebras. The list of wants is never ending.
- Although you can't create a language with everything that every programmer
- could ever want, you can design a language that gives programmers the tools to
- create the data types they need[1]. This is the approach taken by C++. As
- explained by Bjarne Stroustrup, the inventor of C++:
- "C++ has no high-level data types and no high-level primitive operations. For
- example, there is no matrix type with an inversion operator or a string type
- with a concatenation operator. If a user wants such a type, it can be defined
- in the language itself. In fact, defining a new general-purpose type or
- application-specific type is the most fundamental programming activity in C++.
- A well designed user-defined type differs from a built-in type only in the way
- it is defined and not in the way it is used" [2].
- C++ programmers create new types by defining classes. A class defines both the
- representation of class objects and the operations that can be performed on
- those objects. C++, like C, has many operators that apply to built-in types.
- For user-defined types to appear as if they were built-in, programmers need
- the ability to define new meanings for operators when they are applied to
- user-defined types.
- For example, C++ has no built-in support for complex numbers, but you can
- define complex numbers as a class:
- class complex
- {
- public:
- complex ();
- ...
- private:
- double r, i; // real and
- // imaginary parts
- };
- Class names are type names in C++, so
- complex c1, c2;
- is all your users need do to declare c1 and c2 as complex variables. But, if
- you implement complex addition as a member function called add, then users
- must write
- c1.add(c2);
- to add c2 to c1, and they will know that complex numbers are not built in. For
- complex numbers to look built-in, users must be able to write
- c1 = c1 + c2;
- or
- c1 += c2;
- In fact, you can make complex numbers look built-in using a feature known as
- operator overloading. This month's column shows how to define and use
- overloaded operators.
-
-
- Rational Numbers
-
-
- I'll begin my explanation of operator overloading by implementing a class for
- rational numbers (fractions), with values such as 1/2 or -3/4. Whereas
- floating point numbers represent numbers such as 1/3 and 4/9 only
- approximately, rational numbers represent them exactly.
- My rational number class uses long integers to store the numerator and
- denominator of the fraction. The class defines four overloaded binary
- operators: + (addition), -- (subtraction), * (multiplication), and /
- (division), along with constructors and an output function. Listing 1 shows
- the header rational.h that defines the class.
- The declaration of an overloaded operator is just like the declaration of a
- function, except that the name of the function is the keyword operator
- followed by the operator symbol. For example, the member function declaration
- rational operator+(rational r);
- in Listing 1 declares a member function called operator+ that accepts one
- argument of type rational and returns a rational.
- For the most part, there's nothing magical about a function name like
- operator+. Had I named the function add, the function declaration would look
- like just another ordinary member function declaration:
- rational add(rational r);
- You can call operator+ just as you would call any other member function. That
- is, if r1, r2 and r3 are rationals, then
- r3 = r1.operator+(r2);
- adds r1 and r2 using rational::operator+, and then copies the result to r3.
- Obviously, there must be some other advantage to overloading operators,
- because this syntax for calling operator+ is no more readable than
- r3 = r1.add(r2);
- If there is any magic here, it's that for any object x of a class type, the
- expression x+y means x.operator+(y). In other words, for rationals r1 and r2,
- the expression r1+r2 is a familiar shorthand syntax for calling
- r1.operator+(r2).
- Listing 2 shows a small test program that uses rationals, and Listing 3 shows
- the output from that program. Note that this implementation of rational
- numbers doesn't reduce fractions to their simplest form. That is a detail I
- will address later.
- In Listing 2, a rational declaration such as
- rational r2 (3, 5);
-
- uses the constructor
- rational::rational(long n, long d);
- to initialize r2 with the value 3/5 (three-fifths). Although 3 and 5 are int
- (not long int) constants, a C++ compiler applies integral promotions to the
- arguments if necessary to find a constructor with a signature that matches the
- call. (See my earlier column, "Function Name Overloading," CUJ, Nov. 1991, for
- an explanation of function signatures.)
- Notice that you can call that constructor to create rational numbers on the
- fly for use in expressions. For example, in the statement
- r2 = r2 * rational(2, 3) + r1;
- the subexpression rational(2, 3) calls the constructor to create a temporary
- rational object whose value is 2/3. Aside from the fact that rational
- constants must be written as rational (x, y) instead of x/y, expressions
- involving rationals look like expressions involving primitive types.
- Nearly all the built-in unary and binary operators can be overloaded. Only
- . . * : : ? : sizeof # # #
- cannot. (.* is the dereferencing operator for pointers to members, which I
- have not yet covered in this column. # and ## are preprocessing operators for
- stringizing and pasting.) The ANSI C++ committee is even considering allowing
- overloaded. (dot). The operators that are both unary and binary,
- + - * &
- can be overloaded both ways.
- Overloading operators does not change their precedence or associativity. For
- example,
- r2 = r2 * rational(2, 3) + r1;
- is evaluated as
- r2 = ((r2 * rational(2, 3)) + r1);
- because * has higher precedence than + and + has higher precedence than =. You
- cannot create new operator tokens by overloading. For example, you cannot
- create ** as an exponentiation operator.
- I will present an implementation of the overloaded rational operators shortly.
- But first, I must explain a little about the underlying mechanism of member
- function calls.
-
-
- Using The Keyword this
-
-
- A class member function operates implicitly on the class object to which the
- function is applied. For example, suppose class T looks like
- class T
- {
- public:
- void f(const T &r);
- void g(const char *s);
- ...
- private:
- int m, n;
- ...
- };
- Then the call
- x.f(y);
- means "apply member function f to object x using y as an argument." Inside the
- body of f, an unqualified reference to a class member, say m, is a reference
- to the m member in the T object for which the member was called. For instance,
- given the previous function call, the assignment in
- void T::f(const T &r)
- {
- ...
- m = r.m * n;
- ...
- }
- multiplies y's m (referring to y through r) times x's n and stores the result
- in x's m. The compiler knows how to find y, because it was passed explicitly
- as the argument r. But how does f know where to find x?
- The member function f actually has a hidden extra argument that's the address
- of the object to which the function applies. A call such as
- x.f(y);
- translates into the C-like function call
- f(&x, y);
- Inside the member function, you can refer to the hidden argument by the
- keyword this. In every member function of class T, the compiler implicitly
- declares this as a pointer whose type is T *const. In other words, the member
- function declaration
- void T::f(int i);
- translates into the C-like declaration
- void f(T *const this, int i);
- Note that the this pointer itself is const, but *this is not const. This means
- you can't alter the pointer, but you can alter the object it addresses.
- Inside the body of a member function, every unqualified reference to a data
- member of T is implicitly prefixed with this->. For example, inside f, the
- statement
- m = r.m * n;
- compiles as if it were written as
- this->m = r.m * this->n;
- Also, if f calls another member function from its own class, such as
- g("hello");
- the call compiles as if it were written as
-
- g(this, "hello");
-
-
- Implementing rational's Operators
-
-
- Listing 4 shows the source file rational.cpp that implements the member
- functions for the rational class. All of the operator functions use the same
- implementation strategy:
- 1) create a local rational object called result;
- 2) compute result's numerator;
- 3) compute result's denominator;
- 4) return (a copy of) result.
- The four operator functions apply the formulae shown in Table 1.
- As I explained earlier, a class member function operates implicitly on the
- class object to which the member is applied. The expression
- r1 + r2
- compiles as
- r1.operator+(r2)
- which means "apply member function operator+ to r1 using r2 as the argument."
- In the body of operator+, num and denom not prefixed with r. refer to the num
- and denom of the left operand, and r.num and r.denom refer to the num and
- denom of the right operand.
- Class rational has two constructors: a default (parameter-less) constructor
- and another two-argument constructor that initializes the numerator and
- denominator explicitly. I needed the two-argument constructor to create
- rational number objects with specific values. I used the default constructor
- to declare rational variables when I didn't care about the initial value, as
- in the first line inside the body of each operator function in Listing 4. The
- compiler generates a default constructor only if the class has no other
- constructors. Had I not declared the default constructor explicitly, then the
- class would not have a default constructor, and a declaration like
- rational result;
- would be illegal.
- It turns out that, at least for this application, you don't really need a
- default constructor. By using the expressions for the numerator and
- denominator as arguments to the two-argument constructor, you can compute the
- results in a temporary object and return the temporary. That is, for any
- expressions n and d that are convertible to long int, you can rewrite
- rational result;
- result.num = n;
- result.denom = d;
- return result;
- as simply
- return rational(n, d);
- The simplified implementations for the rational operators appear in Listing 5.
-
-
- Assignment Operators
-
-
- In addition to using the four overloaded rational operators, the program in
- Listing 2 also applies the assignment operator = to rationals. In fact there
- are two assignments:
- r1 = (r1 + r2) / rational(1, 2);
- r2 = r2 * rational(2, 3) + r1;
- which work as you'd expect, even though the rational class doesn't define
- operator=.
- If a class doesn't define operator=, the compiler generates a default version.
- The generated operator= performs what is called a memberwise copy -- it copies
- each member of the right-hand operand to the corresponding member of the
- left-hand operand. For each member of the class that is itself a class object,
- memberwise copy uses that member's operator=.
- For a class T, the generated assignment operator is declared as either
- T &T:: operator=(const T &);
- or
- T &T::operator=(T &);
- If all of T's members are primitive types or class types with assignment
- operators of the first form, then T's assignment operator also uses that form.
- Otherwise, T's assignment operator uses the second form.
- Notice that both forms of the overloaded assignment operator return a
- reference to a class object. At first, you might expect that assignment has a
- void return. The most common use for assignment is as a single statement like
- r1 = r2;
- But in C++, as in C, assignments are not just statements; they are expressions
- that can be used in other expressions, such as
- if ((r1 = r2) != 0)
- or
- r1 = r2 = r3;
- = is right-associative (grouped from right to left), so the latter statement
- is interpreted as
- r1 = (r2 = r3);
- When applied to built-in types, the = operator yields the value of its left
- operand. The default operator= for a class type preserves this behavior for
- class types.
- Whenever you explicitly overload an assignment operator, you should have the
- function return a reference to its left operand. That is, the return statement
- in an overloaded assignment should be something resembling
- return *this;
- Note that a function returning a reference cannot use
- return this;
- When a function returns a reference, the return statement binds the return
- expression value to the returned reference using the same semantic rules as a
- reference declaration. When you declare a reference such as
- rational &r1 = r2;
- r2 must be a rational or a rational &; it cannot be a rational *. You cannot
- initialize a reference with the address of another rational:
-
- rational &r1 = &r2;
- because the type of &r2 is rational *. By this rule, the return expression of
- a function that returns a reference must be an object and not a pointer.
- return *this;
- binds the reference to an object, but
- return this;
- is an error because it attempts to bind a reference to a pointer. Listing 6
- shows an implementation for rational::operator= that has the same
- functionality as the default memberwise assignment generated by the compiler.
- Overloaded definitions for the other assignment operators, like += and *=,
- should also return a reference to the left operand. Listing 7 shows the
- definition of class rational extended to include definitions for +=, -=, *=,
- and /=. The implementation of each assignment is very similiar to its
- corresponding binary operator.
- Listing 8 shows an implementation of rational::operator+= and another version
- of rational::operator+ rewritten in terms of rational::operator+=. operator+=
- computes its result directly in the left operand and then returns a reference
- to that operand (by referring to this). operator+ creates a local rational
- called result, initialized by copying this using the (default memberwise) copy
- contructor. operator+ computes result using operator+=, and returns a copy of
- (not a reference to) that result.
- Listing 9 shows an implementation of rational::operator-= written in terms of
- rational::operator-. The operator- in Listing 9 is the same as the one in
- Listing 5. operator-= computes its result in its left operand (*this) using
- both operator- and operator=, and then returns a reference to that left
- operand.
-
-
- Reducing Fractions To Simplest Form
-
-
- The output from my little test program in Listing 3 shows that the rational
- operators don't reduce fractions to their simplest form. For example the
- fraction 22/10 equals 11/5, and -270/150 equals -9/5. Listing 3 shows that
- each arithmetic operation on a rational makes both the numerator and
- denominator grow. Unless you eliminate the common multiples in the numerator
- and denominator, they will overflow after only a few more operations.
- I solved this problem by adding a private member function rational::simplify.
- The revised class definition appears in Listing 10 and the member function
- definitions appear in Listing 11. simplify divides both the numerator and
- denominator by the greatest common divisor, which is computed by calling gcd.
- gcd is a non-member function because I thinks it's a general-purpose function
- that should eventually go into an application-independent library of math
- functions. (This version of gcd is based on one by Niklaus Wirth[3]. I don't
- know if it's optimal, but it seems to work well for this application.)
- I rewrote all the rational binary operators in terms of their corresponding
- assignment operators. Every assignment operator calls gcd. This assures that
- each arithmetic operation on rationals always leaves the resulting fraction in
- its simplest form. Listing 12 shows the revised output of the test program
- with simplified fractions.
- My technique is an improvement, but it doesn't prevent every avoidable
- overflow. Consider this example:
- (999999/1000000) * (1000000/3)
- The result of this multiply is 333333/1, but the numerator overflows before
- the simplify function reduces the fraction to its simplest form. A more robust
- implementation would eliminate the greatest common divisor before multiplying,
- not after.
- I will continue this discussion of overloaded operators in my next column.
-
-
- Inquiring Minds Want To Know...
-
-
- I recently received this question via electronic mail:
- Your article on reference types in the C Users Journal (September 1991) struck
- a cord with me, but not as you might expect. I should add that I am a spotty
- reader of CUJ, so I hope I am not dealing with an old issue.
- In your opening remarks, you say "Just as many programmers often pronounce int
- *as "int star," I often pronounce int & as "int ref." I have been programming
- in C++ for about a year now, and have been following it with interest longer
- than that. And, I must admit, with not a little embarrassment, that I DON'T
- KNOW HOW TO PRONOUNCE IT! Is it "C Plus Plus" (which I always use), or "C
- Incremented"? My "intellectually minded" colleagues and I have been arguing
- this for at least a year. I have never seen a definitive statement about this
- very important topic.
- Could you please answer this, and put my mind to ease? Maybe even publish the
- answer. Thanks in advance.
- David D. Hathaway
- 762 N. Ripley St.
- Alexandria, VA 22304
- 76104.1042@CompuServe.COM
- It's pronounced "C plus plus." That's the way I've heard the inventor Bjarne
- Stroustrup pronounce it, and he says so in his latest book[2].
- References
- [1] Jagger, Mick and Keith Richard, "You Can't Always Get What You Want," Let
- It Bleed. Gideon Music, 1968.
- [2] Stroustrup, Bjarne, The C++ Programming Language, 2nd. ed. Addison-Wesley,
- 1991.
- [3] Wirth, Niklaus, Algorithms + Data Structures = Programs. Prentice-Hall,
- 1976.
- Table 1
- a c a * d + b * c
- - + - = -------------
- b d b * d
-
- a c a * d - b * c
- - - - = -------------
- b d b * d
-
- a c a * c
- - * - = -----
- b d b * d
-
- a
- -
- b a * d
- ----- = -----
- c b * c
- -
- d
-
-
- Listing 1 (rational.h)
- #include <stdio.h>
-
- class rational
- {
- public:
- rational() { }
- rational(long n, long d) : num(n), denom(d) { }
- rational operator+(rational r);
- rational operator-(rational r);
- rational operator*(rational r);
- rational operator/(rational r);
- void put(FILE *);
- private:
- long num, denom;
- };
- // End of File
-
-
- Listing 2 (test1.cpp)
- #include <stdio.h>
- #include "rational.h"
-
- int main()
- {
- int i;
- rational r1 (1, 2); // r1 = 1/2;
- rational r2 (3, 5); // r2 = 3/5;
-
- for (i = 0; i < 3; ++i)
- {
- printf("r1 = ");
- r1.put(stdout);
- putchar('\n');
- printf("r2 = ");
- r2.put(stdout);
- putchar('\n');
- r1 = (r1 + r2) / rational(1, 2);
- r2 = r2 * rational(2, 3) + r1;
- }
- return 0;
- }
-
- // End of File
-
-
- Listing 3
- r1 = (1/2)
- r2 = (3/5)
- r1 = (22/10)
- r2 = (-270/150)
- r1 = (1200/1500)
- r2 = (-1350000/675000)
-
-
- Listing 4 (rational.cpp)
- #include "rational.h"
-
-
- rational rational::operator+(rational r)
- {
- rational result;
- result.num = num * r.denom + r.num * denom;
- result.denom = denom * r.denom;
- return result;
- }
-
- rational rational::operator-(rational r)
- {
- rational result;
- result.num = num * r.denom - r.num * denom;
- result.denom = denom * r.denom;
- return result;
- }
-
- rational rational::operator*(rational r)
- {
- rational result;
- result.num = num * r.num;
- result.denom = denom * r.denom;
- return result;
- }
-
- rational rational::operator/(rational r)
- {
- rational result;
- result.num = num * r.denom;
- result.denom = denom * r.num;
- return result;
- }
-
- void rational::put(FILE *f)
- {
- fprintf(f, "(%1d/%1d)", num, denom);
- }
-
- // End of File
-
-
- Listing 5 (rational.cpp)
- #include "rational.h"
-
- rational rational::operator+(rational r)
- {
- return rational(num * r.denom + r.num * denom,
- denom * r.denom);
- }
-
- rational rational::operator-(rational r)
- {
- return rational(num * r.denom - r.num * denom,
- denom * r.denom);
- }
-
- rational rational::operator*(rational r)
- {
- return rational(num * r.num, denom * r.denom);
- }
-
-
- rational rational::operator/(rational r)
- {
- return rational(num * r.denom, denom * r.num);
- }
-
- void rational::put(FILE *f)
- {
- fprintf(f, "(%1d/%1d)", num, denom);
- }
-
- // End of File
-
-
- Listing 6 (operator=)
- rational &rational::operator=(const rational &r)
- {
- num = r.num;
- denom = r.denom;
- return *this;
- }
-
- // End of File
-
-
- Listing 7 (rational.h)
- #include <stdio.h>
-
- class rational
- {
- public:
- rational() { }
- rational(long n, long d) : num(n), denom(d) { }
- rational operator+(rational r);
- rational operator-(rational r);
- rational operator*(rational r);
- rational operator/(rational r);
- rational &operator+=(rational r);
- rational &operator-=(rational r);
- rational &operator*=(rational r);
- rational &operator/=(rational r);
- void put(FILE *);
- private:
- long num, denom;
- };
-
- // End of File
-
-
- Listing 8 (operator+=)
- rational &rational::operator+=(rational r)
- {
- num = num * r.denom + r.num * denom;
- denom *= r.denom;
- return *this;
- }
-
- //
- // operator+ written in terms of operator+=
-
- //
- rational rational::operator+(rational r)
- {
- rational result(*this);
- return result += r;
- }
- // End of File
-
-
- Listing 9 (operator-)
- rational rational::operator-(rational r)
- {
- return rational(num * r.denom - r.num * denom,
- denom * r.denom);
- }
-
- //
- // operator-= written in terms of operator-
- //
- rational &rational::operator-=(rational r)
- {
- return *this = *this - r;
- }
- // End of File
-
-
- Listing 10 (rational.h)
- #include <stdio.h>
-
- class rational
- {
- public:
- rational() { }
- rational(long n, long d) : num(n), denom(d) { }
- rational operator+(rational r);
- rational operator-(rational r);
- rational operator*(rational r);
- rational operator/(rational r);
- rational &operator+=(rational r);
- rational &operator-=(rational r);
- rational &operator*=(rational r);
- rational &operator/=(rational r);
- void put(FILE *);
- private:
- long num, denom;
- void simplify();
- };
- // End of File
-
-
- Listing 11 (rational.cpp)
- #include <stdlib.h>
- #include "rational.h"
-
- rational &rational::operator+=(rational r)
- {
- num = num * r.denom + r.num * denom;
- denom *= r.denom;
- simplify();
-
- return *this;
- }
-
- rational &rational::operator-=(rational r)
- {
- num = num * r.denom - r.num * denom;
- denom *= r.denom;
- simplify();
- return *this;
- }
-
- rational &rational::operator*=(rational r)
- {
- num *= r.num;
- denom *= r.denom;
- simplify();
- return *this;
- }
-
- rational &rational::operator/=(rational r)
- {
- num *= r.denom;
- denom *= r.num;
- simplify();
- return *this;
- }
-
- rational rational::operator+(rational r)
- {
- rational result(*this);
- return result += r;
- }
-
- rational rational::operator-(rational r)
- {
- rational result(*this);
- return result -= r;
- }
-
- rational rational::operator*(rational r)
- {
- rational result(*this);
- return result *= r;
- }
-
- rational rational::operator/(rational r)
- {
- rational result(*this);
- return result /= r;
- }
-
- void rational::put(FILE *f)
- {
- fprintf(f, "(%1d/%1d)", num, denom);
- }
-
- long gcd(long x, long y)
- {
- x = labs(x);
-
- y = labs(y);
- while (x != y)
- {
- if (x < y)
- y -= x;
- if (y < x)
- x -= y;
- }
- return x;
- }
-
- void rational::simplify()
- {
- long x = gcd(num, denom);
- num /= x;
- denom /= x;
- }
-
- // End of File
-
-
- Listing 12
- r1 = (1/2)
- r2 = (3/5)
- r1 = (11/5)
- r2 = (-9/5)
- r1 = (4/5)
- r2 = (-2/1)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Illustrated C
-
-
- A Portable Menu Compiler
-
-
-
-
- Leor Zolman
-
-
- A long time ago, Leor Zolman wrote and distributed the BDS C Compiler for CP/M
- (what's that?). Following a several-year hiatus from computer-compulsiveness
- to learn some people skills, he got married, dragged his disbelieving wife to
- Kansas and joined the staff of R&D Publications, Inc. Two years later his wife
- has almost forgiven him. You can reach him at leor@rdpub.com or
- uunet!bdsoft!rdpub!leor.
-
-
- Here at R&D Publications, we do most of our internal data processing on a
- single SCO XENIX/386 system running the latest available releases of the
- Informix database system for XENIX. At any time, there may be up to 30 users
- sharing the system. Many of those users run under the FACET/TERM software
- package to launch multiple login sessions on their individual serial
- terminals. Thus it is not unusual to see up to 50 logical users, and perhaps
- up to 150 system processes, active at any single point during the business
- day. Despite the load, the system response time experienced by our users is
- pretty darn good.
- And now for the punch line: our CPU is just an inexpensive 386/25 Taiwanese
- clone. Even with 12Mb of RAM, system units such as this one sell for about
- half the price of the 10Mb hard disk drive I purchased in 1980. My intention
- isn't to tout the cost-effectiveness of imported hardware; I just think it is
- remarkable how well a contemporary entry-level CPU can be made to perform. A
- system load such as ours could have brought even a minicomputer system to its
- knees not so long ago.
- One way we've managed to streamline our system is to eliminate as many
- CPU-intensive tasks as possible from the daily during-business-hours load. We
- did this partially through the design of a general-purpose sequential
- overnight job spooler. From among the set of tasks that used to be routinely
- run during the day, we looked for the worst bottlenecks. We then modified the
- shell scripts that control these programs to allow users to schedule the
- programs for overnight execution.
- The CMENU menu system I shall be describing in this series of columns was
- originally created as part of our effort to reduce the overall daily system
- load. In the end, CMENU also brought some welcome spinoffs: it significantly
- reduced the Technical Department's maintenance requirements, and enhanced the
- usability, speed, and efficiency of the menu system for our users.
-
-
- The 50-Percent Solution
-
-
- R&D's entire internal business management system revolves around menus. All
- the menus stem from two root menus we invoke from the system prompt. The first
- of these two menus contains all invoice processing and customer-related tasks.
- The second main menu handles our advertising subsystems and personal utilities
- (E-mail, calendar maintenance, business letter generation, etc.). Counting all
- the submenus and sub-sub-menus, there are roughly 300 distinct menu options in
- the system. Many of these are shell scripts that manage standardized parameter
- entry and invoke Informix report generators.
- Before CMENU, this entire menu system ran under Informix's own menu-processing
- scheme. Menu headers and their associated lists of menu items existed as
- database tables in a master/detail relationship. The mechanism supplied by
- Informix for creating and maintaining the menu system relied on a screen form
- built to work with Informix's general-purpose table query tool, Perform. This
- screen, unfortunately, allows only one menu selection to appear on the screen
- at a time, which made on-line searches for a particular item in the menu
- hierarchy a repetitive, time-consuming operation.
- Informix integrated their menu-maintenance programs into their new-generation
- SQL runtime module, but imposed the limitation that each database can contain
- only a single associated menu system. Since there were two distinct menu
- hierarchies which we used with our one major database, we had to create a
- dummy database just to support the existence of a second independent menu
- hierarchy. Each time a user invoked a menu, the menu program launched two
- system processes. The first was an SQL interpreter to run the user-interface
- portion of the menu, and the second was a back-end SQL engine that interfaced
- with the actual database. Then, in the cases where we had logically isolated
- some additional menu subsystems from the main menus (as when developing new
- subsystems or when we had not yet ported an existing application from our old
- Informix 3.3 system and the menus were still in the old format), any
- invocation of an external (non-integrated) submenu from the main menu system
- meant that
- all the processes from the main menu were still active,
- a new set of processes was launched to handle the new menu, and
- yet more processes came along when the user made a selection from the new
- menu!
- Clearly, this was not an effective use of system resources.
- In contrast to the clumsy internal implementation and maintenance difficulties
- of Informix's menu system, the end-user interface portion of the Informix
- system is remarkably clean, intuitive, and generally user friendly. I chose
- largely to retain the look and feel of their user interface in designing CMENU
- as a replacement for their menu system. This choice allowed changeover at R&D
- to take place with a minimum of retraining. After installation of the new
- CMENU system, a short e-mail message to all users outlining CMENU's few
- extensions to the Informix menu system's user interface sufficed for the
- entire retraining.
-
-
- The Best Of Both Worlds
-
-
- To the end-user, CMENU-based menus appear roughly the same as their
- Informix-based predecessors. Internally, however, CMENU works quite
- differently. Rather than being built on top of a complex database package,
- CMENU is a standalone system using a pre-compilation scheme to make execution
- (interpretation) of menus as efficient as possible.
- The menu compiler module, named cmenu, compiles an ASCII-format menu
- specification file containing any number of "menu screen" specifications into
- an intermediate binary object format. The menu runner module, rmenu, loads the
- intermediate file(s) into RAM and executes the menu system as specified.
- The advantage of pre-compiled menu specifications is that most of the hard
- work of parsing the source code and compiling it into a form suitable for
- efficient runtime interpretation only happens once, at compilation time. When
- rmenu is invoked, it only has to display the text on the screen, interpret the
- user's keystrokes, process menu navigation commands, and submit the action
- commands associated with selected menu commands to the operating system for
- execution.
- To reduce the number of processes needing to be launched by the runtime menu
- system, rmenu employs a recursive data structure. Separate screens of a menu
- system may be compiled either together in one source file or separately in
- distinct physical source files. At runtime there is no need to spawn
- additional processes just to traverse up and down through the menu structure,
- even when that structure contains separately compiled menu files. A single
- process coordinates all the dynamic loading and menu navigation operations.
- Through a rich, powerful repertoire of options in the CMENU specification
- language, a high level of control over menu behavior is possible in response
- to the varying requirements of application programs and shell scripts. Some
- applications, for example, may require a prompt after their completion to
- allow users to read vital information left on the screen immediately before
- the application terminated. Other applications never leave any useful
- information on the screen and so do not require a prompt. Still other
- applications may sometimes require a prompt and sometimes not, based upon
- their exit status code. Any of these cases can be handled easily by CMENU (at
- least the UNIX version) through appropriate use of the specification language.
-
-
- Two Programs, Two Personalities
-
-
- The cmenu and rmenu programs share a common intermediate menu object format
- and not much else. Each evolved in its own style of programming, necessarily
- distinct from the other due to the disparate natures of the two connected
- tasks.
- The menu compiler, cmenu, operates as an iterative state machine. It processes
- the source file text sequentially, and maintains all state information in
- global data shared by all the token-processing functions of the program.
- To understand why such a scheme is appropriate for the cmenu program, note
- that the CMENU specification language, like C itself, is not block-structured.
- In C, you define all functions at one top level and can't define functions
- within functions (although you can reference functions from within other
- functions). Other constructs in C, such as expressions, do have a recursive
- nature, but there are no such recursive expression structures in a CMENU
- specification. Thus, a sequential approach works well for parsing CMENU source
- code and also makes the program less complicated.
- On the other hand, the sort of simplicity inherent to cmenu wouldn't work for
- rmenu without severely limiting its power. At run time, menus must be callable
- from other menus, and, ideally, there should be no structural limitation on
- the depth to which menus nest. In practice, of course, memory may eventually
- run out.
- Several steps can be taken to increase the practical limit on the number of
- menus or total menu items a single logical menu system can contain. The first
- is to design a longitudinal menu tree structure, to take maximum advantage of
- the dynamic loading features of CMENU. The second is to compile the CMENU
- program using the huge memory model, so array-size limitations are eliminated.
- All my testing was performed with the CMENU programs compiled with the default
- "small" memory model, for efficiency.
- So, one way rmenu differs from cmenu is that rmenu's menu processing functions
- support recursion. There is only one trivial case where rmenu would not employ
- recursion: when a complete menu system consists of only a single menu file,
- and that file contains only a solitary menu definition with no submenu
- references. In any other configuration, recursive calls will always occur.
-
-
- ...And At Least Two Operating Systems
-
-
-
- Another distinction between the two programs is platform-dependence. Both
- programs support C compilers of varying ANSI-compliance, but aside from that,
- cmenu's code is pretty much system-independent. Since cmenu simply compiles a
- text file into a binary file, the issue of real-time user interface management
- (i.e., curses) does not arise.
- rmenu, however, must deal with the user's screen. Pandora's box immediately
- opens, because DOS and UNIX systems have completely different ideas of what
- the user's terminal is like. UNIX sees terminals as serial devices, while DOS
- sees them as direct memory-mapped devices (that is an oversimplification, but
- it'll suffice for present purposes).
- A standard utility library for managing the screen -- the curses library -- is
- available with most UNIX-like systems. The curses library represents enough of
- a standard that several PC-based implementations have even popped up. I
- selected a PC curses package that we distribute through our CUG library. The
- package is called, appropriately, "PC Curses" (CUG volume 298), and it was
- written by Jeff Dean (who, by the way, volunteered extensive assistance to me
- in checking out the CMENU package for bugs and portability issues. Thank you
- Jeff!).
- PC Curses allows the CMENU system to be compiled under DOS with very few
- conditionally compiled variations in the terminal-handling code. This is
- possible because Jeff's library uses the same standard function names as the
- UNIX versions of curses.
- There are some minor variations between the UNIX and DOS flavors of curses,
- and there are also other areas of the CMENU system that must be handled
- differently between UNIX/XENIX and DOS. The most irritating one involves the
- idea of "current working directory," or CWD, and how the CWD can change
- unexpectedly after submitting command strings to the respective operating
- system's command processors.
- Under UNIX, a subshell can never alter the parent shell's current working
- directory, period. There are subtle tricks for letting a child process have a
- say in setting the CWD of its parent, but ultimately the parent is always in
- control of its own CWD.
- Not so under DOS. Once you pass an unknown command string to the command
- processor under DOS, you can't know for sure where the CWD will be upon
- return. The current working directory and currently selected drive (two
- distinct global modes, as we'll see later) must be saved before making any
- system calls. Then, upon return from system calls, those settings must be
- restored.
- Finally, screen and terminal characteristics may vary from system to system.
- Features such as lines per screen, column width, and operation of certain keys
- are terminal-dependent. Under UNIX, for example, certain versions of curses
- support predefined cursor key codes and certain others do not. Under XENIX, at
- least one curses library function name is different than the equivalent curses
- function name on other UNIX-variants. CMENU's header files try to handle known
- variations such as this, to keep the program text as uncluttered with
- conditional compilation directives as possible.
- The CMENU system may be compiled interchangeably under either DOS or XENIX
- without requiring any changes to the code; just use the appropriate makefile
- for the target operating system. Figure 1 shows the makefile for DOS. This
- version works specifically with the Borland C++ make program, but can easily
- be adapted to other dialects of make. Figure 2 shows the UNIX/XENIX version of
- the makefile.
- Each makefile uses the C compiler's -D command line option to define the
- target operating system and cause the appropriate variations of
- system-dependent code to be compiled. Several lines at the top of the makefile
- may be customized as needed to configure CMENU properly for the desired target
- environment.
- There may be issues with UNIX-variants (other than XENIX) that I haven't
- foreseen. One area that I know must be changed for other UNIX systems is the
- way the makefile specifies the names of the curses library object files. It
- works as supplied on XENIX, but I can make no other guarantees.
-
-
- The CMENU language
-
-
- Figure 3 shows a modified BNF description of CMENU's menu definition language.
- This section presents a detailed English description.
- A CMENU description file has one or more menu definitions. The first menu
- appearing in the file is always the main menu for that file, and need not have
- a name. Since additional menus appearing in the same file can only be accessed
- by name via the lmenu (local menu) option in the main menu, all such
- additional menus must be given unique names.
- Each menu definition begins with a menu clause, consisting of the keyword
- menu, an identifier (optional only for the main menu, required otherwise), and
- an optional colon. After the menu clause come any desired menu options,
- followed by the item definitions. The menu definition is complete when the
- endmenu keyword appears.
- A menu system may consist of any number of separate menu files (or compiled
- units). Traversal across compiled units at runtime is totally transparent from
- the user's point of view.
- Selecting and returning from an external menu (that is, a separately compiled
- unit) appears the same as selecting and returning from a local menu, since the
- user receives no indication that another compiled unit has just been
- dynamically loaded. Within a single menu definition, global menu options may
- be specified immediately after the opening menu clause, but before the first
- item has appeared. If such options appear in a menu, they control screen
- processing for the entire menu (but not for any submenus that might be
- invoked). The available menu options and their functions are:
- path -- Defines the default path for all action items in the menu
- escape, noescape -- Tells whether to allow shell escapes
- spacing -- Sets vertical spacing between items (single or double)
- columns -- Tells number of horizontal columns to be used (1 - 6)
- There is code supporting the compilation of a text alignment option, but there
- is currently no runtime support for this option. There didn't seem any real
- need for it. In case someone wants to add customized options to CMENU, I've
- left the code in as a template to provide a starting point for work on such
- extensions.
- After all desired menu options have been specified, the item definitions
- follow. Each item definition begins with an item clause, consisting of the
- keyword item, an optional item identifier, an optional colon, and an optional
- text string (the text string is optional only because the item text may
- alternatively be specified in its own text clause later in the item
- definition).
- Not all components of the item clause are always optional. If the clause does
- not include an item identifier but does include a text string, a colon between
- the item keyword and the text string is required. This keeps the text string
- from getting parsed as an item identifier. When both an identifier and item
- text are present, the colon may be omitted.
- The only strict requirement for each item definition, other than the item
- clause itself, is an action clause. The action clause must be one of the
- following:
- action -- Execute a system command, or set of commands
- lmenu -- Run a local menu (one in the active .mnc unit)
- emenu -- Run an externally compiled .mnc unit
- exit -- Return to the previous menu; if none, exit
- A menu does not require an exit action, since the user may directly exit a
- menu anytime via the e or x runtime keystroke commands. Sometimes, though,
- displaying the exit option on the screen with other menu items can be useful.
- I've included the exit action code to provide that capability.
- Before or after the action code, some item options may also appear. Many of
- these item options have default behaviors that apply in the absence of
- explicit directions. Such defaults are hard-wired at rmenu compile time by
- definitions in the rmenu.h header file. They may be chosen to suit the
- specific requirements of your user environment. Any item options stated
- explicitly in a CMENU specification always override the defaults. These are
- the available item options:
- text -- If the text isn't part of the item clause, a text clause must be
- provided.
- help -- A help message, appearing only when the highlight bar is on the
- associated option.
- path -- A full pathname overrides the default path; a relative pathname is
- appended onto the default path.
- prompt, noprompt -- Controls whether the system pauses for a keystroke
- following termination of an action item.
- pause, nopause -- Equivalent to prompt/noprompt above (I couldn't decide which
- terms were clearer, so I left them both in).
- preclear, nopreclear -- Controls whether an explicit screen clear occurs
- before execution of an action item.
- nextitem -- Tells where to send the highlight after the current item has been
- executed. Options are:
- first -- to the first item in the menu
- last -- to the last item in the menu
- next -- to the next item in sequence
- <ident> -- to the item with the given identifier
- If no nextitem clause is present, the default is for the highlight to remain
- on the item that was just run.
- The path string, if specified, causes a cd command to be pre-pended to the
- action string when running under a UNIX variant. If running under DOS, then
- both a cd and a drive selection operation are performed, to ensure that the
- named drive/directory is current before the associated action statement
- executes.
- Under UNIX, the action clause (with possibly pre-pended cd statement) is
- passed directly to a system() call. DOS does not support multiple commands on
- a single command line the way UNIX does, but some special processing supports
- compound statements under DOS. This code begins by scanning the action text
- for semicolons. If it finds any, then it breaks the action text into a set of
- individual subcommands as delimited by the semicolons. Each subcommand is then
- processed by an independent system() call. This allows several DOS commands to
- be chained together in a single action clause text string.
- Upon completion of all listed actions, the original drive and path are
- restored under DOS. Under UNIX, this isn't necessary, since a child shell
- cannot alter the parent's current directory.
- An item definition implicitly ends when either another item clause or an
- endmenu keyword is encountered.
-
-
- CMENU Language Minutiae
-
-
- The preceding section sums up the high-level structure CMENU language. There
- remain only a few syntactic details to mention before moving on.
- Tokens in the CMENU language are delimited by one or more of the following:
- any whitespace (space, tab, or newline), ; (semicolon) or , (comma). Figure 4
- and Figure 5 illustrate the format I use, but there is no reason
- (syntactically) that you couldn't create something to rival Don Libes's
- "Obfuscated C Code" winners if that is what you want to do.
- The parser considers a string to be any sequence of printable characters that
- is not a keyword and does not contain any whitespace. Strings are legal as the
- operand of a path, text, or action clause, and as labels where needed. Single
- or double quotes may be used to delimit a string, and, except in the case of
- labels, prudence dictates doing so. The quotes, however, are not strictly
- required if the string contains no whitespace; I have, in a rush, written
- lines such as
-
- action mail
- with only a token amount of guilt (sorry) for omitting the quotes around mail.
- Within a string, all characters are taken literally, including the backslash
- character (\). There is no provision for specifying control characters. There
- is only one restriction on allowable character codes within a string: single
- and double quote characters cannot both appear within the same string. If you
- need to include double quotes within a string, delimit that string with single
- quotes (and vice-versa).
- If your editor can generate control characters, you can put them into CMENU
- strings. In practice, however, I've never run across the need to insert a
- non-printable character into any CMENU text string.
- Identifiers, used to assign labels to menus and items, follow the same naming
- rules as C variables, with one exception: case is insignificant. Internally,
- all identifier names are represented by lower-case characters.
- Any token beginning with a digit is parsed as the beginning -- and typically
- the end, since meaningful values are all single-digit here -- of a decimal
- integer value, appropriate only as the operand to either the spacing or
- columns options.
- Finally, the # character (except inside a quoted string) denotes a comment.
- All characters from # to the end of the line are ignored. A line may begin
- with #.
-
-
- The Saga Of Pathname Delimiters
-
-
- Even after years of programming in C under DOS, I still forget to double up
- the backslashes in pathname strings at least haft of the time. For example,
- I'll have a statement at the top of my C program that looks something like:
- #define PATH "c:\foo\bar"
- The compiler sees a string containing c:, a formfeed, oo, a backspace, and ar.
- This is clearly not the intended result. What I really meant to write was:
- #define PATH "c:\\foo\\bar"
- This kind of goof draws no compilation errors; often, it isn't until after
- I've become thoroughly confused and fired up the debugger that I find the
- mistake and kick myself. To keep from having to use the double-backslash
- notation in CMENU source files, I decided not to give the backslash character
- any special meaning in the CMENU specification language. As described above in
- the section on strings, there is no notation for escape sequences and single
- backslashes in strings are taken literally.
- Originally, I had included a feature in cmenu whereby any forward-slash (/)
- characters encountered in path clauses are automatically mapped into the
- paths-delimiter character appropriate for the target operating system (i.e.,
- either a back- or forward-slash.) I did this only for the path clause,
- however, because that is when most CMENU pathnames appear. Later, Jeff Dean
- pointed out to me that pathnames written with forward-slash (/) characters
- under DOS worked just as well as ones with backslash (\) characters! My copy
- of the Waite Group's MS-DOS Bible didn't mention any such equivalence,
- however, so I began to wonder.
- As it turns out, the slash-translation is performed by the C file I/O library
- functions supplied with the various C compiler packages, and not by DOS
- itself. Performing such translation explicitly within a C program for DOS is
- therefore redundant, and I removed my original translation code from CMENU.
- Then, I found that pathnames written with forward-slashes worked correctly in
- path statements, but not in action clauses. Thinking more about it, I ought
- not have been surprised: CMENU action statements are run by simply passing the
- supplied action text to the operating system's own command interpreter, not to
- a C file I/O function. Thus the slash-translation provided in the C file I/O
- library never gets performed, and DOS ends up choking on the forward-slashes.
- The net result of all this is the following rule of thumb: to be absolutely
- safe, always use backslashes. If you prefer to use forward-slashes, then use
- them only in a path clause, never in an action clause.
-
-
- A Sample Menu System
-
-
- Figure 4 and Figure 5 represent a small-scale menu system illustrating most of
- CMENU's features. I've arbitrarily chosen a UNIX-based example, but a DOS
- version would not be too different.
- In t.mnu (Figure 4), the main menu has no identifier label (OK for the initial
- menu in a file), allows shell escapes, and is to be displayed with double
- spacing on the screen. There are nine items in this main menu.
- The first item illustrates the automatic prompting feature of CMENU that is in
- effect when the DEF_PROMPT symbol (to be covered later in the rmenu section)
- is set to ON_ERROR. This causes rmenu, in the absence of an explicit prompt or
- noprompt option, to prompt after an action has been completed if and only if
- the status returned by that action is non-zero. In this example, a prompt is
- issued only if the user typed an e in response to the prompt displayed by the
- test.sh shell script (see Figure 6). Upon return from test.sh, the highlight
- bar moves over to the item labeled "zot" below.
- Since DOS systems do not provide standardized support for direct return values
- from system calls, the ON_ERROR option for the DEF_PROMPT feature is only
- meaningful under UNIX. Under DOS, DEF_PROMPT may be set to either YES or NO,
- and individual menu items may always override that default by including the
- prompt or noprompt options.
- The second item in the main menu calls up an external menu, t2.mnc, located in
- subdirectory test. Since the item path is given relatively (that is, no
- leading / character or, if it were under DOS, no drive letter either), the
- path is treated as a subdirectory of the current default menu path. Since no
- path clause was specified in the menu options section, the default menu path
- is the current working directory at the moment of rmenu invocation.
- The third and fourth items illustrate different ways to invoke an action.
- Using the exec command (UNIX only) saves a process.
- Item zot (lines 34-38) contains help text. This text is displayed on the
- screen only when the highlight bar is on that item. Note that in order to
- display the double quotes around the word "Zot," the entire help text is
- delimited by single quotes.
- Finally, the prompt command in menu item zot causes a prompt to be issued
- after the action is executed, forcing the user to press a key before the
- information left on the screen is erased and the menu screen is redisplayed.
- The next item simply illustrates how nextitem specifications may be backward
- as well as forward.
- The next item invokes the local menu named bar, appearing at the end of the
- file. The last few items are just filler, to illustrate what happens when the
- total number of items exceeds the capacity of the default screen arrangement.
- Since there are 18 lines available for item information in the standard
- 24-line screen setup, double spacing the items means that only nine would fit
- in the single-column format. If an additional menu item were created by
- cloning the filler items, then rmenu would automatically go into two-column
- mode in order to preserve the double spacing specified in line 13. If the
- spacing were set to 1 or omitted entirely, rmenu would go into single-spacing
- mode and remain in single-column mode. There is a function in rmenu devoted
- entirely to determining the most appropriate screen arrangement for any given
- menu. That function uses a heuristic based upon the number of items in the
- menu and any explicit spacing and column directives given. I'll cover this, in
- gory detail, when discussing the rmenu program later on in the series.
- Following the endmenu keyword, a second local menu named bar is defined. The
- local menu options serve to disable shell escapes and to set the default path
- for actions in this menu to /usr/bin. Since setting a path this way just
- generates a cd statement before running an action, a named action need not
- necessarily reside in the directory specified in a path clause, as long as the
- program can be found somewhere along the default system path. In many system
- paths, the current directory is searched first; if this is the case on your
- system, any executable commands in the directory named by a path clause take
- precedence over similarly named commands residing elsewhere along the system
- path.
- There is nothing startlingly new about the three items in the bar menu. I used
- semicolons instead of newline to separate some of the clauses, just to show
- that it's permissible.
- The second menu file, t2.mnu (Figure 5) also lacks interesting features; it
- was included to complete the illustration of a two-file menu system. Note that
- the default path for all actions is the same as the default path of the
- calling menu (in this case, the path specified by the second item in the first
- menu of the t.mnu file), since menu foo contains no explicit path clause to
- override that default path.
- One final note: an action clause may contain explicit commands to change the
- path, effectively superseding all previous default paths. An extraneous cd
- command may be executed if a path clause is given and an explicit cd command
- is present within the action text. Such extra cd commands generate only a
- negligible quantity of CPU overhead and may be effectively ignored.
-
-
- Common Menu
-
-
-
-
- Data Structures
-
-
- There are three header files in the CMENU package. The master header file,
- cmenu.h, contains the symbolic constant definitions (#define statements) that
- control common data structures and operating-system-specific code for both the
- cmenu and rmenu programs. cmenu.h is included by all program modules, so it
- performs the inclusion of the standard C header file, stdio.h.
- Besides including cmenu.h, the cmenu and rmenu programs each have their own
- personal header files, named ccmenu.h and rcmenu.h respectively. Both of these
- header files use the symbolic constants and data types defined in cmenu.h to
- build the actual data structures needed to perform their specific tasks.
- We'll look first at the common, elementary structures defined in cmenu.h, so
- we can see how cmenu and rmenu later build upon them.
-
-
- The Basics
-
-
- As noted earlier, cmenu and rmenu share the intermediate .mnc file format;
- consequently, most of cmenu.h (Listing 1) is devoted to declarations of the
- structure types used in that format. The two major structures are named MENU
- and ITEM. Within these two structure types, string elements are all defined as
- char arrays (as opposed to pointers), Boolean elements have type BOOL (really
- just char), logical and multiple-choice elements have type char, and numeric
- elements have type int (even though they would never be negative and probably
- never exceed a value of 255).
- The logical elements have three possible values: YES, NO, and DEFAULT (as
- opposed to the Booleans, which can only be TRUE or FALSE). YES and NO values
- appear when an explicit option was written for that element in a menu or item
- definition; the value DEFAULT signifies that no explicit option was written,
- and rmenu should, in that case, perform the actions dictated by a set of
- default action definitions specified in the rcmenu.h header file.
-
- If a text element is not specified, it is represented as the null string
- (first character is a '\0'). If a numeric element is omitted, the value
- inserted is DEFAULT, as described above.
- A MENU (a typedefed alias for struct menu) contains the following elements:
- title -- The menu title, displayed at the top of a menu screen
- path -- The default action path for all items (absolute or relative)
- nitems -- The number of items present in the menu
- align -- (not used)
- columns -- Number of columns specified for the item display
- spacing -- Spacing specified for the column display
- widest -- Length of the widest item text (in characters)
- escape -- Whether shell escapes are permitted
- An ITEM (really struct item) contains:
- text -- The text to be put up to represent that item (may be truncated if the
- space is needed for multiple columns)
- path -- the path for the item (absolute or relative)
- action -- The text of the command(s) to be submitted to a system() call when
- the item is chosen to be run, orthe name of an external menu if the action is
- emenu
- help -- The text of any help info, put up on the screen when the item is under
- the highlight bar (never truncated)
- pre_clear (logical) -- Whether to clear the screen before the action
- post_clear (logical) -- Whether to clear the screen after the action
- prompt (logical) -- Whether to pause before returning to menu
- acttyp (multi-choice) -- Tells what brand of action to perform (choices:
- command, lmenu, emenu or exit -- represented by the ACT_* symbolic constants)
- lmenunum -- If the acttyp is ACT_LMENU, specifies the index for the specified
- local menu in the menu table
- nextcode (multi-choice) -- Tells how the next item is to be determined
- (choices: first, last, next, or direct -- NXT_* symbols)
- nextitem -- If nextcode is NXT_DIRECT, this contains the index of the next
- item to be highlighted.
- The way that MENU and ITEM objects are combined is not specified in cmenu.h,
- because that combination is different in cmenu than it is in rmenu. For the
- remainder of this installment I'll focus on the cmenu program and its own data
- data structures.
-
-
- The .mnc Format
-
-
- The format of compiled .mnc files is summarized in Figure 7. Remember, this is
- a binary format, not an ASCII one.
- The first item in an .mnc file is an integer value that tells how many (local)
- menus are defined in the file. Immediately following this count is the MENU
- structure for the first menu, and after that come the ITEM structures for each
- item in the menu. The number of items in each menu is part of the information
- stored in the associated MENU header, so there's no need to store separate
- item counts in the file format.
- That's it; the .mnc format is fairly simple, conceptually. To actually
- generate it, however, requires a bit more complexity... now the real fun
- begins!
-
-
- At Last, cmenu
-
-
- The cmenu program is essentially a big state machine, whose purpose is to scan
- through the sequential ASCII token stream of one or more CMENU specification
- files and produce a correctly compiled .mnc output file corresponding to each
- input file.
- At the heart of cmenu's operation is a structure named keywords, defined in
- ccmenu.h (Listing 2, lines 190-228). keywords defines a couple of attributes
- to be associated with each possible keyword token. The tokens represent each
- symbol, keyword, or special condition (such as EOF) that the CMENU language
- recognizes.
- The first attribute, keyword, is the text of the keyword itself; for tokens
- that have no printing text associated with them, I've contrived some special
- identifying sequences (see lines 191 and 223-226) that assisted me during
- interactive debugging of the cmenu program.
- The other attribute is a pointer to the processing function called when the
- associated token shows up in the input stream. All token processing functions
- are named do_whatever, where whatever is the name of the token. There are also
- additional do_whatever functions not tied to a particular token, but called
- occasionally under special circumstances.
- An enumeration constant list (lines 43-58) defines a symbolic name for each
- keyword. These symbolic names appear in exactly the same order as their
- corresponding keywords table entries; thus, the symbolic name for each token
- has a value equal to that token's physical index position in the keywords
- array. Since the symbolic values are used to represent tokens during the
- parsing of input text, this arrangement has a side-effect that facilitates
- debugging of the cmenu program: you can keep a running display of the ASCII
- string associated with the most recently parsed token by placing the
- expression
- keywords[token].keyword
- into a watch window.
- For compilers that do not support enumeration constants, the "old-fashioned"
- way of defining a set of sequential symbolic values is shown in lines 60-99.
- This section of conditionally-compiled code is used instead of the enum
- section when __STDC__ (a pre-defined symbolic constant indicating ANSI
- compliance) is undefined.
- There are a couple of key global state variables that describe which portion
- of a menu definition is currently being processed. The first of these,
- in_menu, is a simple Boolean variable telling whether or not a menu definition
- is being processed. in_menu remains FALSE until the first menu clause is
- encountered; from that point on, in_menu is TRUE between each menu clause and
- endmenus keyword, FALSE otherwise.
- The other critical state variable is in_item. This one is set to TRUE every
- time the first item clause of a menu is encountered, and reset to FALSE (along
- with in_menu) each time endmenu is processed.
- Both of these state variables are initialized to FALSE near the start of
- do_menu(), and thereafter their values toggle as necessary under control of
- the appropriate token-processing functions.
- With the keywords structure and state variables all in place, cmenu's main
- processing loop can be frightfully simple. Indeed, lines 71-81 of Listing 3 (a
- partial listing of the source file cmenu1.c) make up this entire loop. The
- code relies, however, on the token scanning function gettok() to parse the
- next little piece of the input stream into a token value and associated detail
- values.
- If the next token is a string, for example, then gettok() returns the token
- T_STRING. The actual text of the string is stuffed into a global char array
- named tparam.
- The main loop can prevent the token-processing functions from having to
- perform a common error-checking test by making sure that, when not currently
- within a menu (i.e., whenever in_menu is FALSE), the menu keyword is the next
- token scanned. Whenever any token other than menu is encountered outside of a
- menu definition, an error is reported (line 75) and processing of the current
- file terminates.
- No other error condition is as universally easy to detect, so any further
- diagnostics are relegated to specific token-processing functions. Lines 78-80
- dispatch control to the appropriate processing function, and check for the
- possibility of a fatal error before continuing on to the next loop iteration.
-
-
- Information Economy
-
-
- Now let's return to the ccmenu.h header file and investigate some new data
- structures.
- The CMENU language supports forward references in both menu and item label
- references, so we need some way of keeping track of which menus and items have
- been defined so far, which ones have not, and where those references came from
- so they can be resolved when possible, or diagnosed as unresolved reference
- errors otherwise.
- There is also the matter of the menu and item identifier names. There is no
- place for those identifiers in the .mnc file format, since by the time the
- .mnc file is written, each reference to a menu or item has been resolved into
- an integer index value that allows direct access to the menu or item desired
- through a simple indexing operation.
-
- So, what's needed is a set of data structures that contain the elementary MENU
- and ITEM structures as subsets of larger structures. These larger structures
- add the additional pieces of data necessary to support the compilation
- process, but still allow the essential MENU and ITEM information to be
- extracted, in the required elementary format, when the time comes to write an
- output file.
- The typedefs for just such a set of incremental structures can be found in
- lines 27-37 of Listing 2. The first definition, IINFO, is a structure
- containing just two elements: a name string, and an INFO structure. The second
- typedef, MINFO, is a structure containing a name string, a Processed flag to
- indicate completion of menu processing, a MENU structure describing the
- properties of the menu, and an array of pointers to the IINFO structures that
- make up the individual items associated with the menu.
- The decision to use an array of pointers to structures in the Items array, but
- actual instances for the other structure elements (Item and Menu), actually
- came about as the result of much trial-and-error. The goal was to find an
- ideal balance for this application between memory-efficiency and coding
- clarity. In C, unfortunately, those two properties tend to unfold in inversely
- proportional quantities within any sufficiently complex application. Finding
- the right balance can be a challenging task.
- In order to make efficient use of available memory when the size of an array
- is not fixed at compile time, we need to use dynamic memory allocation to
- obtain exactly the needed quantity of memory, and no more, at runtime. When
- the storage for an object is allocated dynamically, then pointers must be used
- to access the data -- and manipulating pointers to objects is usually more
- complex than manipulating just the objects themselves.
- If more than one level of dynamic arrays are involved, then things get
- positively twisted. In my first design for cmenu, I attempted to use a data
- structure that had multiple levels of dynamically-allocated arrays. (Anyone
- remember the tricky dynamically-allocated arrays from my Mini-Database System
- series in CUJ last year? And that was only one level!) Needless to say, it was
- a mess. I learned, however, that it was still possible to squeeze much good
- use out of CMENU's small-model memory space even without many of the dynamic
- allocation tricks. Most of the memory goes to hold ITEM information, anyway;
- why not restrict dynamic allocation to only the memory needed for ITEM
- structures? The resulting code is much easier to document and understand than
- my earlier multilayered dynamic scheme.
- The MINFO structure applies to a single menu only, so there is an array of
- MAX_MENUS MINFO structures defined in line 235 as MInfo. The dimension of
- MAX_MENUS limits the number of structures that may be defined within a single
- source module; if long menu source files give you memory problems, reducing
- the value of MAX_MENUS offers quick memory relief (but might make it necessary
- to split some large .mnu files into smaller pieces).
- The final major data structure in the cmenu program is the forward-reference
- table, fwd_refs, defined in lines 241-245. This table performs the task of
- tracking forward references to named items in a menu. No such corresponding
- table is needed to handle forward menu references, however. Here is why: The
- order of items within a menu is significant; they should appear on the screen
- in the same order as their specification in the source file. Yet, how is the
- compiler supposed to behave when a reference is made to an as-yet-undefined
- item, one that could appear anywhere from the current point in the menu source
- to the end of the menu? Does the compiler immediately create an item record
- reserved for the future item, and add it to the array of items? If so, then
- the physical order of the items gets messed up (this isn't an obvious
- side-effect; my first stab at this code utilized an IINFO "Processed" flag
- similar to the MINFO flag of the same name, without taking the aforementioned
- phenomenon into consideration. Surprise, I couldn't get forward references to
- work!)
- Rather than creating an IINFO structure when a forward reference in
- encountered, the present scheme simply registers the reference into the
- fwd_refs table (including the line number of the reference, for diagnostic
- purposes), and goes on processing more menu items. Whenever a new labeled item
- definition is encountered, cmenu makes a quick check to see if any references
- have been made to that item label; if found, the references are resolved by
- copying the index number of the new item definition into the location whose
- address was saved in the forward reference table. If any unresolved references
- remain at the end of the menu definition, the precise line number of the
- reference can even be supplied in the error diagnostic. The code that handles
- this is not very complicated, but has proven immensely effective. If I were
- really smart, I would have written it this way in the first place, but then
- I'd have missed another opportunity to play with Turbo Debugger!
- The remaining definitions in ccmenu.h are for miscellaneous scratch pointers,
- global variables, and constants. Next time, we'll journey through the rest of
- cmenu's procedural code.
- Figure 1
- #
- # Make file for CMENU Menu compiler system (DOS version)
- # Developed under Borland C++, but written portably
- #
-
- ################################################################
- # Primary configuration section
- ################################################################
-
- CC = bcc # command-line compiler name
-
- # uncomment only one line in each of following 3 pairs of lines:
-
- #DEBUG = -v # enable Turbo Debugger info
- DEBUG = # disable debugging
-
- #BCCOPTS =
- BCCOPTS = -w-pia -A # Borland-specific stuff
-
- #NEEDSTR = -DNEEDSTR # compile own strstr() definition
- NEEDSTR = # use library version of strstr()
-
- WILDLIB = wildargs.obj # links wildcard module
-
- CULIBS = tscurses.lib # curses library
-
- ##############################################################
- # End of primary configuration section
- ##############################################################
-
- COPTS = $(BCCOPTS) -DDOS=1 $(DEBUG) $(NEEDSTR)
- CFILES = cmenu1.obj cmenu2.obj cmenu3.obj
- RFILES = rmenu1.obj rmenu2.obj rmenu3.obj rmenu4.obj
-
- all: cmenu.exe rmenu.exe dmenu.exe
-
- .c.obj: # For non-Borland MAKE, you may need to
- $(CC) -c $(COPTS) {$< } # substitute "$*.c" for "{$< }"
-
- cmenu.exe: $(CFILES)
- $(CC) $(DEBUG) -ecmenu $(CFILES) $(WILDLIB)
-
- rmenu.exe: $(RFILES)
- $(CC) $(DEBUG) -ermenu $(RFILES) $(CULIBS)
-
- dmenu.exe: dmenu.c cmenu.h
- $(CC) $(COPTS) -edmenu dmenu.c
-
- $(CFILES): ccmenu.h cmenu.h makefile
-
-
- $(RFILES): rcmenu.h cmenu.h makefile
-
- Figure 2
- #
- # Make file for CMENU Menu compiler system
- # For use with Unix/Xenix
- #
-
- CC = cc
-
- # Configuration options:
-
- # Uncomment only one of the following two lines, XENIX for XENIX only,
- # UNIX for any non-XENIX system:
- #SYSTEM = UNIX
- SYSTEM = XENIX
- # uncomment one only (first works for XENIX, second for most others):
- CULIBS = -ltcap -ltermcap
- #CULIBS = -lcurses -ltermcap
-
- # uncomment only ONE of the following 2 lines. The first will
- # compile the included strstr() definition, the second will cause
- # the library version of strstr() to be used.
- NEEDSTR = -DNEEDSTR
- #NEEDSTR =
-
- #
- # From this point on, no changes should be necessary.
- #
-
- COPTS = -D$(SYSTEM)=1 $(NEEDSTR)
- CFILES = cmenu1.o cmenu2.o cmenu3.o
- RFILES = rmenu1.o rmenu2.o rmenu3.o rmenu4.o
-
- all: cmenu rmenu dmenu
-
- .c.o:
- $(CC) -c $(COPTS) $<
-
- cmenu: $(CFILES)
- $(CC) -o $@ $(CFILES)
-
- rmenu: $(RFILES)
- $(CC) -o $@ $(RFILES) $(CULIBS)
-
- dmenu: dmenu.c cmenu.h
- $(CC) $(COPTS) -o dmenu dmenu.c
-
- $(CFILES): ccmenu.h cmenu.h
-
- $(RFILES): rcmenu.h cmenu.h
-
- Figure 3
- CMENU Specification Language
- ----------------------------
-
- Explanation of my "loose BNF" form:
-
-
- := reads "is defined as".
-
- Terms in <>s are syntactic objects.
-
- Terms in CAPITALS are keywords.
-
- An item in [] is optional
-
- If a set of items is enclosed in {} with
- between each item, or a range is shown
- using the - character, exactly one o
- those items must be picked.
-
- * to the right of an object denotes 0
- or more occurrences.
-
- + to the right of an object denotes 1
- or more occurrences.
-
- Wherever a separator is necessary to
- delimit tokens, the characters ';' and
- ',' are both valid separators along
- with the usual whitespace characters
- (space, tab and newline.)
- ----------------------------------------
-
- <cmenu-source-file> :=
- <menu-defn> +
-
- <menu-defn> :=
- MENU [<identifer>] :
- <menu-option> *
- <item-defn> +
- ENDMENU
-
- <identifier> :=
- {a-z A-Z}+ {a-z A-Z 0-9} *
-
- <menu-optiion> :=
- PATH <text>
- ALIGN { LEFT CENTER }
- ESCAPE
- NOESCAPE
- SPACING { 1 2 }
- COLUMNS <integer>
-
- <integer> :=
- {0-9} +
-
- <text> :=
- {
- "<ascii-string>"
- '<ascii-string>'
- <no-space-string>
- }
-
- <item_defn> :=
- ITEM [<identifier>] : [<text>]
- [<item-option>] *
-
- <action-code>
-
- <item-option> :=
- NEXTITEM
- {
- <identifier>
- FIRST
- LAST
- NEXT
- }
- TEXT <text>
- HELP <text>
- PATH <text>
- PROMPT
- PAUSE
- NOPROMPT
- NOPAUSE
- NOPRECLEAR
- PRECLEAR
-
- <action-code> :=
- {
- ACTION <text>
- EXIT
- LMENU <identifer>
- EMENU <text>
- }
-
- <ascii-string> := string containing any
- ASCII characters, delimited by either
- single (') or double (") quotes.
- Line continuation is NOT permitted.
-
- <no-space-string> := string without
- quotes, containing no whitespace
- characters whatsoever. Terminated
- by first whitespace character.
-
- Note: the <text> displayed for any one
- item may be specified either as an
- <item-option> (the TEXT clause) or
- immediately following the colon after
- the ITEM declaration. Exactly ONE
- of these methods must be used for
- each item in the menu file.
-
- Figure 4
- 1: #
- 2: # t.mnu (compiles into t.mnc):
- 3: # Top level menu for CMENU test system. To test this menu,
- 4: # start in a test directory /u/myname/menu, and
- 5: # put the command script "test.sh" into the this directory.
- 6: # Then create a subdirectory named "/u/myname/menu/test"
- 7: # and put the object file for menu t2 (t2.mnc) in it.
- 8: #
- 9:
- 10: menu:
- 11: title "The MAIN Menu" # menu title appears at top
- 12: escape # allow shell escapes
-
- 13: spacing 2 # double space the entries
- 14:
- 15: item:
- 16: "Run test.sh"
- 17: help 'next item is "zot"'
- 18: action "test.sh" # after the "speed" program runs,
- 19: nextitem zot # item "zot" (below) is highlighted
- 20:
- 21: item:
- 22: "Run External Menu t2"
- 23: path "test" # run menu "t2", found in
- 24: emenu "t2" # subdirectory "test".
- 25:
- 26: item:
- 27: "run a Unix shell"
- 28: action "exec sh" # most efficient with "exec"
- 29:
- 30: item:
- 31: "run shell, no exec." # creates an extra process.
- 32: action sh # (note: quotes not really needed)
- 33:
- 34: item zot:
- 35: 'ITEM "Zot": list the directory (in reverse chron. order)'
- 36: help 'this is a help line for #2, "ZOT"'
- 37: action "ls -t"
- 38: prompt # prompt user before continuing
- 39:
- 40: item
- 41: text "Do a long directly listing (no prompt)"
- 42: help "the next item should ALSO be zot"
- 43: action "l"
- 44: nextitem zot
- 45:
- 46: item
- 47: text "GO TO MENU BAR" # invoke a locally defined menu
- 48: lmenu bar
- 49:
- 50: item
- 51: text "filler" # clone this entry several times
- 52: action "ls" # to see automatic multiple columns
- 53:
- 54: item
- 55: text "filler" # clone this entry several times
- 56: action "ls" # to see automatic multiple columns
- 57: endmenu
- 58:
- 59:
- 60: menu bar:
- 61: title "This is local menu BAR. shell escpaes won't work."
- 62: noescape
- 63: path "/usr/bin"
- 64:
- 65: item
- 66: text "here is the first item of menu BAR"
- 67: action "ls"; prompt # semi OK as separator
- 68:
- 69: item
- 70: text "here is the next item of menu BAR"
- 71: action "ls" prompt # so is a space
-
- 72:
- 73: item
- 74: text "here is the LAST item of the menu BAR (with help)"
- 75: help "this is help for the LAST item of menu BAR"
- 76: action "ls"
- 77: endmenu
-
- Figure 5
- 1: #
- 2: # Second test menu: t2.mnu (compiles into t2.mnc)
- 3: #
- 4:
- 5: menu foo:
- 6: title "The MAIN Test Menu for my SECOND menu file"
- 7:
- 8: item fraz:
- 9: text "This is item FRAZ: run the pwd program (next is zot)"
- 10: help "This is a HELP line for item 1 - next item is zot"
- 11: action "pwd"
- 12: prompt
- 13: nextitem zot
- 14:
- 15: item:
- 16: text "This is item #2: run the editor on file foo (next is FRAZ)"
- 17: action "e foo"
- 18: nextitem fraz
- 19:
- 20: item zot:
- 21: text "item ZOT: list the directory (prompt)"
- 22: help "this is a help line for #2"
- 23: action "dir"
- 24: prompt
- 25:
- 26: item text "GO TO MENU #2"
- 27: lmenu bar
- 28:
- 29: endmenu
- 30:
- 31:
- 32: menu bar:
- 33: title "This is the SECOND local menu in the SECOND test file"
- 34:
- 35: item text "here is the first item of the 2nd menu"
- 36: action "dir"
- 37:
- 38: item text "here is the SECOND item of the 2nd menu (with help)"
- 39: help "this is help for that second item of menu #2"
- 40: action "dir"
- 41: endmenu
-
- Figure 6
- #!/bin/sh
- # Unix script to test return status feature of CMENU
- #
-
- cat <<END
- This is test.sh running, to test the error status
- feature of the CMENU system.
-
-
- If you type 'e' to return an error status, rmenu should
- prompt before returning to the menu. Otherwise, no prompt
- should appear issued:
-
- Press just return for normal exit,
- or else press 'e' and then the Return key:
- END
-
- read char
- [ "$char" = e ] && exit 1
- exit 0
-
- Figure 7
- .mnc Menu Object File Format:
-
- <count> (integer count of # of menus in file)
- MENU 1 (MENU structure for 1st Menu)
- ITEM 1
- ITEM 2
- ...
- ITEM n
- MENU 2 (MENU structure for 2nd Menu)
- ITEM 1
- ITEM 2
- ...
- ITEM n
- .
- .
- .
- MENU <count> (MENU structure for final Menu)
- ITEM 1
- ITEM 2
- ..
- ITEM n
-
- Listing 1
- 1: /***********************************************************
- 2: * Program: CMENU/RMENU Menu Compiler
- 3: * Written by: Leor Zolman, 11/90
- 4: *
- 5: * Module: cmenu.h -- master header file
- 6: *
- 7: * This include file contains definitions common to both
- 8: * the cmenu and rmenu programs.
- 9: ***********************************************************/
- 10:
- 11: #include <stdio.h>
- 12:
- 13: #define VERSION "1.2 (10/7/91)"
- 14:
- 15: /************** System-dependent stuff: *******************/
- 16:
- 17: #if __STDC__ XENIX
- 18: # define Void void
- 19: #else
- 20: # define Void int
- 21: #endif
- 22:
- 23: /******************* Maximum sizes/lengths: ***************/
-
- 24:
- 25: #define MAX_PATH 30
- 26: #define MAX_MENUS 25
- 27: #define MAX_NEST 3
- 28: #define MAX_TXTWID 60
- 29: #define MAX_ITEMS 36
- 30: #define MAX_CMD 130
- 31: #define MAX_HELP 79
- 32: #define MAX_NAME 20
- 33:
- 34: /****************** Magic constants: *********************/
- 35:
- 36: #define DEFAULT 0
- 37: #define YES 1
- 38: #define NO 2
- 39:
- 40: #define ERROR (-1)
- 41:
- 42: /******************* Nextitem types: **********************/
- 43:
- 44: #define NXT_FIRST 1
- 45: #define NXT_LAST 2
- 46: #define NXT_NEXT 3
- 47: #define NXT_DIRECT 4
- 48:
- 49: /******************* Action types: ************************/
- 50:
- 51: #define ACT_NONE 0
- 52: #define ACT_CMND 1
- 53: #define ACT_LMENU 2
- 54: #define ACT_EMENU 3
- 55: #define ACT_EXIT 4
- 56:
- 57: /******************* typedefs: ****************************/
- 58:
- 59: typedef char BOOL;
- 60:
- 61: typedef struct menu {
- 62: char title[MAX_TXTWID];
- 63: char path[MAX_PATH];
- 64: int nitems;
- 65: char lign;
- 66: int columns;
- 67: int spacing;
- 68: int widest;
- 69: char escape;
- 70: } MENU;
- 71:
- 72: typedef struct item {
- 73: char text[MAX_TXTWID];
- 74: char path[MAX_PATH];
- 75: char action[MAX_CMD];
- 76: char help[MAX_HELP];
- 77: char pre_clear,
- 78: post_clear,
- 79: prompt;
- 80: char acttyp;
- 81: int lmenunum;
- 82: char nextcode;
-
- 83: int nextitem;
- 84: } ITEM;
-
- /* End of File */
-
-
- Listing 2
- 1: /*************************************************************
- 2: * Program: CMENU Menu Compiler
- 3: * Module: ccmenu.h -- Compiler Module header file
- 4: * Written by: Leor Zolman, 11/90
- 5: *************************************************************/
- 6:
- 7: /******************* Misc. constants ************************/
- 8:
- 9: #define TRUE 1
- 10: #define FALSE 0
- 11: #define OK 0
- 12:
- 13: #define UNDEF_FWD (-1) /* undefined forward reference flag */
- 14:
- 15:
- 16: /******************* extern control ************************/
- 17:
- 18: #ifndef MASTER
- 19: # define Extern extern /* external declarations */
- 20: #else
- 21: # define Extern /* one-time definitions */
- 22: #endif
- 23:
- 24:
- 25: /******************* Type Definitions **********************/
- 26:
- 27: typedef struct {
- 28: char Name[MAX_NAME];
- 29: ITEM Item;
- 30: } IINFO;
- 31:
- 32: typedef struct {
- 33: char Name[MAX_NAME];
- 34: BOOL Processed;
- 35: MENU Menu;
- 36: IINFO *Items[MAX_ITEMS];
- 37: } MINFO;
- 38:
- 39: /******************* Token codes: **************************/
- 40:
- 41: #if __STDC______LINEEND____
- 42:
- 43: enum {
- 44: T_NULL, /* special code */
- 45: T_MENU, T_TITLE, T_PATH,
- 46: T_SPACING, T_COLUMNS, T_ENDMENU,
- 47: T_ITEM, T_TEXT,
- 48: T_ALIGN, T_LEFT, T_CENTER, T_RIGHT,
- 49: T_NEXTITEM, T_FIRST, T_LAST, T_NEXT,
- 50: T_EMENU, T_LMENU, T_ACTION,
- 51: T_HELP,
- 52: T_PROMPT, T_PAUSE, /* synonyms */
-
- 53: T_NOPROMPT, T_NOPAUSE, /* synonyms */
- 54: T_PRECLEAR, T_NOPRECLEAR, T_POSTCLEAR, T_NOPOSTCLEAR,
- 55: T_EXIT,
- 56: T_ESCAPE, T_NOESCAPE,
- 57: T_STRING, T_VALUE, T_COLON, T_EOF /* special tokens */
- 58: };
- 59:
- 60: #else /* __STDC__ */
- 61:
- 62: #define T_NULL 0 /* special code */
- 63: #define T_MENU 1
- 64: #define T_TITLE 2
- 65: #define T_PATH 3
- 66: #define T_SPACING 4
- 67: #define T_COLUMNS 5
- 68: #define T_ENDMENU 6
- 69: #define T_ITEM 7
- 70: #define T_TEXT 8
- 71: #define T_ALIGN 9
- 72: #define T_LEFT 10
- 73: #define T_CENTER 11
- 74: #define T_RIGHT 12
- 75: #define T_NEXTITEM 13
- 76: #define T_FIRST 14
- 77: #define T_LAST 15
- 78: #define T_NEXT 16
- 79: #define T_EMENU 17
- 80: #define T_LMENU 18
- 81: #define T_ACTION 19
- 82: #define T_HELP 20
- 83: #define T_PROMPT 21 /* synonyms */
- 84: #define T_PAUSE 22
- 85: #define T_NOPROMPT 23 /* synonyms */
- 86: #define T_NOPAUSE 24
- 87: #define T_PRECLEAR 25
- 88: #define T_NOPRECLEAR 26
- 89: #define T_POSTCLEAR 27
- 90: #define T_NOPOSTCLEAR 28
- 91: #define T_EXIT 29
- 92: #define T_ESCAPE 30
- 93: #define T_NOESCAPE 31
- 94: #define T_STRING 32 /* special tokens */
- 95: #define T_VALUE 33
- 96: #define T_COLON 34
- 97: #define T_EOF 35
- 98:
- 99: #endif /*__STDC__*/
- 100:
- 101: /********************** Prototypes: ***********************/
- 102:
- 103: /if __STDC__ XENIX /* ANSI Prototypes: */
- 104:
- 105: int write_file(void);
- 106: int dofile(char *);
- 107:
- 108: void itemcheck(void);
- 109: int gettok();
- 110:
- 111: int error(char*, ...);
-
- 112: int fatalerr(char *, ...);
- 113:
- 114: MINFO create_menu(char *);
- 115: IINFO *create_item(char *);
- 116: MINFO *find_menu(char *);
- 117: IINFO *find_item(char *);
- 118:
- 119: int do_menu(void);
- 120: int do_title(void);
- 121: int do_path(void);
- 122: int do_spacing(void);
- 123: int do_columns(void);
- 124: int do_item(void);
- 125: int do_endmenu(void);
- 126: int do_align(void);
- 127: int do_text(void);
- 128: int do_text2(void);
- 129: int do_nextitem(void);
- 130: int do_action(void);
- 131: int do_help(void);
- 132: int do_prompt(void);
- 133: int do_clear(void);
- 134: int do_err(void);
- 135: int do_escape();
- 136: int do_opts(void);
- 137:
- 138: #else /* K&R Prototypes: */
- 139:
- 140: int write_file();
- 141: int dofile();
- 142:
- 143: Void itemcheck();
- 144: int gettok();
- 145:
- 146: int error();
- 147: int fatalerr();
- 148:
- 149: MINFO create_menu();
- 150: IINFO *create_item();
- 151: MINFO *find_menu();
- 152: IINFO *find_item();
- 153:
- 154: int do_menu();
- 155: int do_title();
- 156: int do_path();
- 157: int do_spacing();
- 158: int do_columns();
- 159: int do_item();
- 160: int do_endmenu();
- 161: int do_align();
- 162: int do_text();
- 163: int do_text2();
- 164: int do_nextitem();
- 165: int do_action();
- 166: int do_help();
- 167: int do_prompt();
- 168: int do_clear();
- 169: int do_err();
- 170: int do_escape();
-
- 171: int do_opts();
- 172:
- 173: #endif
- 174:
- 175: #ifdef NEEDSTR
- 176: char *strstr();
- 177: #endif
- 178:
- 179:
- 180: /************ Keyword / function dispatch table ***********/
- 181:
- 182: struct keywd {
- 183: char *keyword;
- 184: int (*t_func)();
- 185: };
- 186:
- 187: extern struct keywd keywords[];
- 188:
- 189: #ifdef MASTER
- 190: struct keywd keywords[] = {
- 191: "(null)", do_err, /* for db only */
- 192: "menu", do_menu,
- 193: "title", do_title,
- 194: "path", do_path,
- 195: "spacing", do_spacing,
- 196: "columns", do_columns,
- 197: "endmenu", do_endmenu,
- 198: "item", do_item,
- 199: "text", do-text,
- 200: "align", do_align,
- 201: "left", do_err,
- 202: "center", do_err,
- 203: "right", do_err,
- 204: "nextitem", do_nextitem,
- 205: "first", do_err,
- 206: "last", do_err,
- 207: "next", do_err,
- 208: "emenu", do_action,
- 209: "lmenu", do_action,
- 210: "action", do_action,
- 211: "help", do_help,
- 212: "prompt", do_opts,
- 213: "pause", do_opts,
- 214: "noprompt", do_opts,
- 215: "nopause", do_opts,
- 216: "preclear", do_opts,
- 217: "nopreclear", do_opts,
- 218: "postclear", do_opts,
- 219: "nopostclear", do_opts,
- 220: "exit", do_action,
- 221: "escape", do_escape,
- 222: "noescape", do_escape,
- 223: "(!string)", do_err, /* for db only */
- 224: "(!value)", do_err, /* for db only */
- 225: "(!colon)", do_err, /* for db only */
- 226: "(!EOF)", do_err /* for db only */
- 227: };
- 228: #endif
- 229:
-
- 230: #define N_KEYWORDS (sizeof keywords / sizeof (struct keywd))
- 231:
- 232:
- 233: /*************** Other Data structures ********************/
- 234:
- 235: Extern MINFO MInfo[MAX_HENUS], *MIp;
- 236: Extern IINFO *IIp;
- 237:
- 238: Extern MENU *Mp, *CMp; /* General, Current Menu Pointers */
- 239: Extern ITEM *Ip, *CIp; /* General, Current Item Pointers */
- 240:
- 241: Extern struct { /* Item Forward Reference Table */
- 242: char iname[MAX_NAME]; /* Item name */
- 243: int *refp; /* Pointer to reference location */
- 244: int lineno; /* source line number of reference */
- 245: } fwd_refs[MAX_ITEMS];
- 246:
- 247: Extern int n_refs; /* Number of forward references */
- 248:
- 249: /************** Miscellaneous data items ******************/
- 250:
- 251: Extern FILE *fp;
- 252: Extern int token, token2; /* token codes */
- 253: Extern char tparam[MAX_CMD]; /* text parameter */
- 254: Extern int vparam; /* value parameter */
- 255: Extern int lineno; /* current line number */
- 256: Extern int n_menus,
- 257: n_items;
- 258: Extern BOOL in_menu,
- 259: in_item;
- 260: Extern BOOL err_flag,
- 261: fatal;
- 262: Extern int item_num,
- 263: menu_num;
- 264:
- 265: Extern char src_name[MAX_PATH];
- 266: Extern char obj_name[MAX_PATH];
-
- /* End of File */
-
-
- Listing 3
- 1: /*************************************************************
- 2: * Program: CMENU Menu Compiler
- 3: * Module: cmenu1.c
- 4: * Menu Compiler:
- 5: * Main and Utility Functions
- 6: * Written by: Leor Zolman, 7/91
- 7: *************************************************************/
- 8:
- 9: #define MASTER
- 10: #include "cmenu.h"
- 11: #include "ccmenu.h"
- 12:
- 13: #include <string.h>
- 14:
- 15: #if __STDC______LINEEND____
- 16: # include <stdarg.h>
- 17: #else
-
- 18: # include <varargs.h>
- 19: #endif
- 20:
- 21: int main(argc,argv)
- 22: int argc;
- 23: char **argv;
- 24: {
- 25: register i;
- 26:
- 27: printf("CMENU Menu Compiler v%s\n", VERSION);
- 28: if (argc< 2)
- 29: {
- 30: puts("usage: cmenu <menu-source-file(s)>\n");
- 31: return 0;
- 32: }
- 33:
- 34: for (i = 1; i < argc; i++)
- 35: if (dofile(argv[i]) == ERROR) /* process source files */
- 36: return 1;
- 37: return 0;
- 38: }
- 39:
- 40: /************************************************************
- 41: * dofile():
- 42: * Process a single .mnu source file
- 43: *************************************************************/
- 44:
- 45: int dofile(name)
- 46: char *name;
- 47: {
- 48: register i;
- 49: char *cp;
- 50:
- 51: if ((cp = strstr(name, ".mnu"))
- 52: (cp = strstr(name, ".MNU")))
- 53: *cp = '\0';
- 54:
- 55: strcpy(src_name, name);
- 56: strcat(src_name, ".mnu");
- 57: strcpy(obj_name, name);
- 58:
- 59: if ((fp = fopen(src_name, "r")) == NULL)
- 60: return fprintf(stderr, "Can't open %s\n", src_name);
- 61:
- 62: n_menus = 0;
- 63: lineno = 1;
- 64: in_menu= FALSE;
- 65: fatal = FALSE;
- 66:
- 67: /* Main processing loop. Read a token and process it,
- 68: * until end of file is reached:
- 69: */
- 70:
- 71: while ((token = gettok(fp)) != T_EOF)
- 72: {
- 73: if (!in_menu && token != T_MENU)
- 74: {
- 75: error("Each menu must begin with the Menu keyword");
- 76: break;
-
- 77: }
- 78: if ((*keywords[token].t_func)() == ERROR)
- 79: if (fatal) /* If fatal error, exit loop */
- 80: break;
- 81: }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- CUG New Releases
-
-
- Database Constructing Tools
-
-
-
-
- Kenji Hino
-
-
- Kenji Hino is a member of The C Users' Group technical staff. He holds a
- B.S.C.S. from McPherson College and an undergraduate degree in metallurgy from
- a Japanese university. He enjoys playing drums in a reggae band.
-
-
-
-
- Updates
-
-
-
-
- CUG343 C Image Processing System
-
-
- Dwayne Phillips (VA) has updated his programs. The update contains a complete
- set of source code including the code that appeared in his "Image Processing"
- articles in CUJ.
-
-
- CUG347 TAVL Tree
-
-
- Bert C. Hughes (MN) has released ver.2 of his programs and placed them in
- public domain. In this release, he has improved the documentation and
- rewritten code to accommodate the fact that some Standard C compilers do not
- support signed bit fields (Roberto Artigas, Jr has contributed on this
- matter).
-
-
- New Releases
-
-
-
-
- CUG358 cbase
-
-
- Lyle Frost (IN) has contributed a shareware version of cbase programs. cbase
- is a complete multiuser C database file management library, providing indexed
- and sequential access on multiple keys. It features a layered architecture and
- comprises four individual libraries:
- chase -- C database library for indexed and sequential access
- lseq -- doubly linked sequential file management library
- btree -- B+-tree file management library
- blkio -- block buffered input/output library
- cbase internally uses lseq for record storage and btree for inverted file
- index storage, which in turn use blkio for file access and buffering. blkio is
- analogous to stdio but based on a file model more appropriate for structured
- files such as used in database software. The lower level libraries can also be
- accessed directly for use independent of cbase. For example, the btree library
- can be used to manipulate B+-trees for purposes other than inverted files, and
- the blkio library to develop new structured file management libraries.
- cbase is written in strict adherence to ANSI C standard while it maintains K&R
- C compatibility. All operating system dependent code is isolated to a small
- portion of the blkio library to make porting to new systems easy. Currently,
- UNIX and DOS systems are supported. For UNIX systems, the programs were tested
- under Interactive UNIX; for DOS systems, Turbo C (v2.0), Turbo C++, and
- Microsoft C v5.1 were used for compiling.
- The distribution disk includes documentation, complete source code for cbase
- (v.1.0.2), and a sample rolodeck card program. Due to the volume of the
- programs, files are archived in ZIP form. Thus, we restrict the distribution
- disk format to MS-DOS.
- Since CUG295 blkio library is a component of this package, we will retire the
- volume.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- Well, it's a new year. I am somewhere in my second year of editing The C Users
- Journal. R&D Publications has moved to new quarters, which I have yet to
- visit. And the C programming language is drifting into its third decade of
- existence.
- I must say that I have mostly enjoyed editing this magazine. Working via
- e-mail from Australia was often a challenge. Some issues I feel I could have
- polished better with a bit more effort. But an occasional kind word from a
- reader goes a long way in this business. The hardest part for me is reading
- the other kind of letters. I find that a little criticism also goes a long
- way. The trick is not to overreact, in either direction.
- R&D seems to be prospering as well, at least from my vantage point as a
- semi-insider. Besides The C Users Journal, they also put out several other
- quality technical publications. (These are not as important as CUJ, of course,
- but you might want to check them out anyway.) And they run The C Users' Group
- and The C Users Bookstore, two valuable services. I expect all of these
- endeavors to become more important as the C community grows.
- In the early 1970s, that community was confined to two floors of one building
- at Bell Labs, Murray Hill, New Jersey. To say that it has grown is the
- grossest of understatements. C is ubiquitous. It is probably the programming
- language in widest use today. We all know that C is not perfect. That's one
- reason why people keep tinkering with it and extending it. But the list of
- successful products written in C keeps growing.
- Not all my reflections on past and future are equally rosy. Certainly, the
- world is wallowing through times of economic uncertainty. Not even the rapidly
- growing computer business is immune to setbacks. And economic hard times have
- a way of depressing everything else.
- Still, it's a new year. I can't help but look on the coming months with
- optimism, at least for CUJ, R&D, and C. I hope you can too.
- P.J. Plauger
- pjp@plauger.uunet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- Borland Ships Resource Workshop for Windows
-
-
- Borland International, Inc. has released The Resource Workshop, a design tool
- for visually creating or customizing Windows resources such as icons, dialogs,
- fonts and bitmap graphics without writing code. A collection of 64 graphical
- icons is included free with each product.
- Resource Workshop is available for IBM personal computers and 100 percent
- compatibles running Windows 3.0 or later. It runs in standard or 386 enhanced
- mode on an 80286 or higher processor and requires a hard disk, 2Mb of RAM,
- plus EGA, Hercules, or VGA graphics and a mouse or other pointing device.
- 2.5Mb of free disk space (3.5Mb if all the files and sample programs are
- loaded) is required.
- Resource Workshop is available direct from Borland for the introductory price
- of $49.95 in the U.S. and Canada. Pricing is in U.S. dollars. Included with
- each Resource Workshop is a free set of more than 64 icons.
- For more information contact Borland International, Inc., 1800 Green Hills
- Road, P.O. Box 660001, Scotts Valley, CA 95067-0001, (800) 331-0877.
-
-
- 3-D Graphics For Windows
-
-
- WISE Software has released ARENA for Windows 3.0 based on the Z-PHIGS graphics
- standard. ARENA supports most 3-D CAD packages, paint programs, and desktop
- publishing packages. The user can import geometry, objects, and scientific
- data created with 3-D CAD programs or 3-D digitizers. The imported image can
- be rotated, scaled, zoomed, resized, and viewed from any angle.
- For more information contact Wise Software, Seelandstrasse 3, D-2400 Lubeck
- 14, Germany, 0451-3909-142, FAX 0451-3909-499.
-
-
- Microware Systems Corporation For Motorola Single Board Computers
-
-
- Microware Systems Corp. has released an optimized version of the popular OS-9
- Real-Time Operating System for the Motorola MVME167 single board computer.
- This version of OS-9 for Motorola microprocessor-based products allows
- designers to use the power of the 32-bit M68040 processor, while providing
- complete support for the on-board serial, SCSI, and Ethernet hardware.
- The OS-9/167 Development Pak includes a number of new OS-9 device drivers for
- the next generation I/O peripherals included on the MVME167 board family.
- These include SCSI drivers for the NCR 53C710 controller which support Common
- Command Set flexible and hard disk drives and tape units. Additional drivers
- are included to support the on-board real-time clock and new CD-2401 serial
- I/O controller.
- Full Ethernet support is provided by means of the OS-9 Internet Support
- Package (ISP) and device drivers for the Intel 82597 Ethernet Controller.
- Support for BSD socket-based interprocess communication is also provided.
- OS-9/MVME167 is available in two versions. The OS-9/167 Development Pak
- includes the OS-9/167 Real-Time Operating System module and device drivers as
- well as the full suite of development tools. Cost for the Development Pak is
- $3,000. The OS-9/167 Run- Time Pak provides only the OS-9 Real-Time Operating
- System modules and is intended to provide target system functionality.
- Quantity one pricing for the Run-Time pak is $1,500. Contact Microware for
- multiple-copy licensing information.
- For more information contact Microware, 1900 N.W. 114th St., Des Moines, IA
- 50325, (515) 224-1929, FAX (515) 224-1352.
-
-
- High Level Object Management For C++
-
-
- Software Ingenuities, Inc. has released Style, a C++ class library designed to
- manage all associations and links between C++ objects. The programmer
- specifies all classes, and the associations between those classes, with a few
- simple declarations. A small, intuitive set of functions is all that is needed
- to build bi-directional links between C++ objects. Style manages the objects
- in a RAM resident database.
- Style provides traversal functions that selectively navigate through the
- database of objects and perform operations on those objects. The operations
- performed by traversal functions can be Style's pre-written functions or can
- be defined by the programmer. Traversal functions separate the task of
- locating objects from the task of performing operations on objects. Because
- the operation is separate from the structure of the application, a programmer
- can easily port traversal functions between applications.
- The suggested retail price of Style starts, at $250; it is available directly
- from Software Ingenuities or through authorized dealers. Style supports
- Borland and Zortech C++. A UNIX version is also available.
- For more information contact Software Ingenuities, Inc., P.O. Box 1586,
- Ballwin, MO 63022, (314) 391-7772, FAX 314-391-0727.
-
-
- Mathematica Announces TEMPRA GIF
-
-
- Mathematica, Inc. has released TEMPRA GIF v1.03. TEMPRA GIF runs in Windows,
- DOS, or OS/2 Release 2 environments and supports TIFF, TGA, WIN, GIF, PCX, and
- IBM AVC image file formats. TEMPRA GIF supports a complete range of VGA cards,
- as well as more than 295 black-and-white printers. Also built into TEMPRA GIF
- is the ability to capture real-time video through a video digitizer card.
- TEMPRA GIF provides a variety of standard paint tools, photorealistic image
- processing effects, and transformation effects. Paint tools include a
- 256-color palette, patterns, and pens, which can be altered to meet individual
- creative tastes. Geometry functions offer all the shapes and lines necessary
- for detailed artwork, including freehand drawing, arcs, splines, and polygons.
- Other functions, such as antialias, tint, soften, color cycle animation,
- palette adjustment, zoom, and advanced masking, provide a vast array of
- painting combinations. Image canvases can be up to 192Mb in size, or 8K by 8K
- resolution.
- For more information contact Mathematica Inc., 402 S. Kentucky Ave., Lakeland,
- FL 33801, (813) 682-1128, FAX (813) 686-5969.
-
-
- COBOL CASE Solution For RISC System/6000
-
-
- Netron Inc. has released its CASE product, NETRON/CAP, for IBM's AIX-based
- RISC System/6000 product line.
- In addition to generating native AIX COBOL systems, the initial release of
- this product will generate standard COBOL applications for all environments
- currently supported by NETRON/CAP, including CICS, IMS, DB2, batch, VSAM, MVS,
- VM/CMS, OS/2, MS-DOS and Digital's VAX/VMS.
-
- NETRON/CAP is a back-end CASE product, based on a unique technology for
- re-using and recycling software components to design and assemble new COBOL
- applications.
- NETRON/CAP for AIX is priced at $10,000 per user. Training and technical
- support/project consulting are extra. For corporate users, a NETRON/CAP
- Starter Kit bundles a five-user development license for AIX with one week of
- training and four weeks of initial implementation support for $65,000. Annual
- product support is 15% of the license price. Volume discounts and special
- vendor pricing are available.
- NETRON/CAP requires AIX v3.1.5 or later and v1 of the AIX VS COBOL
- COMPILER/6000.
- For more information contact Netron, Inc., 99 St. Regis Crescent North,
- Toronto, Canada M3J 1Y9, (416) 636-8333, FAX (416) 636-4847.
-
-
- Faxfacts Version 4 Software Supports Brooktrout's Single And Multiple Line Fax
- Boards
-
-
- Copia International, developer of the FAXFacts interactive fax retrieval
- system, today announced FAXFacts software support for Brooktrout's 111 and 112
- fax boards.
- Brooktrout, a supplier of high performance voice/fax cards, entered the
- international market four years ago by signing O.E.M. agreements with
- manufacturers or distributors in several foreign countries. By the completion
- of 1991, Brooktrout expects to have approval in 25 countries for their fax
- card use. Brooktrout's unique dual capacity card offers speech playback and
- fax capabilities all on the same card.
- For information via the FAXFacts system call 1-708-924-7465 and request
- document number 8900. For more information contact Copia International, (708)
- 682-8898.
-
-
- Cross-C Compiler For Z280 Microprocessor
-
-
- Softools has released a new ANSI cross-C compiler for the Z280 microprocessor.
- Control Cross-C for the Z280 comes with a fully integrated MAKE facility,
- built-in assembler, C preprocessor, K&R/ANSI C cross-compiler for DOS
- compatible systems, and includes a stand-alone SASM assembler, linker,
- librarian, five hundred page manual, and a copy of the reference Standard C by
- P.J. Plauger and Jim Brodie.
- Control Cross-C for the Z280 allows the user to compile, link, and execute
- programs up to 16 megabytes in size. It supports banked function calls into
- segments which are not mapped into the logical address space. It also provides
- mapping capability that is handled by the linker automatically, with no source
- program changes. The Control Cross-C package sells for $699.
- For more information contact Softools, Inc., 8770 Manahan Drive, Ellicott
- City, MD 21043, (301) 750-3733, FAX/BBS: (301) 750-2008.
-
-
- NABJAooc Now Supports ANSI C And K&R C
-
-
- NABJA Software has released a version of its object-oriented development
- environment for C that supports both ANSI C and K&R C compilers.
- NABJAooc is available with compiled libraries for Microsoft C, Microsoft
- QuickC, Borland's Turbo C, Power C 2.0, Watcom C, and Datalight C. Optional
- source code provides full support for any ANSI C or K&R compiler.
- NABJAooc is currently priced at $29.95, with the full source code version only
- $49.95. NABJAooc offers an unconditional 30 day moneyback guarantee. A free
- brochure that describes NABJAooc is available upon request.
- For more information about NABJAooc, contact NABJA Software, P.O. Box 413,
- Girard, PA 16417-0413, (814) 774-3699.
-
-
- Clean Coding In Borland C++
-
-
- M&T Book's latest offering provides novice to intermediate C and C++
- programmers with a complete guide and tutorial to Borland C++, the new
- object-oriented programming environment for developing DOS and Windows
- applications.
- Clean Coding in Borland C++, by Robert J. Traister, presents clear,
- easy-to-understand explanations of Borland C++'s features and functions plus
- complete coverage of the powerful tools included with it. Expert tips and
- techniques teach programmers how to write clean, efficient Borland C++ code,
- develop Windows applications, debug their programs with Turbo Debugger, design
- Windows icons, dialogs, bitmaps and menu bars with The Whitewater Resource
- Toolkit, and more.
- Clean Coding in Borland C++ is filled with practical, hands-on examples. All
- of the source code is available on disk in PC/MS- DOS format.
- Clean Coding in Borland C++ retails for $26.95, $36.95 with disk. For more
- information contact M&T Books, 501 Galveston Drive, Redwood City, CA
- 94063-4728, (415) 366-3600, FAX (415)366-1685.
-
-
- Certification Flash
-
-
- 88open has announced the certification of ASCWINDOWS from Summitpointe
- Technologies now available to 88open members and end-users.
- ASCWINDOWS is a windows application development system for character based
- terminals connected to a UNIX-based system. The toolbox allows for creation of
- windowing standards like windows, dialog boxes, pull-down menus, list boxes,
- edit boxes, mode selectors, etc. Applications developed around ASCWINDOWS are
- event driven. ASCWINDOWS comes with a runtime library, a resource compiler,
- and a keyboard compiler. Application resources are defined using resource
- definition language. ASCWINDOWS features default window procedures, that can
- be defined by the user, accelerator keys for faster input processing, user
- configurable virtual keys for application customization, easy
- internationalization of applications, standard user interfaces across
- platforms, and simple naming conventions. ASCWINDOWS applications are terminal
- independent. They are portable to other systems, without modifying the source.
- Certification by 88open is an assurance that software products have been
- thoroughly tested and will run on all MC88000 based systems certified to
- 88open standards. 88open certified RISC systems include: Data General A ViiON,
- Dolphin Triton 88, Harris Night Hawk 4400, Motorola Delta 8000 series, Opus
- 400 & 8000 Personal Mainframes, and Sanyo/Icon 3000, 3380 and 8000.
- For more information on the above product contact 88open Consortium Ltd., 100
- Homeland Ct., Suite 800, San Jose, CA 95112, (408) 436-6600.
-
-
- Netron Teams Up With DEC
-
-
- Netron, Inc. announced an agreement with Digital Equipment Corporation that
- will make Netron's CASE product, NETRON/CAP, available for the design and
- generation of COBOL applications on VMS systems for the ULTRIX operating
- system. The agreement makes NETRON/CAP one of the first CASE products to
- generate COBOL for ULTRIX, Digital's popular implementation of NIX.
- By offering COBOL development for the ULTRIX operating system, NETRON/CAP
- furher extends Digital's COHESION framework for providing a single application
- development environment across multi-platform/vendor environments. NETRON/CAP
- for ULTRIX also conforms to Digital's NAS (Network Applications Support) open
- systems architecture.
- The agreement calls for a number of key Digital products to be integrated with
- the ULTRIX version of NETRON/CAP. Included will be the integration of the LSE
- (Language Sensitive Editor) and CDD/Repository, support for Rdb as the primary
- database, and use of the VMS Install Utility for software installation.
- Applications will be fully compatible, linkable, and executable on RISC ULTRIX
- systems.
- The minimum configuration necessary to use NETRON/CAP for ULTRIX is a
- DECstation 3100 with 16 MB of main memory, employing ULTRIX v4.2 and using the
- MicroFocus COBOL/2 1.1 compiler.
- NETRON/CAP for the ULTRIX operating system is priced at $10,000 per user.
- Training and technical support/project consulting are extra. For corporate
- users, a NETRON/CAP Starter Kit bundles a five-user development license for
- ULTRIX systems with one week of training and four weeks of initial
- implementation support for $65,000. Annual product support is 15% of the
- license price. Volume discounts and special vendor pricing are available.
- For more information contact Netron, Inc., 99 St. Regis Crescent North,
- Toronto, Canada M3J 1Y9, (416) 636-8333, FAX (416) 636-4847.
-
-
-
- Operating Environment Allows Vendors To Support Industry Standards
-
-
- Cadre Technologies Inc. today announced its support for SunSoft's UNIX-based
- operating environment, Solaris v2.0. Cadre will offer its Teamwork line of
- CASE products for UNIX on Solaris. Teamwork will continue to conform to
- industry standard graphical user interfaces by integrating with OPEN LOOK as
- part of Sunsoft's new Solaris environment.
- Solaris is based AT&T's System V Release 4 technology, and consists of three
- functional layers: the operating system layer made up of SunOS v5.0 and ONC,
- the applications layer made up of OpenWindows v3.0 and developer tools, and
- the user layer made up of DeskSet 3.0 and user interfaces.
- Teamwork currently runs on SunOS with OpenWindows and will support the three
- layers of the Solaris environment: SVR4, OpenWindows, and OPEN LOOK/DeskSet.
- The graphical user interface and developer tools that Sun will be offering
- with Solaris will enhance the look and feel of the Teamwork environment.
- For more information contact Cadre Technologies, 222 Richmond Street,
- Providence, RI 02903, (401) 351-CASE, FAX (401) 351-7380.
-
-
- WATCOM 32-Bit Development System For DOS And Windows
-
-
- WATCOM has released the C8.5/386 Optimizing Compiler and Tools, a 32-bit
- development system for DOS and Windows. Key features include a royalty-free
- 32-bit DOS extender and a true 32-bit Windows GUI and DLL development kit.
- The product supports a range of 80x86 based environments including Windows and
- 32-bit DOS extenders from Rational, Phar Lap and Ergo. C8.5/386 has numerous
- Microsoft language extensions to simplify porting of existing 16-bit code.
- The package includes DOS/4GW, a 32-bit DOS extender developed by Rational
- Systems, Inc.
- WATCOM also has released the 32-bit FORTRAN 77/386 Optimizing Compiler and
- Tools, containing all of the significant new features described here for
- C8.5/386. Also available is WATCOM's 16-bit optimizing compiler packages,
- C8.15 and FORTRAN 77 version 8.5, which include all applicable 16-bit
- enhancements described here for C8.5/386.
- C8.5/386 and FORTRAN 77/386 each have a suggested retail price of $995 and a
- special limited-time introductory price of $795. Upgrades from earlier
- versions are available directly from WATCOM.
- For more information contact WATCOM, 415 Phillip St., Waterloo. Ontario,
- Canada N2L 3X2, (800) 265-4555, Fax (519) 747-4971.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Mr. Plauger,
- I am looking for a software publisher to market a program I have developed. I
- noticed that another person requested similar information (CUJ, September
- 1991, "We Have Mail", p. 132), but you did not provide a method individual
- developers could use to locate marketing agents.
- I have developed a program which is appears to be unique in the DOS/80x86
- world. EP ("Evoked Potential") is a program which integrates data acquisition
- and process control. It was originally developed for the neuroscience
- community but appears to have wide applicability. Before I explain some of its
- features, let me briefly described what Evoked Potentials are.
- Evoked Potentials are EEG signals produced in response to a stimulus. A
- patient is fitted with a cap containing a number of electrodes to detect low
- levels of voltage (potentials) on specific parts of the scalp. Channels of
- analog electrical signals are monitored for activity while the patient is
- subjected to a stimulus. Typical stimuli include visual, somatosensory (ie:
- vibrations) and auditory (sounds). For example, a checker board may flip black
- and white squares on a screen, or a waveform generator produce waveforms at a
- certain frequency to make sounds. One of the most important aspects of
- collecting Evoked Potentials is that the data collected, usually consisting of
- recordings or averaged histograms, is synchronized with the stimulus. This
- synchronization needs to be accurate within a fraction of a millisecond.
- For instance, we may want to collect 100 trials consisting of one second
- samples of 16 channels of analog data at a rate of 1000 samples per second on
- each channel. A real-time display of the averaged histograms must be presented
- during the acquisition. The averaging process may at times reject certain
- trials due to artifacts in the data or other unusual circumstances.
- A researcher begins by first defining an experiment and a protocol. An
- experiment describes the placement of electrodes, the sample rate, and number
- of channels. A protocol describes the behavior of laboratory equipment such as
- waveform generators, other computers, and handcrafted stimulus producing
- equipment. EP uses a simple protocol language which allows the researcher to
- describe the sequence of events and contingent responses the computer should
- perform when various conditions occur (ie., a switch is pressed). A protocol
- can be defined, edited, compiled, and loaded during acquisition. This allows a
- person to start with a simple protocol then enhance and test it incrementally.
- The original purpose of EP was to acquire analog data and synchronously
- control arbitrary laboratory equipment. EP can be configured to acquire up to
- 256 channels of analog data at an aggregate rate of 200,000 samples per
- second. Nonproprietary data acquisition cards and other special equipment may
- be added to the system and accessed in the protocol. Single unit (neuron)
- spike recording capabilities will be available soon. We are also developing a
- clinical, turnkey version for use by technicians here at the Washington
- University Medical School in St. Louis.
- Each of these functions are generally available in one form or another in the
- market place. What is unique is the combination of capabilities in EP.
- Specifically, the ability to monitor and acquire many channels of data at high
- speeds while synchronously controlling external equipment from a MS DOS 80x86
- computer is novel. Further, the speed of creating or changing a protocol
- allows rapid development of complex experiments, an important capability in
- research.
- Surprisingly, people outside of this small community have also expressed
- interest in EP. Due to EP's general design, it turns out to be possible to use
- it to control arbitrary equipment and EP may have application in factories and
- other environments needing to monitor and control complex machines (ie:
- robots). Several university laboratories on the east coast are considering
- incorporating it into their work. A group studying Positron Emission
- Tomography (PET) here at Washington University are considering its use in
- counting gamma ray emissions from bank pairs of a PET machine.
- I am a computer scientist, not a marketing person. I admit my ignorance in
- that area but I have a feeling that what I have may be useful in a broader
- range of applications. How do I go about finding people interested in
- marketing this program? It seems to be a relevant question to any programmer
- who feels they have produced a useful program. Is it consistent with the
- philosophy of the C Users Journal to raise such questions in these pages?
- Sincerely,
- Sheldon Hoffman
- 917 Alanson Dr.
- St. Louis, MO. 63132
- It's hard to give general advice about how to market computer software. More
- and more companies are learning the advantages, and logistics, of distributing
- software developed by others. These companies range in size from Microsoft
- down to very specialized niche marketers working a limited geographical area.
- In your case, I'd say you have to do a lot of educating of potential customers
- -- at least outside the very limited application area you now inhabit. Your
- best bet may be to gain experience with your current crop of potential
- customers. My experience is that word of mouth does at least half the job of
- technical selling. So long as you don't sign away all rights to your product
- early on, you have a good chance of eventually connecting up with a marketeer
- you can trust.
- Meanwhile, all I can do is give you some exposure here and wish you good luck.
- -- pjp
- Dear C Users Journal,
- I am writing in response to Belinda Aboshanab's letter, published in The C
- Users Journal, September 1991. In her letter she complains about Microsoft
- OuickC's requirement of additional royalty payments due to Bitstream for any
- commercial distribution of their fonts. She asked if Borland had the same
- policy.
- I am an avid fan of Borland's products. I currently own Borland's Turbo C++
- 2.0 and Professional and Turbo Pascal for Windows. I am desperately trying to
- learn the Windows 3.0 API well enough to write some commercial grade software
- which I intend to distribute via shareware. I am extremely pleased with
- Borland's products and their technical support. I am approximately halfway
- through Charles Petzold's Programming Windows Second Edition. I have yet to
- encounter a programming example in the book that would not compile and run
- properly using Borland's C++ compiler.
- As for Ms. Aboshanab's question about royalty payments, I quote from Borland's
- No-nonsense License Statement: "Programs that you write and compile using
- Borland's language compilers, and that contain any portion of Borland code,
- may be used, given away or sold without additional license or fees, as long as
- all copies of these programs bear a valid copyright notice. By "copyright
- notice," we mean either your own copyright notice or the copyright notice
- which appears on the original diskette label on your Borland language compiler
- product. Borland's language compilers may include various support files that
- contain encoded hardware and font information used by the runtime library. You
- may use these proprietary Borland files with the programs you create with
- Borland language compilers for your own personal use. In addition, if the
- programs you write and compile using a Borland language compiler make use of
- these support files, you may distribute these support files in combination
- with these programs, provided that you do not use, give away, or sell the
- support files separately, and all copies of your programs bear a valid
- copyright notice."
- In my opinion Borland is the leader in reasonable license agreements. They
- even grant you permission to use their product on more than one machine,
- provided there can never be more than one copy in use at the same time.
- Borland also frequently offers special deals to computer magazine subscribers.
- Their Turbo C++ 2.0 Professional Compiler package includes a full
- ANSI-compatible C compiler, a C++ compiler which conforms to AT&T's 2.0
- specification, the Turbo Assembler, two Turbo Debuggers (one for DOS and one
- for Windows), and the Whitewater Resource Toolkit for creating Windows icons,
- bitmaps, etc. All this for a list price of $495. I have seen it offered for as
- low as $149. Since Ms. Aboshanab is a registered owner of Microsoft's QuickC,
- she may qualify for a discount on the purchase of Turbo C++ 2.0. It would
- certainly be worth her time to call Borland and ask about it.
- Now that I have thoroughly plugged Borland and Microsoft Windows, how about
- some articles on Windows programming techniques? Thank you for an excellent
- magazine.
- Leon G. Rollison
- 116 Towne Creek Trail
- Anderson, SC 29621
- Thanks for the clarification. We are always on the lookout for good articles
- on Windows programming. -- pip
- You can also try the Windows/DOS Developer's Journal. For information, call
- Customer Service.-- rlw
- Hello (via e-mail),
- As a reader of The C Users Journal I have been looking forward to each new
- article in your series on the ANSI C standard (now covering the headers). I
- recall you commenting that you didn't see much use for the new syntax of the
- sizeof operator which allows you to determine the size of an expression. I
- wonder if you have any other solution to the following problem relating to
- that feature?
- Given:
- typedef struct dbe {
- struct dbe *next;
- char name [8] ;
- int sequence ;
- char * comment ;
- } DBEntry_t ;
- and I want to code a comparison between a given string and the name field of a
- specific structure. Right now it looks something like this:
- DBEntry_t * current ;
- char * some_string ;
-
- strncmp( some_string, current->name,
- sizeof( current->name ) )
- I can't think of any other way to get the size of the name field in the
- DBEntry_t structure. I'm not terribly pleased with this code because I have to
- declare a structure or structure pointer to be able to specify the field. This
- limits my ability to create a macro to provide this value. I'm also worried
- that the expression current->name would have type char * on some C compilers.
- But it seems like ANSI covered that.
- Do you know of any other way to specify a field of a structure to the sizeof
- operator without using the expression syntax? (Something like DBEntry_t.name?)
- Thank you for your attention,
- Sincerely,
- Philip D. Pokorny
- philip@cel.cummins.com
- Your code is fine -- I do that sort of thing all the time. It's a minor
- nuisance that you have to name an explicit pointer to a structure when you
- want to inquire about the size of one of its members, but C has worse
- limitations than that.
- What I have branded as useless in the past is taking the sizeof an expression
- that yields only an rvalue. The expression you wrote is an array lvalue.
- That's just the sort of thing that sizeof should work on. Don't be distracted
- by the fact that an array lvalue almost invariably changes to a char * rvalue.
- For the sizeof operator and the address-of operator, the arrayness sticks
- around. -- pjp
- Dear Mr. Plauger,
- In the September 1991 issue of CUJ, you give your e-mail address as
- pjp@wsa.oz. As I understand things, that's not quite correct -- your address
- should be given as pjp@wsa.oz.au. The form you gave was appropriate for the
- old ACSnet mail system, which was internal to Australia (although a lot of
- gateway sites knew how to reach it). The form with .au is a valid Internet
- address, with the .AU indicating that you are in Australia.
- In any case, I hope you are enjoying your stay in Australia. I'm an American
- myself, so I can appreciate the rather subtle sort of "culture shock" you've
- probably been experiencing.
- Sincerely,
- Eric Zurcher
-
- CSIRO Division of Entomology
- Canberra ACT 2601
- E-mail: ericz@ento.csiro.au
- I welcome corrections to my rendition of e-mail addresses. I am too senile
- ever to understand the intricacies of intertwined networks that snake around
- the world. I'm only grateful that they work sometimes. -- pjp
- Dear Sir:
- Three short answer questions for you.
- 1) How can you pass data to a spawned process, and back again, either in C or
- 8086 assembler? I need to pass up to about 30 data items.
- 2) Is there a magazine, with focus and coverage similar to CUJ, for 8086
- assembler?
- 3) How would you define the term "side effect"? I've seen the term frequently
- used, have some vague understanding of what it means (I think), but have never
- seen a formal definition.
- I like you magazine very much. I find it always interesting, if not
- immediately useful.
- Sincerely,
- John Beach
- 1025 Medburst Rd.
- Columbus, OH 43220
- I assume you're talking about MS-DOS. You could probably set aside an area in
- the parent process large enough to hold the data to be passed. Getting the
- pointer to the spawned process is not easy, however. To pass it as part of the
- command line, you really need to encode the pointer as hexadecimal text, or
- some such. If the overhead is tolerable, you're best off opening a temp file
- in the parent and having the child write data to it.
- Other magazines besides CUJ show more assembly language than we do. Check out
- our sister publication, Windows/DOS Developer's Journal.
- I define side effect as a change in the state of a file or the value stored in
- a data object. I think that's about as good a definition as any.
- Glad you like the magazine. -- pjp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Nearest Neighbor Algorithm For Color Matching
-
-
- Geoffrey Probert
-
-
- Geoffrey Probert has been programming in C for nine years. He currently works
- at Aztek Engineering. He can be reached at (303) 466-9710 or (303) 786-9100
- ext. 144.
-
-
- A problem generally referred to as a nearest neighbor, or closest neighbor,
- problem begins with a fixed set of points whose locations are all known. When
- given a new point, you must find the nearest point in the original population
- to that new point. Most beginning programmer text books deal with the
- one-dimensional version of the nearest neighbor problem. They solve the
- problem by sorting the elements and then doing a binary search. Text books do
- not usually deal with the multi-dimensional version of this problem. This
- article will discuss an approach guaranteed to find the nearest neighbor to a
- point in a multi-dimensional space.
-
-
- The Problem
-
-
- In my application, I needed to display an image on a VGA screen (640 pixels by
- 480 lines with 256 colors) that was originally 768 pixels by 512 lines with
- 256 colors. Since the palette for the original image already existed, it
- seemed best to keep that same palette. For each of the pixels in the VGA
- image, I computed the red, green, and blue values using bilinear interpolation
- as shown in Figure 1 (see "Resampling Methods for Image Manipulation" by
- Girish T. Hagan in the August 1991 issue of C Users Journal). After computing
- the red, green, and blue values, I needed to select the color from the palette
- that was the closest match to the computed values.
- The first approach to this problem is the brute force method. This involves
- computing the distance from each of the desired values to all of the available
- neighbors. If I am generating an image of 640 pixels by 480 lines with a
- palette of 256 colors, the distance must be calculated 78.6 million times.
- This takes a considerable amount of time even on a fast CPU and inspires the
- search for a faster algorithm. I approached this problem with an algorithm
- that limits the number of distance calculations and comparisons.
- The algorithm contains two parts. The first part initializes data and
- structures. It does this once before any searching for a nearest neighbor. The
- second part actually searches for the nearest neighbor for each new pixel
- being generated.
-
-
- Two-Dimensional Problem
-
-
- To visualize the algorithm more easily, look at the two-dimensional problem
- first. In the preparation phase, I proceed just as in a one-dimensional
- problem. I sort all of the neighbors using just one of the dimensions, say X.
- Since I am sorting the neighbors, I must also bring along the values for the
- rest of the dimensions (Y in the two-dimensional case).
- The search phase is a two part process. First I must find the nearest neighbor
- using just the single dimension I originally sorted on, in this case X. This
- neighbor becomes the initial estimate. The program computes and stores the
- distance from this initial estimate to the real point as the shortest
- distance. Next, I begin to search to the left and to the right, in X, of the
- initial estimate. At each of these points the program computes the distance to
- the real point. If it is less than the shortest distance so far, then it
- becomes the new shortest distance. The trick is to end the search before
- searching all of the points. To do this, the program also computes the
- distance in the sorted dimension only (X). Once this distance is greater than
- the shortest distance for both the left and right sides, I know that no more
- points need to be looked at. The nearest neighbor corresponds to the point
- that had the shortest distance to the desired point.
-
-
- Two-Dimensional Example
-
-
- The example in Figure 2 illustrates this concept. There are nine neighbors,
- each indicated by a dot. An X indicates the desired location at (6,2). Assume
- the sort along the X dimension has been done.
- The sort along the X dimension results in an initial estimate at the point
- (6,7). The distance to this point is 5, initially the shortest distance.
- The first point to be examined would be to the left of the initial estimate,
- at (5,5). The X distance is 1, which is less than the shortest distance, 5.
- Therefore I compute the distance, sqrt(10), to this point. This is less than
- the shortest distance and therefore replaces the shortest distance.
- The next point to be examined is to the right of the initial estimate and is
- at (7,4). The X distance is 1, which is less than the shortest distance,
- sqrt(10). Therefore I compute the distance, sqrt(5), to this point. This is
- less than the shortest distance and therefore replaces the shortest distance.
- The next point to be examined is to the left of the initial estimate and is at
- (4,5). The X distance is 2, which is less than the shortest distance, sqrt(5).
- Therefore I compute the distance, sqrt(13), to this point. This is greater
- than the shortest distance and therefore does not replace the shortest
- distance.
- The next point to be examined is to the right of the initial estimate and is
- at (8,5). The X distance is 2, which is less than the shortest distance,
- sqrt(5). Therefore I compute the distance, sqrt(13), to this point. This is
- greater than the shortest distance and therefore does not replace the shortest
- distance.
- The next point to be examined is to the left of the initial estimate and is at
- (3,4). The X distance is 3, which is greater than the shortest distance,
- sqrt(5). Therefore I can quit searching to the left of the initial estimate.
- The next point to be examined is to the right of the initial estimate and is
- at (9,4). The X distance is 3, which is greater than the shortest distance,
- sqrt(5). Therefore I can quit searching to the right of the initial estimate.
- I have finished searching to both the left and the right of the initial
- estimate, therefore the nearest neighbor has been found at (7,4) with a
- distance of sqrt(5).
-
-
- Color Matching, Three Dimensions
-
-
- In the application that I was working on, an image was being generated for
- display at 640 pixels by 480 lines by 256 colors. The palette was already
- present with the original image. The individual color values ranged from 0 to
- 255. These would later be scaled for the VGA palette which ranges from 0 to
- 63. The size of my arrays and structures were designed to handle the larger
- range of 0 to 255. I used the "red" dimension for my sort.
- Listing 1 declares the global variables needed by the routines. In order to
- generate new pixels by interpolating from the original image, a copy of the
- original palette (palo) must be kept to have access to the original colors.
- The copy of the palette is named pals. It carries along the index of the
- original palette entry in num. This allows the original palette in the
- original order to be used for the new image. To speed things up, the square of
- the distance between points was computed rather than the actual distance.
- To speed up the acquisition of the initial estimate, another array was
- initialized during the preparation phase. Since there were only 256 possibly
- values in the sorted dimension, I generated an array named redindex that would
- immediately index into the sorted palette for the initial estimate.
- Listing 2 details the initialization phase. The routine sort_color makes a
- copy of the original palette and uses qsort to sort on the red dimension. The
- routine color_comp is the comparison routine for qsort. After sorting pals,
- the program generates the indexing array redindex. To do this, it uses two
- indices, one to step through redindex, and the other to step through pals. It
- sets each value in redindex to the closest value there is in pals.
- Listing 3 does the actual search for the nearest neighbor. It finds the
- initial estimate in redindex and computes the shortest distance for it. My
- search looks at one possibility on the left and then at a possibility on the
- right. The program computes the "red distance" followed by the real distance,
- if applicable. Listing 3 also provides tests to make sure the search remains
- within the bounds of pals. The dist routine computes the square of the
- distance between a palette entry and the desired red, green, and blue values.
-
-
- Summary
-
-
- This algorithm is over 45 times faster than the brute force method would be in
- my application. Stopping the search before all possible values were examined
- contributed the most to this increase in speed. Using redindex to get a quick
- initial estimate also contributed significantly.
- This algorithm should be applicable to any multidimensional nearest neighbor
- problem. One word of caution: if your data is clumped in one dimension and
- spread out in another, you should sort along the spread out dimension. This
- will speed up the search algorithm.
- Figure 1
- Figure 2
-
-
- Listing 1
- #include <stdio.h>
- #include <stdlib.h>
-
- #define PAL_LEN 256
-
- typedef struct
- {
- int red;
- int grn;
- int blu;
- int num;
- } pal;
-
- pal palo[ PAL_LEN ]; /* original palette */
- pal pals[ PAL_LEN ]; /* sorted palette */
-
- /* index for the sorted palette */
- int redindex[ PAL_LEN ];
-
- /* prototypes */
-
- int closest( int red, int green, int blue );
- long dist( int red, int green, int blue, int num );
- void sort_color();
- int color_comp( const void *a, const void *b );
-
- /* End of File */
-
-
- Listing 2
- /*begin************************************************
- * Program : sort_color
- * Descript. : Sorts the palette by the red color only
- * Also builds the index array
- * for fast lookup.
- *end**************************************************/
-
- void sort_color()
- {
- int i;
- int j;
-
- /* Make a copy of the original palette */
- for ( i=0; i<PAL_LEN; i++ )
- {
- pals[ i ].num = i;
- pals[ i ].red = palo[ i ].red;
- pals[ i ].grn = palo[ i ].grn;
- pals[ i ].blu = palo[ i ].blu;
- }
-
- /* Sort the copy of the palette */
- qsort( (void *)pals, PAL_LEN, sizeof(pal), color_comp);
-
- /* build the quick index */
- /* so we don't have to do a */
- /* binary search every time */
-
-
- for ( i=0, j=0; i<PAL_LEN; i++ )
- {
- while( pals[ j ].red < i )
- if ( ++j >= PAL_LEN )
- {
- j = PAL_LEN - 1;
- break;
- }
- redindex[ i ] = j;
- }
- }
-
-
- /*begin*************************************************
- * Program : color_comp
- * Descript. : Compares two colors (red) in a palette.
- *end***************************************************/
-
- int color_comp( const void *a, const void *b )
- {
- pal *aa;
- pal *bb;
-
- aa = (pal *)a;
- bb = (pal *)b;
- return( aa->red - bb->red );
- }
-
- /* End of File */
-
-
- Listing 3
- /*begin************************************************
- * Program : closest
- * Descript. : Returns the number of the closest color
- * from the palette, given the desired red
- * green, and blue. Find the closest point
- * by using the red index arrey. Look at
- * points on both sides of the closest
- * point. While searching, if a point is
- * closer then it is the shortest distance
- * Once you are examining points that are
- * farther away in just the one dimension
- * (red) then you are done.
- *end**************************************************/
-
- int closest( int red, int green, int blue )
- {
- long least;
- long dif;
- long sum;
- int index;
- int left;
- int right;
-
- index = redindex[ red ];
- least = dist( red, green, blue, index );
- left = index;
-
- right = index;
-
- while( ( left >= 0 ) (right < PAL_LEN ) )
- {
- if ( -left >= 0 )
- {
- /* if red dist. alone is greater, then quit */
- dif = red - pals[ left ].red;
- if ( ( dif * dif ) > least )
- left = -1;
- else
- {
- sum = dist( red, green, blue, left );
- if ( sum < least )
- {
- least = sum;
- index = left;
- }
- }
- }
- if ( ++right < PAL_LEN )
- {
- /* if red dist. alone is greater, then quit */
- dif = red - pals[ right ].red;
- if ( ( dif * dif ) > least )
- right = PAL_LEN;
- else
- {
- sum = dist( red, green, blue, right );
- if ( sum < least )
- {
- least = sum;
- index = right;
- }
- }
- }
- }
- return( pals[ index ].num );
- }
-
-
- /*begin*************************************************
- * Program : dist
- * Descript. : Color distance (squared)
- *end**************************************************/
-
- long dist( int red, int green, int blue, int num )
- {
- long dif;
- long sum;
- pal *palt;
-
- palt = &pals[ num ];
- dif = red - palt->red;
- sum = dif * dif;
- dif = green - palt->grn;
- sum += dif * dif;
- dif = blue - palt->blu;
- sum += dif * dif;
-
- return( sum );
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Hashing: From Good To Perfect
-
-
- Ron Burk
-
-
- Ron Burk has a B.S.E.E. from the University of Kansas and has been a
- programmer for the past 10 years. You may contact him at Burk Labs, P.O. Box
- 3082, Redmond, WA 98073-3082. CIS: 70302, 2566.
-
-
- Hashing is one of the most elegant responses in computer software to the
- limitations of computer hardware. You may not have seen hashing used anywhere
- but in limited database applications, but the basic algorithm has found wide
- use. This article covers some of the ways the hashing algorithm has been
- extended, and its application to a variety of problems. Some useful code
- fragments may aid your own implementation of hashing. First, however, let's
- review basic hashing.
-
-
- The Basics
-
-
- Probably the most common use of hashing is to organize database files. Suppose
- you are writing software that runs cash machines for a bank with 50,000
- customers. When a customer inserts their card into the cash machine, your
- software must quickly locate that customer's records (such as the account
- balance) using the 16-digit number on their card. In database terminology, the
- card number is the "key" to the customer database.
- Clearly, a sequential search of 50,000 records will require too much disk I/O.
- An indexed file scheme (such as a B-Tree) could cut the search to just two or
- three disk reads, but even that may be prohibitive; you must provide fast
- responses to many customer requests on inexpensive hardware.
- Hashing is a simple, efficient solution to this problem. First, create the
- card database as a file containing 2N empty, fixed-length records, where N is
- the total number of records you expect to store. Second, store each customer
- entry at the record position obtained by feeding that customer's card number
- through a hashing function (a mathematical function that transforms the key to
- an integer value in the range 0 to 2N-1). Here is an example of a simple
- hashing function that maps 16-digit card numbers into a record number between
- 0 and 99,999:
- long hash(char *cardnum) {
- long h = 0;
- whi1e (*cardnum)
- h = (h<<4) ^ *cardnum++;
- return h%100000;
- }
- The hashing function uses the record key to calculate a location for the
- record, whereas an indexed scheme like a B-tree uses tables to store and
- retrieve the correct record position. The advantage of hashing is speed; there
- are some disadvantages, however.
- The most obvious disadvantage of hashing is the problem of collisions. For
- example, the hashing function given above maps card number 1234567812345678
- and card number 1202120038744598 to the same record position: 32808. A simple
- solution to collisions is to store the record at the next empty position. In
- general, you must store the record key as part of the record to determine
- whether the hashing function has indexed the desired record or the subsequent
- positions must be searched due to a collision.
- Another shortcoming of hashing is the requirement for fixed-size tables. While
- B-trees are designed to grow and shrink gracefully, adapting to record
- insertions and deletions, the basic hashing scheme requires an I/O-intensive
- reorganization to convert tables to larger or smaller sizes. Worse, extra
- space is usually allocated for hash tables to reduce the number of collisions.
- That is why the example used a hash table twice as large as the expected
- number of entries.
- Still another problem with hashing is that the records are scattered
- arbitrarily in a disk file. Suppose you have a large database of customers in
- which the primary key is the customer name. If the file is organized as a
- B-tree, then producing a listing sorted by customer name requires relatively
- little disk I/O, since the records themselves are maintained sorted by key. If
- the same database is kept as a hashed file, then accessing the customers in
- sorted order may require as many disk reads as there are customer records.
- Some interesting algorithms have evolved from the basic hashing algorithm;
- some attempt to address shortcomings, while others simply put various
- disadvantages to good use.
-
-
- Extendible Hashing
-
-
- For some time, there was a dichotomy between hashed database organizations and
- indexed database organizations. You could get fast record access with a hashed
- file or the ability to grow and shrink gracefully with a B-tree, but you
- couldn't get both. In the past decade, however, several hashing techniques for
- expandable files have been devised.
- Extendible hashing is an algorithm that provides the speed of a hashed file
- and the extensibility of a B-tree. Extendible hashing requires a hash function
- and a table of pointers to disk pages. A disk page is simply a fixed-size
- chunk of disk (say, 4K bytes long) which is the basic unit of disk I/O for the
- database. A particular page may contain many database records. In general, the
- cost of disk I/O is high enough that database design focuses on locating the
- correct page in as few disk reads as possible and ignores the cost of locating
- the correct record within that page.
- Extendible hashing views the hashed key as an index into the table of page
- pointers. The size of the page table is required to be a power of two; only
- enough bits to address the full page table are used from the hashed key as an
- index.
- The algorithm is easier to understand with an example. Figure 1 shows an
- extendible hash database which contains a few records. The page table has two
- entries and both point to empty database pages. To insert a record, hash the
- record key and use the most significant bit of the hash as an index into the
- page table. The result points to the page where the record should be inserted.
- So far this is simple. All new records will be inserted into one of the two
- database pages, based on the first bit of the hashed record key. However, when
- one of the pages becomes too full to add another record, that page must split
- and things get interesting. Figure 2 shows what might happen to the database
- in Figure 1 after one more record insertion.
- When a page in the database overflows, there are too many keys with identical
- values in their first N bits, where 2N is the current page table size. To
- handle the overflow, you double the size of the page table and divide the
- records in the too-full page between that page and its new "buddy" page -- the
- page whose index is the same except for the last bit. For example, the buddy
- of page 0110 is page 0111.
- The extendible hash table can shrink in an analogous way. When you delete a
- record from a disk page, if the number of the records on that page has fallen
- below some threshhold, then check the buddy page to see if it has room to hold
- the records from both pages. If two pages can be coalesced, then you may also
- be able to halve the size of the page table.
- An objection to extendible hashing is the need for additional memory
- requirements to store the page table. For many applications, though, only a
- modest amount of memory is needed. For example, suppose you use this algorithm
- on a PC. By reserving 64K bytes (32K two-byte integers) for the page table and
- using a 4K disk page size, you can handle a database of up to 128M bytes (32K
- pages of 4K bytes each)!
- I presented only the fundamentals of extendible hashing here; there are
- several published improvements and variations. There is also an alternative,
- called linear hashing, that requires no page table in memory, but is a bit
- slower than one disk read per record fetch. There is no room to discuss all
- these altenatives, but see the references listed at the end of this article
- for details.
-
-
- Collisions
-
-
- Collisions are annoying for implementors of the hashing algorithm. Even though
- the odds of a collision may be small (as they will be if you have a good
- hashing function and fill only a small percentage of the hash table), you
- always have to check for collision.
- One way to remove the annoyance without actually solving the problem is to
- find applications where collisions don't matter. There are quite a number of
- these applications and they have the following in common: they can live with
- answers that are right most of the time.
- A good example of an application that can live with collisions is
- checksumming. When you send a packet of data across a noisy telephone line,
- you need to ensure that the data arrived correctly. You can hash the packet
- data and append the hash value to the packet. The receiver can then use the
- same hash function on the received data and, if the result is not the same as
- the hash value received, request a retransmission (since the data must have
- become corrupted). The hash or checksum function can be designed so that the
- probability of a corrupted packet having the same hash value as the original
- data is acceptably low.
- Another situation where 100 percent accuracy is not required is using a hash
- function to tell you when it is safe to avoid doing extra work. An interesting
- example arises in writing an SQL query processor. An SQL query may generate a
- table of records that contains duplicate rows and the user has the option of
- requesting that duplicates be removed from the query result.
- The brute force solution is to sort the table when uniqueness is required and
- strip the duplicates. Before going to that extra effort, it would be nice to
- know whether or not there are any duplicate rows. You can accomplish this by
- combining hashing with a bit vector.
- First, initialize a 64K bitmap (8K bytes) to zero. Then, as your SQL code
- retrieves each row that matches the query, feed the entire row through a
- hashing function that produces a number between zero and 64K. Using that hash
- value as a bit offset, check that entry in the bitmap. If the bit is on, then
- set a flag indicating that duplicate removal is required. If the bit is not
- on, set the bit and continue.
- This application doesn't care if there are collisions, just so long as
- collisions are fairly rare. The only penalty for a collision occurring is an
- unnecessary operation (removal of duplicates) won't be avoided. The
- performance benefits are worth some effort to keep the number of collisions
- down.
- The obvious way to reduce the number of collisions is to use a very large bit
- vector. Large vectors aren't always practical, since the bit vector needs to
- contain at least R*N bits, where R is the number of records to be hashed and
- 1/N is the highest acceptable probability of collision. If you want to handle
- a query that may return 64K records and to limit the probability of an
- unnecessary sort to 0.1, then you need at least 640K (64K*10) bits (more, if
- the hashing function does not have a perfectly uniform distribution).
- On the other hand, if you have plenty of room for a large bit vector, you can
- use that extra space to further decrease the probability of collisions. Send
- each record through three different hashing functions instead of only one.
- Now, collisions are indicated only if all three bits indexed by the hashed
- values are already set. If there is no collision, then turn on the three bits.
- You can use any number of hashing functions, but more than three often
- produces diminishing returns.
-
-
-
- Building A Hash Function
-
-
- Hashing applications depend on having suitable hashing functions. A good
- hashing function is efficient and scatters the keys uniformly. If the function
- is slow, it defeats the purpose: fast, direct access. If the function does not
- distribute keys uniformly, then collisions will be the rule rather than the
- exception.
- In constructing a hashing function for a given application, the table size,
- and also the range of hashing function results, is often determined by the
- data. For example, if you need a hash table to handle 50,000 entries, with the
- chance of collision at 50 percent or less, the hashing function must map the
- keys into the range, zero to 100,000.
- The hash() function at the beginning of this article illustrates applying the
- modulus operator (%) to make the hashing function fit the table size. It turns
- out, however, that the modulus operator is a decent hash function by itself if
- the table size is a prime number. Usually, making the hash table a little
- bigger than needed doesn't hurt, so a good choice for table size is the
- smallest prime number greater than the estimated table size.
- For an associative array class for C++, I needed to construct hash tables of
- any size on the fly. I wanted to pick table sizes that were prime numbers near
- the size specified by the caller, but I certainly didn't want to calculate
- prime numbers every time a table was created. And storing a large table of
- primes in memory was also unacceptable.
- Listing 1 is a C++ program that solves this problem. The program generates a
- logarithmic table of prime numbers that can then be compiled into a program
- that selects hash table sizes. The table is constructed so that it contains a
- prime number within roughly 20 percent of any desired hash table size. Twenty
- percent is close enough for most purposes and the entire range of 16-bit
- numbers is covered by a table that consumes only 88 bytes.
- Having solved the problem of fitting the hash to a desired range, let's look
- at how to get fast, uniform hashing functions in the first place. My current
- preference is a simple function that uses a 256-byte character transformation
- table.
- To use this hashing function algorithm, first construct a 256-byte array in
- which the 0th byte has the value 0, the first byte has the value 1, and so on.
- Next, shuffle the values in this array as follows: for each element in the
- array, pick a random number from 0 to 255 and exchange the current element
- with the array element indexed by the random number.
- Now the table performs a random character transformation. A hashing function
- that uses this table can be as simple as:
- int hash(char *key) {
- extern table[256];
- int h=0;
- while (*key)
- h = table[h^*key++];
- return h;
- }
- The hashing function takes a "random walk" through the character
- transformation table; wherever it ends up at the end of the key is the value
- for the hash.
- You will usually want at least a two-byte hash value rather than just a
- one-byte value. To get a two-byte hash, compute the left-most byte as shown
- and compute the right-most byte with the same formula, except add one to the
- first character in the key. Adding one to the first character starts the
- random walk at a different spot in the table and the result bears no
- relationship to the other hash byte. You can form a three-or four-byte hash in
- an analogous manner.
-
-
- Perfect Hashing
-
-
- The previous algorithm depends on a table constructed at random, so there is a
- small, but non-zero probability that a very poor hashing function may have
- been created. You can guard against poor functions with the following steps.
- First, construct a data file that contains a large number of keys
- representative of the data the function must be good at hashing. Second, write
- a program that tests hashing functions against the keys in your test file.
- This program is basically a loop that contains two actions: reshuffle the
- character transformation table and apply the resulting hash function to the
- data file to see how well it performs.
- The program is looking for a shuffled table that generates a hash function
- that produces few collisions as possible. An easy way to count collisions is
- to allocate a bit vector that has twice as many bits as there are keys in your
- sample data file -- there's no point in allocating a table that can hold any
- data, since all you are interested in is counting collisions. This program
- also has to save a copy of the most successful transformation table found so
- far.
- This program is appealing: you can start it up and have it look for a good
- hashing function all day long while you do something else. This leads to an
- interesting thought: what if you tell the program to keep going until it finds
- a hashing function that produces no collisions whatsoever? The program may
- take forever to find such a function and even if it does, the hashing function
- will probably produce collisions on any other set of keys. There is an
- application for this technique, however.
- A hashing function that produces no collisions for a given set of keys is
- called a perfect hashing function. If that hashing function also maps those
- keys onto consecutive numbers with no gaps, then it is called a minimal
- perfect hashing function. One application which has to deal with a small,
- constant set of keys is the lexical analyzer of a compiler.
- Part of a C++ book I am writing involves a compiler for a little language that
- reserves the following keywords:
- break else local
- class exit new
- const for return
- continue foreach sysconst
- delete function while
- do if
- When the lexical analyzer encounters an identifier, it must efficiently figure
- out whether or not the identifier is a keyword. Keywords are mapped to
- integers for ease of use (it is easier to store if as a 5 than to keep the
- string if and do string comparisons).
- Listing 2 contains a C++ program that attempts to find a perfect hashing
- function for an input file of keywords. Rather than just reshuffling the
- character transformation table at random and hoping for the best, this program
- uses heuristics to try to swap just a few table entries as it tries to achieve
- a perfect hash. In order to shorten the program, I've left out the code for
- writing the character table out as a compilable program.
- The real beauty of this hashing function is that the same function can be used
- for both perfect hashing of keywords and general-purpose hashing of
- identifiers. The program produced a hashing function that maps the keywords
- into the integers zero through 17. In the lexical analyzer, I form a two-byte
- hash, as shown earlier. If the first byte lies in the range zero to 17, then
- it may be a keyword and must be checked. Non-keywords use the two-byte hash as
- an index into it in the symbol table.
-
-
- Further Reading
-
-
- As always, Knuth (in this case, Volume 3, Sorting and Searching) is a good
- place to start reading. The character transformation table method of hashing
- and the perfect hashing algorithm were both derived from an article by Peter
- K. Pearson in June 1990 issue of Communications of the ACM.
- I finally found a book that does a decent job of covering the advances in file
- organization algorithms of the last ten years and, in particular, hashing
- algorithms. That book is File Organization and Processing, Alan L. Tharp
- (1988, Wiley) and it covers most of the algorithms mentioned in this article
- (and a good many more) in enough detail for you to derive your own
- implementations.
- Figure 1
- Figure 2
-
- Listing 1 (mkprmc.)
- /*
- (C) Copyright 1990 Ron Burk
- All Rights Reserved
-
- mkprm.c - Generate htabp.c, a list of prime numbers.
-
-
- This program generates a logarithmic table of prime numbers. The
- table is stored as source code that can be compiled into another program.
-
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <limits.h>
-
- typedef unsigned long ulong;
-
- class outfile
- {
- FILE *fout; // handle to output file
- int nprimes;
- void printhead();
- void printtail();
- public:
- outfile() : fout(NULL), nprimes(0) { };
- ÿoutfile() { close(); }
- void open(const char *fname);
- void close() { printtail(); fclose(fout); }
- void prime(ulong); // format prime # into C initialized array
- };
-
-
- // PERCENTAGE specifies the allowable percentage discrepancy between
- // the desired table size and the nearest prime number in the table.
-
- const PERCENTAGE = 20;
-
-
- // MAX_PRIME limits the range of numbers which will be searched for
- // primes. Note that USHRT_MAX is guaranteed to be >= 65,535
-
- const unsigned MAX_PRIME = USHRT_MAX;
-
-
- const MAX_NPRIMES = 100 + MAX_PRIME / 10;
-
- const char OUTFILE[] = "htabp.c";
-
- int main(int /*argc*/, char /***argv*/)
- {
- unsigned previous = 0, log = 10;
- static unsigned primes[MAX_NPRIMES];
- int nprimes = 1;
- int nprint = 0;
- outfile fout;
-
- fout.open(OUTFILE);
- primes[0] = 2;
- for(unsigned num = 3; num < MAX_PRIME-log && nprimes < MAX_NPRIMES; num+=2)
- {
- for(int i = 0; i < nprimes; ++i)
- if(!(num%primes [i]))
- break;
- else if(num/primes[i] <= primes[i])
- {
-
- primes [nprimes++] = num;
- if(num > previous + log)
- {
- ++nprint;
- previous = num;
- log = num / (100 / PERCENTAGE);
- fout.prime(num);
- printf( "%u\n", num );
- }
- break;
- }
- }
- num = primes [nprimes-1];
- if(num != previous)
- {
- fout.prime(num);
- printf( "%u\n", num );
- ++nprint;
- }
- printf( "nprimes = %d nprint = %d\n", nprimes, nprint );
- }
-
- void outfile::open(const char * fname)
- {
- fout = fopen(fname, "w");
-
- if(fout == NULL)
- }
- fprintf(stderr, "Can't open output file '%s' for writing.\n",
- fname);
- exit(EXIT_FAILURE);
- }
- }
-
- void outfile::prime(ulong p)
- {
- int boundary = !(nprimes%4);
-
- if(nprimes == 0)
- printhead();
- if(nprimes)
- {
- fprintf(fout, ",");
- if(boundary)
- fprintf(fout, "\n");
- }
- if(boundary)
- fprintf(fout, " ");
- fprintf(fout, "%12lu", p);
- ++nprimes;
- }
-
- void outfile::printhead()
- {
- char *type;
-
- fprintf(fout,
-
- "// machine generated; DO NOT EDIT\n"
-
- "static unsigned short Primes [] = \n"
- " {\n"
-
- );
- }
-
- void outfile::printtail()
- {
- fprintf(fout,
-
- "\n"
- " };\n"
- "const NPRIMES = %d;\n",
-
- nprimes
- );
- }
-
- /* End of File */
-
-
- Listing 2 (makehash.c)
- /*
- * (C) Copyright 1990 Ron Burk
- * All Rights Reserved
- *
- * makehash.c - program to make hashing functions.
- *
- * Note that this is a C++ program. It has been compiled
- * with Turbo C++ and Zortech C++. There is conditional
- * code to hack around the fact that Zortech does not
- * allow a member function and a const member function of
- * the same name.
- */
-
- #ifdef__ZTC______LINEEND____
- #define CONST
- #else
- #define CONST const
- #endif
-
- #include <assert.h>
- #include <ctype.h>
- #include <limits.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- typedef unsigned char uchar;
- enum bool { FALSE, TRUE };
-
- FILE *OpenFile(const char *FileName, const char *IOMode)
- {
- assert(FileName != 0);
- if(IOMode == 0)
- IOMode = "r";
- FILE *FileDescriptor = fopen(FileName, IOMode);
- if(FileDescriptor == 0)
- {
-
- fprintf(stderr, "'%s': Can't open for mode '%s'\n",
- FileName, IOMode);
- exit(EXIT_FAILURE);
- }
- return FileDescriptor;
- }
-
- char *strdup(const char *s, size_t len)
- {
- char *ret = (char *)malloc(len+1);
- memcpy(ret, s, len);
- ret[len] = '\0';
- return ret;
- }
-
- inline char *strdup(const char *s)
- {
- return strdup(s, strlen(s));
- }
-
- class KeyWord
- {
- public:
- KeyWord(char *Name=0, char *Value=0);
- char *Name()
- { return Name_; }
- char *Value()
- { return Value_; }
- bool Read(FILE *FileDescriptor);
- private:
- char *Name_;
- char *Value_;
- };
-
- class KeyTable
- {
- public:
- KeyTable(const char *FileName);
- int NumberOfKeys()
- { return NumberOfKeys_; }
- KeyWord *operator[] (int KeyWordNumber);
- void MoveTo(int Position1, int Position2);
- static const int MAXKEYWORDS;
- private:
- int NumberOfKeys_;
- KeyWord **Table;
- };
- const int KeyTable::MAXKEYWORDS = 100;
-
- class ByteTable
- {
- public:
- ByteTable();
- void SetAscending();
- void Shuffle(int Shuffles=1);
- int Index(int Value);
- uchar &operator[](int ByteNumber);
- #ifndef __ZTC______LINEEND____
- uchar operator[](int ByteNumber) const;
-
- #endif
- ByteTable &operator=(int Value);
- private:
- uchar *Table;
- };
-
- // KeyWord member functions
- KeyWord::KeyWord(char *Name__, char *Value__)
- : Name_(Name__), Value__(Value__)
- {
- }
-
- /********************************
- KeyWord::Read - read a KeyWord from an open file.
-
- Each line in the KeyWord file must be a KeyWord line, an empty line,
- or a comment. A KeyWord line is a line that contains a KeyWord
- optionally followed by white space and a value. The KeyWord and the
- value can contain any characters except white space (except the KeyWord
- cannot begin with '#'). An empty line is a line that only contains
- white space. A comment is any line that begins with a '#' (optionally
- preceeded by white space).
-
- ********************************/
-
- bool KeyWord::Read(FILE *FileDescriptor)
- {
- const int MAXLINE = 256;
- char Line[MAXLINE+1];
-
- Name_ = Value_ = 0;
- while(fgets(Line, MAXLINE, FileDescriptor))
- {
- char *Scanner = Line;
- while(*Scanner && isspace(*Scanner)) // allow leading white space
- ++Scanner;
- if(*Scanner && *Scanner != '#') // if not empty and not comment
- {
- Name_ = Scanner; // remember start of KeyWord name
- while(*Scanner && !isspace(*Scanner))
- ++Scanner; // skip over name
- if(*Scanner) // if something follows the name
- {
- *Scanner++ = '\0'; // NUL-terminate the name
- while(*Scanner && isspace(*Scanner))
- ++Scanner;
- if(*Scanner)
-
- {
- Value = Scanner; // remember start of value
- while(*Scanner && !isspace(*Scanner))
- ++Scanner;
- *Scanner = '\0';
- Value_ = strdup(Value_); // make permanent copy
- }
- }
- Name_ = strdup(Name_); // make permanent copy
- return TRUE;
- }
-
- }
-
- return FALSE; // hit End-Of-File
- }
-
- // KeyTable member functions
-
- KeyTable::KeyTable(const char *FileName)
- {
- Table = new KeyWord *[MAXKEYWORDS];
- assert(Table != 0);
- FILE *KeyWordFile = OpenFile(FileName, "r");
- KeyWord CurrentKeyWord;
- for(int KeyWordNumber=0; CurrentKeyWord.Read(KeyWordFile); ++KeyWordNumber)
- {
- Table[KeyWordNumber] = new KeyWord;
- *Table[KeyWordNumber] = CurrentKeyWord;
- printf( "name='%s', value='%s'\n", Table[KeyWordNumber]->Name(),
- Table[KeyWordNumber]->Value());
- }
- NumberOfKeys_ = KeyWordNumber;
- printf( "read %d keywords\n", KeyWordNumber);
- }
-
- KeyWord *KeyTable::operator[] (int KeyWordNumber)
- {
- assert(KeyWordNumber >= 0);
- assert(KeyWordNumber < NumberOfKeys());
- return Table[KeyWordNumber];
- }
- void KeyTable::MoveTo(int MovePosition, int ToPosition)
- {
- KeyWord *Value = Table[MovePosition];
- int Position;
- for(Position=MovePosition; Position < NumberOfKeys()-1; ++Position)
- Table[Position] = Table[Position+1];
- for(Position=NumberOfKeys()-1; Position >= ToPosition; --Position)
- Table[Position] = Table[Position-1];
- Table[ToPosition] = Value;
- }
-
- // ByteTable member functions
-
- ByteTable::ByteTable()
- {
- Table = new uchar[256];
- assert(Table != 0);
- memset(Table, 0, 256);
- }
-
- void ByteTable::SetAscending()
- {
- for(int ByteNumber=0; ByteNumber < 256; ++ByteNumber)
- Table[ByteNumber] = ByteNumber;
- }
-
- void ByteTable::Shuffle(int Shuffles)
- {
- // swap each entry in the table with a randomly chosen entry
-
-
- for(int PassNumber=0; PassNumber < Shuffles; ++PassNumber)
- {
- for(int ByteNumber=0; ByteNumber < 256; ++ByteNumber)
- {
- int RandomPartner = rand() % 256;
- uchar OtherValue = Table[RandomPartner];
- Table[RandomPartner] = Table[ByteNumber];
- Table[ByteNumber] = OtherValue;
- }
- }
- }
-
- uchar &ByteTable::operator[](int ByteNumber)
- {
- assert(ByteNumber >= 0);
- assert(ByteNumber <= 255);
-
- return Table[ByteNumber];
- }
- #ifndef__ZTC______LINEEND____
- uchar ByteTable::operator[](int ByteNumber) const
- {
- assert(ByteNumber >= 0);
- assert(ByteNumber <= 255);
-
- return Table[ByteNumber];
- }
- #endif
-
- int ByteTable::Index(int Value)
- {
- uchar *Found = (uchar *)memchr(Table, Value, 256);
- if(Found)
- return Found - Table;
- else
- return -1;
- }
-
- inline
- ByteTable &ByteTable::operator=(int Value)
- {
- assert(Value >= 0);
- assert(Value <= 255);
- memset(Table, Value, 256);
- return *this;
- }
-
- int ByteHash(const char *Text, const ByteTable &HashTable)
- {
- int Hash = 0;
- while(*Text)
- Hash = HashTable[*Text++ ^ Hash];
- return Hash;
- }
-
- int MakeHash(const char *KeyWordFileName);
-
- void Usage()
-
- {
- fprintf(stderr, "Usage: makehash keyfile\n");
- exit(EXIT_FAILURE);
- }
-
- int main(int argc, char **argv)
- {
- if(argc < 2)
- Usage();
- char *KeyWordFileName = argv[1];
- exit(MakeHash(KeyWordFileName));
- }
-
- int MakeHash(const char *KeyWordFileName)
- {
- KeyTable InputTable(KeyWordFileName);
- int iKeyWord;
- int *Failures = new int[InputTable.NumberOfKeys()];
-
- assert(Failures != 0);
- ByteTable HashBytes;
- for(int Attempts=0; Attempts < 999; ++Attempts)
- {
- for(int xx=0; xx < InputTable.NumberOfKeys(); ++xx) Failures[xx]=0;
- for(int TableBase = 0; TableBase < 256; ++TableBase)
- {
- HashBytes.SetAscending(); // set equal to 0,1,2,...255
- HashBytes.Shuffle(); // randomize
- int NotEligible[256];
- for(int x=0; x < 256; ++x) NotEligible[x] = 0;
- for(int iKeyWord = 0; iKeyWord < InputTable.NumberOfKeys(); ++iKeyWord)
- {
- const char *Text = InputTable[iKeyWord]->Name();
- int TextLength = strlen(Text);
- int RandomWalk[99];
- int Hash = 0;
- uchar H[99];
- for(int i = 0; i < TextLength; ++i)
- {
- int j = Hash ^ Text[i];
- Hash = H[i] = HashBytes[j];
- ++NotEligible[j];
- RandomWalk[i] = j;
- }
- int DesiredValue = (iKeyWord + TableBase) % 256;
- for(i = TextLength-1; i >= 0; --i)
- {
- int Pos = RandomWalk[i];
- --NotEligible[Pos];
- int Other = HashBytes.Index(DesiredValue);
-
- assert(Other >= 0);
- if(NotEligible[Pos] == 0 && NotEligible[Other] == 0)
- {
- HashBytes[Other] = HashBytes[Pos];
- HashBytes[Pos] = DesiredValue;
- ++NotEligible[Other];
- ++NotEligible[Pos];
- assert(ByteHash(Text, HashBytes)
-
- == (iKeyWord + TableBase)%256);
- break;
- }
- else // else not eligible for swapping
- {
- DesiredValue = Other ^ Text[i];
- ++NotEligible[Other];
- }
- }
- if(i < 0)
- break;
- }
- if[iKeyWord >= InputTable.NumberOfKeys())
- {
- for(iKeyWord = 0; iKeyWord < InputTable.NumberOfKeys(); ++iKeyWord)
- {
- const char *Name = InputTable[iKeyWord]->Name();
- printf("Hash('%s') = %d\n", Name, ByteHash(Name, HashBytes));
- }
- return EXIT_SUCCESS;
- }
- else
- {
- if(++Failures[iKeyWord] > 20)
- {
- InputTable.MoveTo(iKeyWord, 0);
- break;
- }
- }
- }
- }
-
- return EXIT_FAILURE;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Data Compression Using Huffman Coding
-
-
- Dwayne Phillips
-
-
- Dwayne Phillips works as a computer and electronics engineer with the United
- States Department of Defense. He has a Ph.D. in electrical and computer
- engineering at Louisiana State University. His interests include computer
- vision, artificial intelligence, software engineering, and programming
- languages.
-
-
- One of the best known data compression schemes is Morse code. Morse code uses
- few dots and dashes for frequently occurring letters such as e and t, and more
- dots and dashes for infrequent letters such as x and z. Compare this scheme to
- the ASCII code which uses seven bits for all the letters and symbols. The
- ASCII code is clearly inferior when it comes to the length of a message.
- Huffman coding[1] uses an idea similar to Morse code. In Huffman coding, you
- represent frequently occurring characters with a small number of bits, and
- infrequent characters with a larger number of bits. We could always represent
- the character t by 01 instead of 1100001 (ASCII), e by 10 instead of 1100101
- and so on. This would save space, but some files do not contain any t's or e's
- and these short codes could be used for other characters. Adaptive coding
- solves that problem. In adaptive coding, you examine a file, count the
- occurrences of each character, and create a code unique to that file.
- To compress a file using Huffman coding you (1) examine the file and count the
- occurrences of characters, (2) create a new code for the characters in the
- file, and (3) code the file using the new codes. To decompress a file, you (1)
- read the unique code for the file, (2) read the compressed file, and (3)
- decode the compressed file using the unique code. The trick in this process is
- creating the new code and packing and unpacking bits. The source code
- presented later will illustrate how to do that.
- Huffman coding attempts to create a minimum redundancy code, that minimizes
- the average number of bits per character. Let N = number of characters (256
- for eight bit codes) and P(i) = probability of the ith character occurring.
- This yields equation 1.
- Equation 1
- We let L(i) = number of bits to represent the ith character and we want to
- minimize the average number of bits needed to code a file as shown in equation
- 2.
- Equation 2
- There are two rules to follow when using Huffman coding. First, the code for
- each character must be unique. Second, there will be no start of code or end
- of code once the stream of bits begins. In ASCII you know when the code for
- each character starts because they are all seven bits long. When you've coded
- a file using Huffman coding, however, the codes for each character have
- different lengths (some are two bits, some three bits, and so on). If you have
- a start of code marker, you will waste space and defeat the purpose of data
- compression.
- In order to follow the second rule, you must abide by the first. This means
- that no code can be the prefix of another code. The code for each character
- must be unique. For example, if you represent an a by 01 and a b by 1, then
- the bit stream 011101101 can be decoded to abbaba. Now suppose we needed a
- code to represent c. We cannot represent c by 10 because the code for b (1) is
- a prefix of the code for c (10). In such a case, the stream 011101101 could
- either be abcbcb or abbaba. To correct this confusion, you could represent a
- by 01, b by 1, and c by 00. The prefix condition is not violated and there
- will not be any confusion when it's time to decompress the bit stream.
- There is one more derived rule for Huffman coding. The following equation
- states mathematically that the most frequently occurring characters will have
- shorter codes. If two characters occur with the same frequency, then their
- codes will be equal or differ only by one bit.
- if P (a) £ P (b) £ P (c) then L (a) >= L (D) >= L
- Figure 1 and Figure 2 illustrate the creation of Huffman codes. Figure 1 shows
- a simple example. Suppose there is a file containing 10 as, five cs, and three
- bs. Figure 1 shows the characters in the file arranged with the most frequent
- letter at the top and with the number of occurrences next to each character.
- You begin coding at the bottom with the least frequently occurring characters.
- You link characters b and c by adding their numbers of occurrences and
- connecting them with a line. You give the least occurring letter (b) a 1 bit
- and the most occurring letter (c) a 0 bit. In the next step you link letter a
- with the bc combination. You give the bc combination a 1 bit and the a a 0
- bit. The codes for these characters are read backwards from right to left. The
- new code for a is 0. The new code for b is 11 and for c is 10.
- Now, if a part of the input file contained abcaaaccb, the coded bit stream
- would be 0-11-10-0-0-0-10-10-11. This required only 14 bits. The ASCII coding
- (seven bits per character) would require 63 bits. The entire file of 18
- characters would only require 26 bits instead of 126 bits. The compressed
- coding requires only 26/126 = 20.6 percent as much space. (There is, however,
- the question of how to store the new code (a=0 b=11 c=10) at the front of the
- compressed file without taking too much space.) Notice that the most
- frequently occurring character, a, has the shortest code. Using equation 1 and
- equation 2, the average number of bits per character is 1.44 instead of 7 for
- ASCII.
- Figure 2 shows a more complicated example. The coding process begins at the
- bottom. Everything proceeds as before until you link c with the def
- combination. At this point both a (10) and b (8) occur less frequently than
- the cdef combination (12) so you must link a and b. After this you may link
- the ab combination with cdef. You read the new codes backwards from right to
- left and the result is shown at the bottom right of the figure. Again, using
- equations 1 and 2, the average number of bits per character is 2.46 instead of
- 7 for ASCII.
- Now let's look at some code listings. Listing 1 shows the include file for the
- program. Note the three data structures. The first structure, item_struct, is
- used inside the program to create the new codes, code the characters in the
- input file, and decode the bit stream in the compressed file. This structure
- is easy to use inside the program, but is too long to store as the header to
- the compressed file. The second, header_struct, is the one stored as the
- header to the compressed file. It requires much less space than the
- item_struct and still holds enough information to allow the compressed file to
- be decompressed. This scheme enables you to experiment with different types of
- file headers to store with the compressed file. If you can create a shorter,
- more efficient file header, then you only need to change the conversion
- routines and the read and write routines. If you used only one structure for
- internal use and the file header, then you would need to change code
- throughout the program whenever you thought of a better file header. The third
- structure in Listing 1 is the constant CODE_LENGTH. This gives the maximum
- number of bits you can use for a new code. This may not be big enough if you
- are compressing a large file that has some very infrequent characters. The
- more infrequent the character the longer its code. If you have problems with
- large files you may need to increase this to 24 or 32.
- Listing 2 shows the main routine of the program and several other functions.
- The main routine interprets the command line and starts the compression or
- decompression process. The function read_input_file_and_create_histogram
- performs the first step of compression. It reads through the entire
- uncompressed file and records the number of occurrences of each character.
- This information will be passed to the functions in Listing 3 which will then
- create the new code. This points out one of the disadvantages of Huffman
- coding. In Huffman coding you must read the file twice. The first time you
- count occurrences of characters and the second time you code the characters.
- Also shown in Listing 2 are the functions convert_short_to_long and
- convert_long_to_short. These functions convert the long item_struct to the
- shorter header_struct discussed previously. If you create a better file header
- for the compressed file, then you need to change these two conversion
- functions.
- Listing 3 shows the functions that perform the second step of compression.
- They take the number of occurrences of each character and create the new code
- for this particular file. The function create_huffman_code is the controlling
- routine for this process. It first sorts the item_array so the most frequent
- characters are at the "top" and the least frequent are at the "bottom" as in
- the examples of Figure 1 and Figure 2. Next, it disables the characters that
- do not occur in the input file. There is no need to process those characters.
- Finally, it goes into a while loop of linking or combining the two smallest
- items in the item_array until they are all linked. Most of the functions in
- Listing 3 search the item_array, and find the characters to be linked.
- The key functions in Listing 3 are code_smallest_item and
- code_next_smallest_item. They attach the 1's and 0's to the characters. The
- difficult part of this task is propagating the 1's and 0's to all the
- characters linked together. Notice in Figure 2 that when cdef was linked to
- ab, the 1 assigned to c propagated down to d, e, and f. At the end of
- code_smallest_item the program loops through the item_array looking for links
- to other characters. If it finds a link, it calls itself recursively using the
- link (code_next_smallest_item does the same operation). The function
- combine_2_ smallest_items created these links using the .includes part of the
- item_array.
- Listing 4 shows the final step of compression. These functions use the new
- codes to code the input file into a compressed bit stream and write this to a
- file. The function code_and_write_output_file controls the process. It reads a
- character from the input file, uses the new code for that character to set
- bits in the output buffer, and writes the output buffer to the output file.
- One thing to watch in the output process is to wait until the bit stream in
- the output buffer is on a byte boundary before writing to the file. The last
- section of code in this function performs that task.
- The key functions in Listing 4 are code_byte, set_bit_to_1, and
- clear_bit_to_0. The code_byte function looks at the character to code, finds
- its new code, and calls the other two functions to set the bits in the output
- buffer. The bit setting functions set or clear a particular bit in the output
- buffer. They use bit masks defined in the include file and bit-wise AND or
- bitwise OR the mask with a byte in the output buffer.
- Listing 5 shows the code to decompress a file. Decompression is easier and
- quicker than compression. The new code already exists so you only need to
- reverse the last step of the compression process. The function
- decode_compressed_file controls the process. The first step is to read the new
- codes from the file header. This comprises a file read and then a call to
- convert_short_to_long to put the header into the item_struct format.
- The function decode_file_and_write_output performs the last two steps of the
- decompression process. It decodes the compressed bit stream and writes the
- characters to the output file. To decode the compressed bit stream you must
- look at the codes read from the file header and compare them one by one to the
- bit stream. Recall that the codes are all unique and the prefix condition must
- hold. Therefore, if you start at one end of the item_struct and compare each
- code to the first bits in the bit stream, you will find a match. The program
- uses two functions to do this. The function convert_bits_to_char looks at the
- bit stream and translates the 1 and 0 bits to ONE and ZERO character
- constants. The function decode_bits uses this stream of characters to search
- through the item_struct. This makes the comparisons easier because the
- item_struct holds the codes as characters. The convert_bits_to_char function
- examines each bit using a shift bit and compare operation. This requires less
- code than the set bit functions discussed earlier.
- The decode_bits function looks at the input bits, matches them to a code in
- the item_struct, and puts the decoded character in the output buffer. This
- operation requires searching through the item_struct until you find a code
- that matches the first bits in the bit stream. A strncmp performs the
- comparisons. When the decode_bits finds a match, it writes the decoded
- character to the output buffer.
- This process of reading compressed bits, matching the codes, and writing out
- the decoded characters continues until the compressed file is empty. At that
- time the decompression process is finished.
- There is always room for improvement in a program. The objective of this
- program is to compress files. The biggest place for reducing the size of the
- compressed file is in the file header. This must store the new codes for the
- characters in the original file. The header_struct shown in Listing 1 contains
- 4,356 bytes. This can no doubt be smaller. One idea is to keep only those
- characters that occurred in the message instead of all 256. Another idea is to
- pack the codes into bits instead of characters. The program is designed to
- allow experiments in this area. The item_struct is used internally so you do
- not need to make code changes everywhere. When you are ready to try a new file
- header, change the conversion routines in Listing 2 (convert_long_to_short,
- convert_short_to_long) and the write and read routines in Listing 4 and
- Listing 5, (output_file_header) and (input_file_header).
- Reference
- 1. Huffman, David, "A Method for the Construction of Minimum-Redundancy
- Codes," Proceedings of the IRE, Vol. 40, No. 9, pp. 1098-1101, 1952.
- Figure 1
- Figure 2
-
- Listing 1 (cujhuff3.c)
- /************************************************
- *
- * file d:\lsu\huffman.h
- *
- * Functions: This file contains no functions. It
- * contains declarations of the data
- * structures used by the hoffman coding
- * program.
- *
- * Purpose: To declare data structures.
- *
- * Modifications:
- *
-
- *************************************************/
-
-
- #include "d:\c600\include\stdio.h"
- #include "d:\c600\include\graph.h"
- #include "d:\c600\include\io.h"
- #include "d:\c600\include\fcntl.h"
- #include "d:\c600\include\dos.h"
- #include "d:\c600\include\math.h"
- #include "d:\c600\include\sys\types.h"
- #include "d:\c600\include\sys\stat.h"
- #include "d:\lsu\iptype.h"
-
- #define LENGTH 256 /* length of item array */
- #define LLENGTH 25 /* length of includes array */
- #define ONE '1'
- #define ZERO '0'
- #define OTHER '2'
- #define CODE_LENGTH 16 /* max # of bits for a character */
- #define IB_LENGTH 150
- #define OB_LENGTH 1000
- #define END_FILE 254
-
- /* The following constants are for setting, clearing, and
- testing bits in a char buffer */
-
- #define SET_BIT_SEVEN 1
- #define SET_BIT_SIX 2
- #define SET_BIT_FIVE 4
- #define SET_BIT_FOUR 8
- #define SET_BIT_THREE 16
- #define SET_BIT_TWO 32
- #define SET_BIT_ONE 64
- #define SET_BIT_ZERO 128
-
- #define CLEAR_BIT_ZERO 127
- #define CLEAR_BIT_ONE 191
- #define CLEAR_BIT_TWO 223
- #define CLEAR_BIT_THREE 239
- #define CLEAR_BIT_FOUR 247
- #define CLEAR_BIT_FIVE 251
- #define CLEAR_BIT_SIX 253
- #define CLEAR_BIT_SEVEN 254
-
- /**************************************************
- *
- * This is the item_struct which defines the
- * item_array used throughout the Huffman
- * program.
- *
- * indicator - shows whether or not to process
- * an element of the item array. The values
- * can be D=disable or E=enalble.
- *
- * character - the original character in the big
- * file. This can be any character from
- * decimal 0 to 255.
- *
- * count - counts the number of times a character
- * appears in the original big file.
- *
-
- * coded[CODE_LENGTH] - this holds the code for a
- * character. It is of the form 101101222222.
- * The '2' is the OTHER character which means
- * it is not used. e.g. if code=10122222, then
- * the code is 101 and the 22222 are dummy
- * characters.
- *
- * includes - this is a number that links the item
- * to any other item with which it has been
- * combined.
- *
- **************************************************/
-
- struct item_struct{
- char indicator;
- char character;
- long count;
- char coded[CODE_LENGTH];
- short includes[LLENGTH];
- };
-
- /*************************************************
- *
- * This is the header_struct. We'll save this
- * at the beginning of the compressed file. It
- * is smaller than the item_struct and will save
- * some space in the compressed file.
- *
- *************************************************/
-
- struct short_item{
- char character;
- char coded[CODE_LENGTH];
- };
- struct header_struct{
- struct short_item items[LENGTH];
- long in_file_length;
- };
- /* End of File */
-
-
- Listing 2 (huffman.c)
- /******************************************************
- *
- * file d:\huffman.c
- *
- *
- ******************************************************/
-
-
- #include "d:\lsu\cujhuff.h"
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- char input_file_name [80],
- output_file_name [80],
- r[80];
-
-
- int i, j;
- struct item_struct item_array[LENGTH];
- struct header_struct file_header;
-
-
- if(argc < 3){
- printf("\nHUFFMAN> You did not enter enough file names.");
- printf("\nHUFFMAN> Try again: huffman in_file_name out_file_name");
- printf("\nHUFFMAN> or");
- printf("\nHUFFMAN> huffman destination_file packed_file d");
- exit(1);
- }
-
- /*
- If there are four arguments then you are decompressing
- the compressed input file to a full size output file.
- */
-
- if(argc >= 4){
- strcpy(output_file_name, argv[1]);
- strcpy(input_file_name, argv[2]);
- decode_compressed_file(input_file_name,
- output_file_name,
- item_array,
- &file_header);
- }
-
- else{ /* else you compress the full size input file and write
- out a compressed output file */
-
- strcpy(input_file_name, argv[1]);
- strcpy (output_file_name, argv[2]);
- read_input_file_and_create_histogram(input_file_name, item_array);
- sort_item_array (item_array);
- printf("\n\nHUFFMAN> This is the sorted item array:\n");
- print_item_array (item_array);
- create_huffman_code(item_array);
- printf("\n\nHUFFMAN> This is the Huffman coding of the
- characters:\n");
- print_item_array(item_array);
- printf("\n> Coding the file");
- convert_long_to_short(item_array, &file_header);
- code_and_write_output_file(item_array,
- input_file_name,
- output_file_name,
- &file_header);
-
- } /* ends else compress input file to output file */
-
- } /* ends main */
-
- /*
- read_input_file_and_create_histogram(...
-
- Read the input file. Count up the occurances of each
- character and create a histogram.
- */
-
-
- read_input_file_and_create_histogram(input_file_name, item_array)
- char input_file_name[];
- struct item_struct item_array[];
- {
- char buffer[1000];
-
- int bytes_read,
- i,
- in_file_desc,
- j;
-
- clear_item_array (item_array);
- in_file_desc = my_open(input_file_name);
- printf("\n> in file desc = %d", in_file_desc);
- bytes_read = 1000;
-
- while(bytes_read == 1000){
- bytes_read = my_read(in_file_desc, buffer, 1000);
- for(i=0; i<bytes_read; i++) {
- j = buffer[1];
- item_array[j].count = item_array[j].count + 1;
- } /* ends loop over i */
- } /* ends while bytes_read == 1000 */
-
- close (in_file_desc);
-
- } /* ends read_input_file_and_create_histogram */
-
- /*
- clear_item_array(...
-
- This function initializes the item_array.
- */
-
- clear_item_array(item_array)
- struct item_struct item_array[];
- {
- int i,j, k;
-
- for(i=0; i<LENGTH; i++)(
- item_array[i].indicator = 'E';
- item_array[i].character = i;
- item_array[i].count = 0;
- for(k=0; k<LLENGTH; k++)
- item_array[i].includes[k] = 256;
- for(j=0; j<CODE_LENGTH; j++)
- item_array[i].coded[j] = OTHER;
- } /* ends loop over i */
- } /* ends clear_item_array */
-
- /*
- print_item_array (item_array)
-
- This function is for debugging. It prints
- to the screen the item_array.
- */
-
- print_item_array (item_array)
- struct item_struct item_array[];
-
- {
- char response[5];
- int i,
- j,
- k,
- max_i,
- printed;
- float ratio;
- long max;
- printed = 0;
- max = 0;
-
- printf("\n>");
- printed++;
-
- for(i=0; i<LENGTH; i++){
- if(item_array[i].count > max){
- max = item_array[i].count;
- max_i = i;
- } /* ends if count > max */
- } /* ends loop over i */
-
- ratio = 30.0/(float) (item_array[max_i].count);
-
- for(i=0; i<LENGTH; i++){
- if(item_array[i].count != 0){
- printed++;
- if((printed%22) == 0){
- printf("/n> Hit return to continue-");
- read_string(response);
- } /* ends if printed 20 lines */
- printf("\n> [%3d]=%3d=%c=%4d=", i, item_array[i].character,
- item_array[i].indicator,
- item_array[i].count);
- for(k=0; k<CODE_LENGTH; k++)
- printf ("%c", item_array[i].coded[k]);
- for(j=0; j<(ratio*item_array[i].count); j++){
- printf("*");
- } /* ends loop over j */
- } /* ends if count != 0 */
- } /* ends loop over i */
- } /* ends print_item_array */
-
- /*
- convert_long_to_short (...
-
- This function converts the long item_array into
- a shorter file header.
-
- */
-
- convert_long_ to_ short(item_array, file_header)
- struct item_struct item_array[];
- struct header_struct *file_header;
- {
- int i, j, k;
-
- for(i=0; i<LENGTH; i++){
- file_header->items[i].character = item_array[i].character;
-
- for(j=0; J<CODE_LENGTH; j++)
- file_header->items[i].coded[j] = item_array[i].coded[j];
- } /* ends loop over i */
-
- } /* ends convert_long_to_short */
-
- /*
- convert_short_to_long (...
-
- This function converts the short file header into
- a the longer item_array for use in the program.
-
- */
-
- convert_short_to_long (item_array, file_header)
- struct item_struct item_array[];
- struct header_struct *file_header;
- {
- int i, j, k;
-
- for(i=0; i<LENGTH; i++){
- item_array[i].character = file_header->items[i].character;
- for(j=0; J<CODE_LENGTH; j++)
- item_array[i].coded[j] = file_header->items[i].coded[j];
- } /* ends loop over i */
-
- } /* ends convert_short_to_long */
-
- /* End of File */
-
-
- Listing 3 (cujhuff2.c)
- /********************************************
- *
- * file d:\lsu\cujhuff2.c
- *
- ********************************************/
-
- #include "d:\lsu\cujhuff.h"
-
-
- /*
- create_huffman_code(item_array)
-
- This routine is the top of the routines that create
- the codes. Create the codes xxxxx010 for the characters
- read in. You use the item_array which contains the
- counts of occurances and so on.
- */
-
-
- create_huffman_code (item_array)
- struct item_struct item_array[];
- {
- int counter,
- i,
- not_ended;
- struct item_struct temp;
-
-
- sort_item_array(item_array);
- disable_zero_counts(item_array);
-
- /***********************************
- *
- * The following short loop is the
- * heart of the algorithm. The
- * rest is the implementation detail
- * which is not trivial.
- *
- ************************************/
-
- counter = 0;
- not_ended = 1;
- while(not_ended){
- if( (counter % 50) == 0) printf("\n> Creating code ");
-
- printf(".");
- combine_and_code_2_smallest_items(item_array, ¬_ended);
- sort_item_array(item_array);
- counter++;
- } /* ends while not_ended */
-
- reverse_order_of_coded(item_array);
-
- } /* ends create_huffman_code */
-
-
- /*
- sort_item_array(item_array)
-
- This is a very simple bubble sort algorithm.
- It does use the Microsoft C ability to
- set one struct equal to another. Some
- compilers do not support this.
- */
-
-
- sort_item_array(item_array)
- struct item_struct item_array[];
- {
- int i,
- not_finished,
- swapped;
- struct item_struct temp;
-
- not_finished = 1;
-
- while(not_finished){
- swapped = 0;
- for(i=0; i<LENGTH-1; i++){
- if(item_array[i].count < item_array[i+1].count){
- swapped = 1;
- temp = item_array[i];
- item_array[i] = item_array[i+1];
- item_array[i+1] = temp;
- } /* ends if you need to swap */
- } /* ends loop over i */
- if(swapped == 0)
-
- not_finished = 0;
- } /* ends while not_finished */
-
-
- /* Perform an extra pass through the sort to
- ensure all 'D'isbaled items are below all
- 'E'nabled items in the item_array */
-
- not_finished = 1;
-
- while(not_finished){
- swapped = 0;
- for(i=0; i<LENGTH-1; i++){
- if( (item_array[i].indicator == 'D') &&
- (item_array[i+1].indicator == 'E')){
- swapped = 1;
- temp = item_array[i];
- item_array[i] = item_array[i+1];
- item array[i+1] = temp;
- } /* ends if you need to swap */
- } /* ends loop over i */
- if(swapped == 0)
- not_finished = 0;
- } /* ends while not_finished */
-
-
- } /* ends sort_item_array */
-
-
-
- /*
- disable_zero_counts(item_array)
-
- You do not want to work on the characters
- that were not in the input file so you
- disable them.
- */
-
- disable_zero_counts(item_array)
- struct item_struct item_array[];
- {
- int i;
-
- for(i=0; i<LENGTH; i++){
- if(item_array[i].count == 0)
- item_array[i].indicator = 'D';
- } /* ends loop over i */
- } /* ends disable_zero_counts */
-
- /*
- combine_and_code_2_smallest_items(item_array, not_ended)
-
- This function calls other functions to find the two
- smallest items and then combine or link them.
-
- */
-
- combine_and_code_2_smallest_items(item_array, not_ended)
- struct item_struct item_array[];
-
- int *not_ended;
- {
- char r[80];
- int next_smallest, smallest;
-
-
- find_smallest_item(item_array, &smallest);
- if(smallest <= 0){
- *not_ended = 0;
- }
-
- else{
- next_smallest = smallest;
- find_next_smallest_item(item_array, &next_smallest);
- code_2_smallest_items(item_array, smallest, next_smallest);
- combine_2_smallest_items(item_array, smallest,
- next_smallest);
- }
-
- } /* ends combine_and_code_2_smallest_items */
-
- /*
- find_smallest_item(item_array, smallest)
-
-
- You are working with a sorted item_array
- so you start looking at the bottom of the
- array. You look until you find the first
- 'E'nabled indicator then you stop.
- */
-
- find_smallest_item(item_array, smallest)
- struct item_struct item_array[];
- int *smallest;
-
- {
- int i,
- searching;
-
- *smallest = 0;
- searching = 1;
- i = 255;
-
- while(searching){
- if(item_array[i].indicator == 'E'){
- *smallest = i;
- searching = 0;
- } /* ends if indicator == 'E' */
-
- else{
- i = i-1;
- if(i<0){
- *smallest = -1;
- searching = 0;
- }
- } /* ends else indicator != 'E' */
- } /* ends while searching */
- } /* ends find_smallest_item */
-
-
- /*
- find_next_smallest_item(item_array, next_smallest)
-
- You are working with a sorted item_array
- so you start looking at the smallest item of the
- array. You look until you find the first
- 'E'nabled indicator then you stop.
- */
-
- find_next_smallest_item(item_array, next_smallest)
- struct item_struct item_array[];
- int *next_smallest;
- {
- int i,
- searching;
-
- searching = 1;
- i = *next_smallest-1;
-
- while(searching){
- if(item_array[i].indicator == 'E'){
- *next_smallest = i;
- searching = 0;
- } /* ends if indicator == 'E' */
-
- else{
- i = i-1;
- if(i<0){
- *next_smallest = -1;
- searching = 0;
- }
- } /* ends else indicator != 'E' */
- } /* ends while searching */
- } /* ends find_next_smallest_item */
-
- /*
- combine_2_smallest_items(...
-
- . add the two counts together
- . disable the smallest one
- . link the smallest one to the next smallest one
- */
-
- combine_2_smallest_items(item_array, smallest, next_smallest)
- struct item_struct item_array[];
- int next_smallest, smallest;
- {
- int i, not_finished;
-
- item_array[next_smallest].count = item_array[smallest].count
- +
-
- item_array[next_smallest].count;
-
- item_array[smallest].count = item_array[next_smallest].count;
-
- item_array[smallest].indicator = 'D';
-
- i = 0;
-
- not_finished = 1;
- while(not_finished){
- if(item_array[next_smallest].includes[i] == 256){
- item_array[next_smallest].includes[i] = smallest;
- not_finished = 0;
- }
- else{
- i++;
- if(i > LLENGTH){
- printf("\n\n> Ran out of links\n\n");
-
- exit(1);
- }
- }
- } /* ends while not_finished */
-
- } /* ends combine_2_smallest_items */
-
- /*
- code_2_smallest_items (...
-
-
- The smallest item is coded with a ONE
- The next smallest item is coded with a ZERO
- */
-
-
- code_2_smallest_items(item_array, smallest, next_smallest)
- struct_item struct item_array[];
- int next_smallest, smallest;
- {
-
- code_smallest_item(item_array, smallest);
- code_next_smallest_item(item_array, next_smallest);
-
- } /* code_2_smallest_items */
-
- /*
- code_smallest_item(item_array, smallest)
-
- You must code the item as well as
- all the other items included with it
- on down to the end.
-
- Set the code to ONE.
- */
-
- code_smallest_item(item_array, smallest)
- struct item_struct item_array[];
- short smallest;
- {
-
- int i,
- j,
- k,
- setting;
-
- j = smallest;
- setting = 1;
-
- i = 0;
-
- /* set code ONE */
- while(setting){
- if(item_array[j].coded[i] == OTHER) {
- item_array[j].coded[i] = ONE;
- setting = 0;
- } /* ends if == OTHER */
-
- else
- i++;
- } /* ends while setting */
- /* Recursive calls */
- for(k=0; k<LLENGTH; k++)
- if(item_array[j].includes[k] != 256)
- code_smallest_item(item_array,
- item_array[j].includes[k];
-
- } /* ends code_smallest_item */
-
- /*
- code_next_smallest_item(item_array, smallest)
-
- You must code the item as well as
- all the other items included with it
- on down to the end.
-
- Set the code to ZERO
- */
-
- code_next_smallest_item(item_array, smallest)
- struct item_struct item_array[];
- short smallest;
- {
-
- int i,
- j,
- k,
- setting;
-
- j = smallest;
- setting = 1;
- i = 0;
-
- /* set code ZERO */
-
- while(setting){
- if(item_array[j].coded[i] == OTHER){
- item_array[j].coded[i] = ZERO;
- setting = 0;
- } /* ends if == OTHER */
-
- else
- i++;
- } /* ends while setting */
-
- /* Recursive calls */
- for(k=0; k<LLENGTH; k++)
- if(item_array[j].includes[k] != 256)
-
- code_next_smallest_item(item_array,
- item_array[j].includes[k]);
-
- } /* ends code_next_smallest_item */
-
- /*
- reverse_order_of_coded(item_array)
-
- Now trace backwards
- */
-
- reverse_order_of_coded(item_array)
- struct item_struct item_array[];
- {
- char temp;
- int i, j;
-
- for(i=0; i<LENGTH; i++){
- if(item_array[i].coded[0] != OTHER){
- for(j=0; j<(CODE_LENGTH/2); j++){
- temp = item_array[i].coded[j];
- item_array[i].coded[j] =
- item_array[i].coded [CODE_LENGTH-1-j];
- item_array[i].coded[CODE_LENGTH-1-j] = temp;
- } /* ends loop over j */
- } /* ends if coded[0] != OTHER */
- } /* ends loop over i */
- } /* reverse_order_of coded */
-
- /* End of File */
-
-
- Listing 4 (cujhuff3.c)
- /****************************************************
- *
- * file d:\lsu\cujhuff3.c
- *
- ****************************************************/
-
-
- #include "d:\lsu\cujhuff.h"
-
-
- /*
- code_and_write_output_file(...
-
- Look at each byte in the input file.
- Code each byte using the item_array.coded.
- When the output buffer (packed bits) is on a byte
- boundary, then write it out to the output file
- */
-
-
- code_and_write_output_file(item_array, in_file name,
- out_file_name, file_header)
- char in_file_name[],
- out_file_name[];
- struct item_struct item_array[];
- struct header_struct *file_header;
-
- {
- char in_buffer[IB_LENGTH],
- out_buffer[OB_LENGTH],
- r[80];
-
- int coding,
- counter,
- in_file_desc,
- j,
- not_end_of_file,
- out_file_desc;
-
- long bytes_read,
- bytes_written,
- i,
- in_counter,
- in_file_displacement,
- out_counter;
-
-
- open_files(in_file_name, out_file_name,
- &in_file_desc, &out_file_desc);
-
- lseek(in_file_desc, 0L, 0);
- file_header->in_file-length = lseek(in_file_desc, 0L, 2);
- lseek(in_file_desc, 0L, 0);
-
- output_file_header(file_header, out_file_desc);
-
- clear_input_buffer(in_buffer);
- clear_output_buffer(out_buffer);
-
- in_counter = 0;
- out_counter = 0;
- in_file_displacement = 0;
- not_end_of_file = 1;
- counter = 0;
-
- while(not_end_of_file){
-
- position_in_file_displacement(in_file_desc,
- in_file_displacement);
-
- bytes_read = my_read(in_file_desc, in_buffer, IB_LENGTH);
- /*printf("\n\t\tread %d bytes", bytes_read);*/
-
- if(bytes_read < IB_LENGTH)
- not_end_of_file = 0;
-
- i = 0;
- coding = 1;
-
- while(coding){
-
- if((counter % 100) == 0)
- printf("\n> Coding - counter=%d\n", counter);
- if((counter % 10) == 0)
- printf(".");
- counter++;
-
- code_byte(item_array, i, in_buffer, out_buffer,
- &in_counter, &out_counter);
- i++;
- /*printf("\n\n> in_counter=%ld out_counter=%ld\n",
- in_counter, out_counter);*/
-
- /*************************************
- *
- * The rest of this function looks
- * at the output buffer and writes it
- * out when the buffer is on a byte
- * boundary.
- *
- *************************************/
-
- if( (out_counter/8 >= 100) &&
- (out_counter % 8 == 0) ){
- printf("\n> Writing to output file");
- /*printf("\n\t> out count = %d", out_counter/8);*/
- write_output(out_file_desc, out_buffer,
- out_counter/8);
- out_counter = 0;
- clear_output_buffer(out_buffer);
- } /* ends if 100 bytes in out_buffer and on a byte
- boundary */
-
- if(i == bytes_read){
- in_file_displacement = in_file_displacement + i;
- coding = 0;
- } /* ends if the in_buffer is empty */
-
- } /* ends while coding */
-
- } /* ends while not_end_of_file */
-
- printf("\n> Writing to output file");
- write_output(out_file_desc, out_buffer, out_counter/8);
-
-
- close(in_file_desc);
- close(out_file_desc);
-
- } /* ends code_and write_output_file */
-
- /*
- open_files(...
-
- Open the input and output file.
- */
-
- open_files(in_file_name, out_file_name, in_file_desc,
- out_file_desc)
- char in_file_name[], out_file_name[];
- int *in_file_desc, *out_file_desc;
- {
- int a ,b;
-
- *out_file_desc = open(out_file_name, 0_RDWR 0_CREAT 0_BINARY,
- S_IWRITE);
-
- *in_file_desc = open(in_file_name, 0_RDWR 0_CREAT 0_BINARY,
- S_IWRITE);
-
- } /* ends open_files */
-
- /*
- output_file_header(...
-
- This function outputs the short file header
- to the beginning of the compressed file.
-
- */
-
-
- output_file_header(file_header, out_file_desc)
- int out_file_desc;
- struct header_struct *file_header;
- {
- int i;
-
- char out_buffer[sizeof(struct header_struct)],
- *charptr,
- name[80];
-
- charptr = (char *)file_header;
- for(i=0; i<((sizeof(struct header_struct))); i++)
- out_buffer[i] = *charptr++;
-
- write_output(out_file_desc, out_buffer,
- sizeof(struct header_struct));
-
- } /* ends output_item_array */
-
-
- /*
- clear_input_buffer(in_buffer)
-
- This clears out the input buffer.
- */
-
- clear_input_buffer(in_buffer)
- char in_buffer[];
- {
- int i;
- for(i=0; i<IB_LENGTH; i++)
- in_buffer[i] = ' ';
- } /* ends clear_in_buffer */
-
-
- /*
- clear_output_buffer(out_buffer)
-
- This clears out the output buffer.
- */
-
- clear_output_buffer(out_buffer)
- char out_buffer[];
- {
- int i;
-
- for(i=0; i<0B_LENGTH; i++)
- out_buffer[i] = 0x00;
- } /* ends clear_out_buffer */
-
-
- /*
- position_in_file_displacement(...
-
- This sets the pointer to the input file
- to the desired located specificied by
- in_file_discplacement.
- */
-
- position_in_file_displacement(in_file_desc, in_file_displacement)
- int in_file_desc;
- long in_file_displacement;
- {
- long position;
- position = lseek(in_file_desc, 0L, 0);
- position = lseek(in_file_desc, in_file_displacement, 0);
-
- } /* ends position_in_file_displacement */
- /*
- code_byte(...
-
- This function looks at the input file byte and
- sets the bits in the output buffer.
- */
-
- code_byte(item_array, byte, in_buffer, out_buffer, in_counter,
- out_counter)
- char in_buffer[], out_buffer[];
- long byte;
- long *in_counter, *out_counter;
- struct item_struct item_array[];
- {
- char out_code[CODE_LENGTH];
- int i;
-
- find_output_code(item_array, out_code, in_buffer[byte]);
- *in_counter = *in_counter + 1;
-
- /*****************************************
- *
- * Set the output code to either ONE or
- * ZERO.
- *
- ******************************************/
-
- for(i=0; i<CODE_LENGTH; i++){
- if(out_code[i] == ONE){
- set_bit_to_1(out_counter, out_buffer);
- *out_counter = *out_counter + 1;
- } /* ends if out_code == ONE */
-
- if(out_code[i] == ZERO){
- clear_bit_to_0(out_counter, out_buffer);
- *out_counter = *out_counter + 1;
- } /* ends if out_code == ZERO */
-
- } /* ends loop over i */
- } /* ends code_byte */
-
-
- /*
-
- find_output_code(...
-
- Search through the item_array to find the correct
- character and its code.
- */
-
-
- find_output_code(item_array, out_code, in_character)
- struct item_struct item_array[];
- char out_code[];
- char in_character;
- {
- int i, j, searching;
-
- i = 0;
- searching = 1;
- while(searching){
- if(item_array[i].character == in_character){
- searching = 0;
- for(j=0; j<CODE_LENGTH; j++){
- out_code[j] = item_array[i].coded[j];
- } /* ends loop over j */
- } /* ends if there is a match */
- else
- i++;
- } /* ends while searching */
- } /* ends find_output_code */
-
-
- /*
- set_bit_to_1(out_counter, out_buffer)
-
- This function sets the specified bit in the output
-
- buffer to a 1.
- */
-
-
- set_bit_to_1(out_counter, out_buffer)
- long *out_counter;
- char out_buffer[];
- {
- int bit_in_byte,
- byte_in_buffer;
- char temp;
- bit_in_byte = *out counter % 8;
- byte_in_buffer = *out_counter / 8;
- switch(bit_in_byte){
- case 0:
- temp = out_buffer[byte_in_buffer] SET_BIT_ZERO;
- break;
- case 1:
- temp = out_buffer[byte_in_buffer] SET_BIT_ONE;
-
- break;
- case 2:
- temp = out_buffer[byte_in_buffer] SET_BIT_TWO;
- break;
- case 3:
- temp = out_buffer[byte_in_buffer] SET_BIT_THREE;
- break;
- case 4:
- temp = out_buffer[byte_in_buffer] SET_BIT_FOUR;
- break;
- case 5:
- temp = out_buffer[byte_in_buffer] SET_BIT_FIVE;
- break;
- case 6:
- temp = out_buffer[byte_in_buffer] SET_BIT_SIX;
- break;
- case 7:
- temp = out_buffer[byte_in_buffer] SET_BIT_SEVEN;
- break;
- } /* ends switch */
- out_buffer[byte_in_buffer] = temp;
- } /* ends set_bit_to_1 */
-
-
- /*
- clear_bit_to_0(out_counter, out_buffer)
-
- This function sets the specified bit in the
- output buffer to 0.
- */
-
-
- clear_bit_to_0(out_counter, out_buffer)
- long *out_counter;
- char out_buffer[];
- {
- int bit_in_byte,
- byte_in_buffer;
- char temp;
-
- bit_in_byte = *out_counter % 8;
- byte_in_buffer = *out_counter / 8;
-
- switch(bit_in_byte){
- case 0:
- temp = out_buffer[byte_in_buffer] & CLEAR_BIT_ZERO;
- break;
- case 1:
- temp = out_buffer[byte_in_buffer] & CLEAR_BIT_ONE;
- break;
- case 2:
- temp = out_buffer[byte_in_buffer] & CLEAR_BIT_TWO;
- break;
- case 3:
- temp = out_buffer[byte_in_buffer] & CLEAR_BIT_THREE;
- break;
- case 4:
- temp = out_buffer[byte_in_buffer] & CLEAR_BIT_FOUR;
- break;
-
- case 5:
- temp = out_buffer[byte_in_buffer] & CLEAR_BIT_FIVE;
- break;
- case 6:
- temp = out_buffer[byte_in_buffer] & CLEAR_BIT_SIX;
- break;
- case 7:
- temp = out_buffer[byte_in_buffer] & CLEAR_BIT_SEVEN;
- break;
- } /* ends switch */
- out_buffer[byte_in_buffer] = temp;
- } /* ends clear_bit_to_0 */
-
- /*
- write_output(...
-
- This function writes the output buffer to the
- output file.
- */
-
- write_output(out_file desc, out_buffer, number_of_bytes)
- char out_buffer[];
- int out_file_desc;
- long number_of_bytes;
- {
- int bytes_written;
-
- bytes_written = write(out_file_desc, out_buffer,
- number_of_bytes);
- /*printf("\n> wrote %d bytes", bytes_written);*/
-
- } /* ends write_output */
-
- /* End of File */
-
-
- Listing 5 (cujhuff4.c)
- /****************************************************
- *
- * file d:\lsu\cujhuff4.c
- *
- ***************************************************/
-
- #include "d:\lsu\cujhuff.h"
-
- /*
- decode_compressed_file(...
-
- This is the main function of the decompression
- portion of the program.
-
- */
-
- decode_compressed_file (input_file_name, output_file_name,
- item_array, file_header)
- char input_file_name[], output_file_name[];
- struct item_struct item_array[];
- struct header_struct *file_header;
- {
-
- char r[80];
- int in_file_desc,
- out_file_desc;
-
- open_files_for_decode(input_file_name, output_file_name,
- &in_file_desc, &out_file_desc};
-
- input_file_header(file_header, in_file_desc);
- convert_short_to_long(item_array, file_header);
-
- decode_file_and_write_output (item_array,
- in_file_desc,
- out_file_desc);
-
- close_decode_files(in_file_desc, out_file_desc);
-
- } /* ends decode_compressed_file */
-
- /*
- open_files_for_decode(...
-
- Open the input end output file.
- */
-
- open_files_for_decode(in_file_name, out_file_name,
- in_file_desc, out_file_desc)
- char in_file_name[], out_file_name[],
- int *in_file_desc, *out_file_desc;
- {
- int a ,b;
-
- *out_file_desc = open(out_file_name, 0_RDWR 0_CREAT 0_BINARY,
- S_IWRITE);
- *in_file_desc = open(in_file_name, 0_RDWR 0_CREAT 0_BINARY,
- S_IWRITE);
-
- } /* ends open_files_for_decode */
-
- /*
- input_file_header( file_header, in_file_desc)
-
- This function reads in the short header from the
- front of the input file.
- */
-
-
- input_file_header( file_header, in_file_desc)
- int in_file_desc;
- struct header_struct *file_header;
- {
- int bytes_read,
- i;
-
- char in_buffer[(sizeof(struct header_struct))],
- *charptr,
- name[80];
-
- long position;
-
-
-
- position = lseek(in_file_desc, 0L, 0);
- bytes_read = my_read(in_file_desc, in_buffer,
- sizeof(struct header_struct));
- /*printf("\n\n> Read %d bytes using file header", bytes_read);*/
-
- charptr = (char *)file_header;
- for(i=0; i<((sizeof(struct header_struct))); i++)
- *charptr++ = in_buffer[i];
-
- } /* ends input_item_array */
-
- /*
-
- decode_file_and write_output(...
-
- This function takes in the packed bits, decodes them,
- and puts the decoded characters out to the output
- file.
-
- /*
-
- decode_file_and_write_output(item_array, in_file_desc, out_file_desc)
- int in_file_desc, out_file_desc;
- struct item_struct item_array[];
- {
- char output_buffer[IB_LENGTH],
- r[80],
- stream_buffer[CODE_LENGTH],
- stream_of_bits[OB_LENGTH];
-
- int bytes_read,
- counter
- i,
- in_disp,
- not_finished,
- output_pointer,
- stream_pointer;
- counter = 0;
- output_pointer = 0;
- stream_pointer = 0;
- bytes_read = 0;
- not_finished = 1;
-
- for(i=0; i<IB_LENGTH; i++)
- output_buffer[i] = OTHER;
-
- while(not_finished){
-
- read_in_buffer(in_file_desc, in_disp,
- stream_of_bits, &bytes_read);
-
- /*printf("\n> read %d bytes into stream of bits", bytes_read);*/
- stream_pointer = 0;
-
- if(bytes_read < OB_LENGTH)
- not_finished = 0;
-
- /* work through the stream_of_bits
-
- you're finished when the stream_pointer
- equals bytes_read * 8 bits per byte. */
-
- while(stream_pointer < bytes_read*8){
- convert_bits_to_char(stream_of_bits, stream_pointer,
- stream_buffer);
-
- decode_bits(stream_buffer, item_array,
- output_buffer, &output_pointer,
- &stream_pointer);
-
- /*printf("\n> stream pointer = %d", stream_pointer);*/
- /* if output_buffer fills up write it to disk */
- if(output_pointer >= IB_LENGHT-10){
- counter++;
- printf("\n> Writing to output file-%d", counter);
- write_out_buffer(out_file_desc,
- output_buffer,
- output_pointer);
- output_pointer = 0;
- } /* ends if output pointer is too big */
- } /* ends while stream_pointer < bytes_read*8 */
-
- } /* ends while not_finished */
-
- if(output_pointer > 0) {
- printf ("\n> Writing to output file");
- write_out_buffer(out_file_desc, output_buffer, output_pointer);
- }
-
- } /* ends decode_file_and_write_output */
-
-
- /*
- read_in_buffer(...
-
- This function reads the input file and puts the packed
- bits into the stream_of_bits.
- */
-
- read_in_buffer(in_file_desc, in_disp, stream_of_bits,
- bytes_read)
- int *bytes_read, in_file_desc, in_disp;
- char stream_of_bits[];
- {
- int b;
-
- for(b=0; b<OB_LENGTH; b++)
- stream_of_bits[b] = 0x00;
-
- *bytes_read = read(in_file_desc, stream_of_bits, OB_LENGTH);
-
- } /* ends read_in_buffer */
-
-
- /*
- convert_bits_to_char(...
-
- This function takes the next CODE_LENGTH # of bits
-
- from the stream_of_bits and converts them to bytes
- having the value ONE or ZERO. These bytes are placed
- into the stream_buffer. This makes it easier for
- the decode bits function to compare coded with the
- packed bits.
- */
-
-
- convert_bits_to_char(stream_of_bits, stream_pointer, stream_buffer)
- char stream_of_bits[], stream_buffer[];
- int stream_pointer;
- {
- char temp;
-
- int bit_in_byte,
- byte_in_buffer,
- i,
- j;
-
- for(i=0; i<CODE_LENGTH; i++)
- stream_buffer[i] = OTHER;
-
- j = -1:
-
- for(i=stream_pointer; i<stream_pointer+CODE_LENGTH; i++){
-
- j++;
- bit_in_byte = i % 8;
- byte_in_buffer = i/8;
-
- /* Test the bit by shifting it to the left
- by the number bit_in_byte and then ANDing
- it with 1000 0000 (128).
- For this routine bit zero is the left most
- or most significant bit of temp */
-
- temp = stream_of_bits[byte_in buffer];
- temp = temp << bit_in_byte;
- if((temp & SET_BIT_ZERO) != 0)
- stream_buffer[j] = ONE;
- else
- stream_buffer[j] = ZERO;
-
- } /* ends loop over i */
-
- } /* ends convert_bits_to_char */
-
- /*
- decode_bits(...
-
- Look at the converted bits from the packed file
- compare them to the item_array.coded (start looking
- at the top of the item_array since those occur most
- frequently) and put the original character in the
- output buffer
- */
-
- decode_bits(stream_buffer, item_array,
- output_buffer, output_pointer,
-
- stream_pointer)
- char output_buffer[], stream_buffer[];
- int *output_pointer, *stream_pointer;
- struct item_struct item_array[];
- {
- int found,
- j,
- i,
- length_of_code,
- not_finished;
-
- char compare_string[CODE_LENGTH], r[80];
-
- found = 0;
- not_finished = 1;
- j = 0;
-
- /* search through the item_array looking for a coded array that
- matches the characters in the stream_buffer
- If you find it, increase the stream_pointer by
- adding length_of_code to it. */
-
- while(not_finished){
-
- extract_code(item_array[j].coded, compare_string, &length_of_code);
-
- if(strncmp (compare_string,
- stream_buffer,
- length_of_code) == 0){
- found = 1;
- not_finished = 0;
- *stream_pointer = *stream_pointer + length_of_code;
- } /* ends if strncmp */
-
- else{ /* did not find the code so keep looking */
- j++;
- if(j > LENGTH){
- not_finished = 0;
- printf("\n\n\t> FAILURE - did not find code\n\n");
- }
- } /* ends else */
-
- } /* ends while not_finished */
-
- /* now put the decoded character in to the output_buffer */
-
- if(found == 1){
- output_buffer[*output_pointer] = item_array[j].character;
- *output_pointer = *output_pointer + 1;
- } /* ends if found == 1 */
-
- } /* ends decode_bits */
-
- /*
- extract_code(...
-
- Pull the code out of item_array[x].coded,
- put it into compare_string, and put the
- length of the code into length.
-
- */
-
- extract_code(coded, compare_string, length_of_code}
- char coded[], compare_string[];
- int *length_of_code;
- {
- int i, j, k, m, not_finished;
-
- /* clear the compare string */
- for(k=0; k<CODE LENGTH; k++)
- compare_string[k] = OTHER;
-
- not_finished = 1;
- *length_of_code = 0;
- i = CODE_LENGTH-1;
- j = 0;
-
- /* Start at the end of coded and search back until you
- run out of code i.e. you hit a OTHER. */
-
- while(not_finished){
- if(coded[i] != OTHER){
- i-;
- j++;
- }
- else
- not_finished = 0;
- } /* ends while not_finished */
-
- /* adjust value of i */
- i++;
-
- /* Copy the coded to the compare_string */
- *length_of_code = j;
- m = 0;
- for(k=i; k<CODE_LENGTH; k++){
- compare_string[m] = coded[k];
- m++;
- }
-
- } /* ends extract_code */
-
- /*
- write_out_buffer(...
-
- Write the output_buffer to file.
- */
-
- write_out_buffer(out_file_desc, output_buffer, output_pointer)
- char output_buffer[];
- int out_file_desc, output_pointer;
- {
- int bytes_written;
-
- bytes_written =
- my_write(out_file_desc, output_buffer, output_pointer);
- } /* ends write_out_buffer */
-
-
-
- /*
- close_decode_files(...
-
- Close the files.
- */
-
- close_decode_files(in_file_desc, out_file_desc)
- int in_file_desc, out_file_desc;
- {
-
- close(in_file_desc);
- close (out_file_desc);
-
- } /* ends close_decode_files */
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Arithmetic In Factorial-Base
-
-
- Frederick W. Hegeman
-
-
- Frederick Hegeman is an amateur programmer and computer language hobbyist. He
- can be reached at P.O. Box 2368, Rapid City, SD 57709, telephone (605)
- 343-7014.
-
-
- C takes a machine-oriented approach to numbers; its arithmetic types are
- coupled to the physical architecture of the processor. Arithmetic operations
- on ints, longs, and doubles use a compact representation and provide fast and
- well-defined performance. The cost is limits on the magnitude and accuracy of
- operands and results.
- Other languages take a less mechanical approach. Common Lisp supports bignums,
- integral values limited only by available memory, and ratios, rational values
- which allow exact computations by storing separate integral valued numerators
- and denominators (which can be bignums, of course).
- For C applications that need to operate beyond the restraints imposed by
- float.h and limits.h, similar types could be coded, but doing so would be a
- nightmare. What kind of code would be required to compare
- (4129181559/4294967296) to (449/467)? To multiply or divide them? Even worse,
- memory usage could grow without limit, up to the point where the program
- fails.
- Happily, there is a more practical approach that works within bounded memory
- and almost allows computation to unlimited magnitude and perfect accuracy.
-
-
- Factorial-Base Arithmetic
-
-
- Any rational number can be represented exactly in a number system based, not
- on increasing powers of a particular number (e.g., 100,101, 102, ...), but on
- the series of factorial numbers, (1!, 2!, 3!, ... ). Any integer in any
- static-base system can also be represented in factorial-base.
- The decimal integer 231 can be represented as 2x102+3x101+1x100 or as
- 1x5!+4x4!+2x3!+1x2!+1x1!. And any rational fraction has an exact
- representation in factorial-base, even if infinitely repeating in a
- static-base.
- Click Here for Equation
- Whatever numbers a computer manipulates must be rational numbers. If I type in
- 3.1415926535897932384, I am not typing in p, but a rational approximation. I
- can't calculate the square root of 2; I can calculate a rational number than
- approximates it. Every operand that can be used, and every result that can be
- returned, is a rational number. I find it ironic that Georg Cantor, who
- discovered those computer incompatible values, the transfinite number, was
- also responsible for factorial-base representation. In Cantor's scheme every
- number that can be computed by machine can, given the resources, be computed
- exactly.
- The limiting resources of the factorial-base system are memory and the
- implementation language. The language is the most important factor. Time, in
- theory, is not a factor. Memory is the limiting factor only for very small
- systems such as micro controllers. In C, the limiting factor is related to the
- integer square root of LONG_MAX. Because ANSI conforming compilers are allowed
- generous amounts of slack, the limiting value may be one less than the
- quotient of INT_MAX divided by sizeof(long). For a subset without longs, the
- limits are set by the integer square root of INT_MAX.
-
-
- Representing Factorial-Base Numbers In C
-
-
- Factorial-base numbers have a natural representation in C as arrays of some
- signed integral type. Consider the representation of decimal 231 given
- previously. The value of each factorial place is an integer to be stored in a
- separate element of the array. Arrange things so that references begin at
- array[1], rather than at array[0], and the subscripts track the factorial
- places
- int factorial_integer[6] = {0,1,1,2,4,1};
- Likewise for the ratio 1/3
- int factorial_fraction[4] = {0,0,0,2};
- To represent the real number 231 + 1/3, some scheme is needed to keep track of
- both parts. Declaring a struct with members to hold the separate arrays does
- so in a straightforward manner. However, since many subset compilers, and
- cross compilers, do not implement structs and since the idea of a tiny
- controller limited to eight-bit "words" calculating to the limits of 10! and
- 1/10! has a certain charm, I chose to implement both parts in a single array,
- as illustrated in Figure 1.
- The numbers are represented in a sign and magnitude scheme. The equivalent of
- 2's complement simply does not exist and since the subscript 0 is free, it
- holds the sign. The factorial-base integer follows. Each element of the
- integer portion will hold a maximum value equal to its subscript. If it ever
- happens that the value exceeds the subscript, the number can be rearranged so
- that it does not. Consider array element 2, which represents 2!. The maximum
- entry it should hold is 2. The value of 2 at subscript 2 is 2*2!. Suppose the
- entry at element 2 was 3 -- the value of 3 at subscript 2 would be 3*2!. That
- is the definition of 3!, and the number can be normalized by representing it
- in terms of 3!. Note that it is not necessary to actually calculate the value
- of 3!.
- Consider that the subscript i references the ith factorial "digit". What is
- the maximum value for i? Suppose the number is represented as an array of
- ints. The element i may have to temporarily contain the value of (i+1)*i
- before being normalized. This value must be representable as a positive int,
- and must not be allowed to suddenly turn negative, in order to propagate a
- carry value into the guard, G, and mark integer overflow. Since we are using
- ints, (i+1)*i cannot exceed INT_MAX. Assume INT_MAX is 32767 and the maximum
- value of i is 180, or 1 less than the integer square root of INT_MAX.
- The rest of the array contains the factorial-base fraction. Consider it as a
- separate array beginning at &array[G]. The value of factorial place 1, at
- subscript G+1, is some integer x/1!, which is the same as x*1!, which is the
- same as x. This subscript is referenced to propagate carrys and borrows from
- the fraction part to the integer part during normalization. Otherwise, the
- array value at subscript G+1 is 0.
- The remaining array elements represent the fraction proper. Each element will
- hold a maximum value equal to 1 less than its subscript relative to G. The
- maximum subscript (relative to G) is 1 less than the integer square root of
- INT_MAX. Underflow, or loss of significance, spills into subscript G2.
- Now, assume the number is represented an array of longs and LONG_MAX is
- 2147483647. Most applications will not use up 46,339 factorial places. ANSI
- conforming compilers need only provide objects, including arrays, of up to
- 32,767 bytes and since each array element uses 4 bytes, arrays of at least
- 8,191 longs are presumably available.
-
-
- facbase.c
-
-
- Procedures and declarations for factorial-base numbers, the operations +, --,
- /, *, conversion to and from ASCII decimal, and support functions, are in
- Listing 1. Since that code contains only simple macros and no structs, unions,
- longs, or pointers to anything more exotic than chars and ints, it should
- compile under even the most rudimentary systems.
- Only two things might lead to trouble on a system. First, some subset
- compilers may not allow compile-time initialization of arrays and variables.
- In that case, a short routine will have to be written to do runtime
- initialization of the integer variable nowarning and the constant arrays
- zero[] and one[]. The second problem may be lack of stack space. Some of the
- routines can use prodigious amounts of stack. On systems that allot a fixed
- amount of memory for use as a program stack, it may be necessary to declare
- all or most of the temporary numbers as static arrays, rather than the default
- to auto.
- As described above, either integer or fractional parts can be adjusted freely
- up to about 180 factorial digits. The system is written for 100 factorial
- digits in both integer and fractional parts for the simple reason that the
- values of factorials in that range can be checked against the tables in the
- Chemical Rubber Co. Standard Mathematical Tables.
- If some of the code looks oddly familiar, it is because operations on
- factorial-base numbers built around arrays are almost exactly the same as
- binary operations on arrays of bits. Shifting left and right are different
- because they are done by overt multiplication and division, while binary
- operations shift left and right in order to multiply and divide quickly. The
- only routine that might seem totally unfamiliar is the normalization routine
- that cleans up after operations. The software must mimic normalization usually
- performed in silicon.
- In the absence of overflow or underflow, mathematic operations in
- factorial-base are exact. No rounding or truncation need ever be performed
- except when converting to a static-base on output. This code converts to
- decimal. To the limits of the system, the maximum error of any computation, no
- matter how large or how small the result, need never be greater than ± .5
- units in the last place displayed. The actual results can always be exact and
- no errors propagate through a series of calculations. (Rounding to nearest, as
- that implies, might not be as appropriate as rounding towards zero or always
- rounding towards ± x. Note that the code provided truncates, rather than
- rounds.)
- Included at the end of Listing 1 is code for a simple and slow RPN calculator
- program, which compiles when DEBUG is #defined. The calculator includes a
- routine to display a factorial-base number in factorial-base. Factorial-base
- integers are well-behaved but factorial-base fractions are confusing unless
- viewed as separate digits. Consider the infinite series for e: [IMG =
- 9202F1QY.PCX] The series can be coded directly into an array, normalized, and
- printed out. If the fraction is 100 factorial digits, it will print out
- correctly to 159 decimal places. On the other hand, .000 000 000 000 000 000
- 000 000 1 will underflow; it only has an exact representation in 105 factorial
- digits. Note, though, how it prints in decimal after underflow. If the
- fraction is 180 factorial digits, the value of e prints out correctly to 333
- decimal places, 1.0E--44 requires all 180 factorial digits and 1.0E--45
- underflows.
-
-
- Conclusion
-
-
- The factorial-base arithmetic is not practical for recalculation of a
- spreadsheet. But, for applications where accuracy is more important that
- speed, factorial-base arithmetic may be the answer. For programs that need to
- deal in exact fractions, factorial-base arithmetic is probably a good choice.
- Suppose you want both fast and accurate arithmetic. You have what you think
- are fast and simple algorithms that perform calculations correct to 20 decimal
- places. Or do they? How do you verify that? How do you debug your code? Don't
- dismiss factorial-base math out of hand as too slow for practical
- applications.
-
-
-
- Bibliography
-
-
- Wayner, Peter. "Error-Free Fractions," BYTE, Vol. 13, No. 6, June, 1988, pp.
- 289-298. Presents a limited implementation in Pascal and cites a submission to
- CACM that has not yet appeared.
- The Chemical Rubber Co., Standard Mathematical Tables, 18th ed., Cleveland,
- OH, 1970. Later editions have slimmed down, but earlier ones like this contain
- exact values for factorials up to 20!
- Wozniak, Stephen. "The Impossible Dream: Computing e to 116,000 Places with a
- Personal Computer," BYTE, Vol. 6, No. 6, June, 1981, Pp. 392-407. Where else
- can you find the value of e printed out to 1,500 decimal places?
- Figure 1 A Number in Factorial-Base Expressed as an Array
- <-- integer part -> <- fraction part ->
- +----+----+----+ +----+----+----+----+ +----+----+
- 1! 2! .. i! 1! 2! .. f!
- +----+----+----+ +----+----+----+----+ +----+----+
- 0 1 2 i G G+1 G+2 G+f G2
- [0] The sign.
- [1] An integer portion of i factorial-base digits.
- The value of [n] ranges from 0 to n, The value
- [i] of the integer portion is ?*1! + ?*2! + ... + ?*i!
- [G] A guard to detect integer overflow.
- [G+1] Records carrys & borrows during normalization.
- [G+2] A fractional portion of f-1 factorial-base digits.
- The value of [n] ranges from 0 to n-1. The value
- [G+f] of the fraction is ?/2! + ?/3! + ... + ?/f!
- [G2] A guard to detect fractional underflow.
-
- Listing 1 (facbase.c) Arithmetic in Factorial-Base
- #include <stdio.h>
-
- #ifndef TRUE
- #define TRUE 1
- #endif
- #ifndef FALSE
- #define FALSE 0
- #endif
-
- #define DEBUG TRUE /* include RPN calculator */
- #define PLUS FALSE /* sign values */
- #define MINUS TRUE
- #define MAXFBINTEGER 100 /* <= 180 for arrays of ints */
- #define MAXFBFRACTION 100 /* <= 180 for arrays of ints */
- #define HIGHGUARD MAXFBINTEGER + 1
- #define LOWGUARD MAXFBFRACTION + 1
- #define ARRAYSIZE 1 + HIGHGUARD + LOWGUARD
-
- /* two constants in the proper format */
- int zero[ARRAYSIZE] = { PLUS, 0 }; /* signed 0 */
- int one[ARRAYSIZE] = { PLUS, 1, 0 };
-
- /* a flag set by divide() while "estimating" */
- int nowarning = FALSE;
- /* assign the value of one array to a second array */
- assignto(dest, source)
- int *dest;
- int *source;
- {
- int i;
-
- for(i = 0; i < ARRAYSIZE; i++)
- dest[i] = source[i];
- {
-
- /* z = abs(x) */
- absolute(x, z)
- int *x;
- int *z;
- {
- if(z != x) /* if not changing in place */
- assignto(z, x); /* copy */
- z[0] = PLUS; /* force positive */
- }
-
- /* Assign unary negative of x to z */
- negative(x, z)
- int *x;
- int *z;
- {
- int sign;
-
- sign = (x[0] == PLUS) ? MINUS : PLUS;
- if(z != x) /*if not changing in place */
- assignto(z, x); /* copy */
- z[0] = sign; /* change sign */
- }
-
- /* compare 2 factorial-base numbers for x > y */
- int greaterthan(x, y)
- int *x;
- int *y;
- {
- int i, j;
-
- /*
- * check the integer part first,
- * largest subscript to smallest
- */
- for(i = MAXFBINTEGER; (x[i] == y[i]) && (i > 1); -i)
- ;
- if(i != 0)
- {
- if(x[i] > y[i])
- return(TRUE);
- if(x[i] < y[i])
- return(FALSE);
- }
- /*
- * The integer portions are equal,
- * continue with the fractional part,
- * smallest subscript to largest.
- */
- for(i = HIGHGUARD + 1, j = 1;
- (x[i] == y[i]) && (j < MAXFBFRACTION - 1);
- i++, j++)
- ;
- return((x[i] > y[i]) ? TRUE : FALSE);
- }
-
- /* Compare 2 factorial-base numbers for x < y */
- int lessthan(x, y)
- int *x;
- int *y;
-
- {
- int i, j;
-
- /*
- * check the integer part first,
- * largest subscript to smallest
- */
- for(i = MAXFBINTEGER; (x[i] == y[i]) && (i > 1); -i)
- ;
- if(i != 0)
- {
- if(x[i] < y[i])
- return (TRUE);
- if(x[i] > y[i])
- return(FALSE);
- }
- /*
- * The integer portions are equal,
- * continue with the fractional part,
- * smallest subscript to largest.
- */
- for(i = HIGHGUARD + 1, j = 1;
- (x[i] == y[i]) && (j < MAXFBFRACTION - 1);
- i++, j++)
- ;
- return((x[i] < y[i]) ? TRUE : FALSE);
- }
-
- /* Compare 2 factorial-base numbers for x == y */
- int equalto(x, y)
- int *x;
- int *y;
- {
- int i;
-
- /*
- * Check the whole array except the sign
- * - order doesn't matter.
- */
- for(i = ARRAYSIZE-1; (x[i] == y[i]) && (i > 1); -i)
- ;
- return((x[i] == y[i]) ? TRUE : FALSE);
- }
-
- /*
- * "Normalize" a factorial-base number. All of the
- * arithmetic functions call this routine to handle
- * carrys and borrows. A factorial-base number has a
- * proper form where every factorial position in its
- * integer part has a value between 0 and the the
- * magnitude of its position and every factorial position
- * in its fractional part has a value between 0 and one
- * less than the magnitude of its position.
- */
- normalize(n)
- int *n;
- {
- int i, j;
- int *x;
-
-
- x = &n[HIGHGUARD];
- /*
- * First, check for loss of precision
- * during multiplication or division.
- */
- if(x[LOWGUARD])
- {
- if(nowarning != TRUE)
- fputs("UNDERFLOW\n", stderr);
- x[LOWGUARD] = 0;
- }
- /*
- * Now, work the fractional part first,
- * largest subscript to smallest.
- * The subscript j is the factorial position
- * being put into proper form.
- */
- for(j = MAXFBFRACTION, i = j - 1; i >= 1; -i, -j)
- {
- /* if(x[j] >= j) carry */
- x[i] += (x[j] / j);
- x[j] %=j;
- if(x[j] < 0) /* borrow */
- {
- x[i] -= 1;
- /* modulo= j */
- x[j] += j; /* make positive */
- }
- }
- /* shift any carry to integer part & clear carry */
- n[1] += x[1];
- x[1] = 0;
- /*
- * Now, normalize the integer part,
- * working from smallest subscript to largest.
- * The subscript i is the factorial position
- * being put into proper form.
- */
- x = n;
- for(i = 1, j = 2; i <= MAXFBINTEGER; i++, j++)
- {
- /* if(x[i] >= j) carry */
- x[j] += (x[i] / j);
- x[i] %= j;
- if(x[i] < 0) /* borrow */
- {
- x[j] -= 1;
- x[i] += j;
- }
- }
- if(x[i]) /* if an entry in x[HIGHGUARD] */
- {
- fputs("OVERFLOW\n", stderr);
- x[i] = 0;
- }
- }
-
- /* Add y to x, put result in z */
-
- add(x, y, z)
- int *x;
- int *y;
- int *z;
- {
- int sign, i;
- int copy[ARRAYSIZE];
-
- if(x[0] != y[0]) /* if different signs */
- {
- if(y[0] == MINUS)
- {
- /*
- * Change the sign of y
- * and subtract y from x.
- */
- negative(y, copy);
- subtract(x, copy, z);
- }
- else
- {
- /*
- * Change the sign of x
- * and subtract x from y.
- */
- negative(x, copy);
- subtract(y, copy, z);
- }
- }
- else
- {
- sign = x[0]; /* save the sign */
- for(i = ARRAYSIZE - 1; i > 0; -i)
- z[i] = x[i] + y[i];
- z[0] = sign;
- normalize(z);
- }
- }
-
- /* Subtract y from x, put result in z */
- subtract(x, y, z)
- int *x;
- int *y;
- int *z;
- {
- int sign, i;
- int copy[ARRAYSIZE];
-
- if(x[0] != y[0]) /* if signs are different */
- {
- negative(y, copy); /* change sign of y */
- sign = x[0]; /* save sign of x */
- add(x, copy, z);
- z[0] = sign;
- return;
- }
- else if(y[0] == MINUS) /* (-x) - (-y) */
- {
- /* if(abs(y) < abs(x)) sign = MINUS */
-
- sign = lessthan(y, x);
- }
- else
- {
- /* if(x < y) sign = MINUS */
- sign = lessthan(x, y);
- }
- /* Subtract based on the absolute values */
- if (lessthan(x, y))
- {
- for(i = ARRAYSIZE - 1; i > 0; -i)
- z[i] = y[i] - x[i];
- }
- else
- {
- for(i = ARRAYSIZE - 1; i > 0; -i)
- z[i] = x[i] - y[i];
- }
- z[0] = sign;
- normalize(z);
- }
-
- /*
- * Multiply factorial-base x by integer y, result in z.
- * Utility routine called by multiply(), divide(),
- * atofact(), fractoa(), and facttoa(), and always
- * with a positive value for y
- */
- multfbyi(x, y, z)
- int *x;
- int y;
- int *z;
- {
- int i;
-
- for(i = ARRAYSIZE - 1; i > 0; -i)
- z[i] = x[i] * y;
- normalize(z);
- }
-
- /*
- * Divide factorial-base x by integer y, result in z.
- * Utility routine called by multiply(), divide(),
- * and facttoa(), and always with a positive y
- */
- divfbyi(x, y, z)
- int *x;
- int y;
- int *z;
- {
- int i, j, carry, part;
-
- carry = 0;
- /*
- * Work the integer part first,
- * from the largest subscript to smallest
- */
- for(i = MAXFBINTEGER, j = i + 1; i >= 1; -i, -j)
- {
-
- part = x[i] + carry * j;
- carry = part % y;
- z[i] = part / y;
- }
- /*
- * Now, work the fractional part,
- * from the smallest subscript to largest
- */
- for(i = HIGHGUARD + 1, j = 1;
- j <= MAXFBFRACTION; i++, j++)
- {
- part = x[i] + carry * j;
- carry = part % y;
- z[i] = part / y;
- }
- /*
- * propogate any carry into z[LOWGUARD]
- * to mark underflow and loss of precision
- */
- z[i] = carry * j;
- normalize(z);
- }
-
- /*
- * Multiply factorial-base x by factorial-base y,
- * assign result to z. Uses the identity
- * number * (integer + fraction)
- * == (number * integer) + (number * fraction)
- */
- multiply(x, y, z)
- int *x;
- int *y;
- int *z;
- {
- int i, j, k, sign;
- int partial [ARRAYSIZE];
- int temp[ARRAYSIZE];
- int copy[ARRAYSIZE];
-
- if(x[0] != y[0]) /* if signs different */
- sign = MINUS;
- else
- sign = PLUS;
-
- assignto(partial, zero); /* Initialize result */
- /*
- * Work the integer portion first,
- * from smallest subscript to largest
- */
- absolute(x, copy); /* copy = abs(x) */
- /* first, find largest subscript k where y[k] != 0 */
- for(k = MAXFBINTEGER; (k > 0) && (y[k] == 0); -k)
- ;
- for(i = 1; i <= k; i++)
- {
- /* first shift copy by factorial position */
- multfbyi(copy, i, copy);
- if(y[i]) /* don't bother multiplying by 0 */
- {
-
- /* multiply by factorial digit */
- multfbyi(copy, y[i], temp);
- add(partial, temp, partial);
- }
- }
-
- /* now work fraction part */
- assignto(copy, x); /* reset copy */
- /* find largest subscript k where y[k] != 0 */
- for(k = ARRAYSIZE - 1; (k > HIGHGUARD + 1) && (y[k] == 0); -k)
- ;
- for(i = HIGHGUARD + 2, j = 2; i <= k; i++, j++)
- {
- /* first shift copy by factorial position */
- divfbyi(copy, j, copy);
- if(y[i]) /* don't bother multiplying by zero */
- {
- /* multiply by factorial digit */
- multfbyi(copy, y, temp);
- add(partial, temp, partial);
- }
- }
- partial[0] = sign;
- assignto(z, partial);
- }
- /*
- * Divide factorial-base x by factorial-base y, store
- * result in z. Uses blackboard style long division.
- */
- divide(x, y, z)
- int *x;
- int *y;
- int *z;
- {
- int i, j, sign;
- int estimate;
- int copyx[ARRAYSIZE];
- int copyy[ARRAYSIZE];
- int temp[ARRAYSIZE];
- int partial[ARRAYSIZE];
-
- if(x[0] != y[0]) /* if signs different */
- sign = MINUS;
- else
- sign = PLUS;
- absolute(x, copyx);
- absolute(y, copyy);
- assignto(partial, copyx);
- assignto(temp, copyy);
- /*
- * First, estimate the integer part of result by
- * driving y to 1.xxx. Division is VERY slow, so
- * the extra time spent to identify special cases
- * is well worth it.
- */
- if(equalto(temp, zero))
- {
- /* division by zero fault */
- /* not handled here */
-
- return;
- }
- else if(lessthan(partial, temp))
- {
- assignto(partial, zero); /* integer part 0 */
- }
- else if(lessthan(one, temp))
- {
- /*
- * This could cause a spurious UNDERFLOW
- * message even though the final result
- * would be exact, so we set a flag to
- * suppress the warning.
- */
- nowarning = TRUE;
- while(lessthan(one, temp))
- {
- divfbyi(partial, 2, partial);
- divfbyi(temp, 2, temp);
- }
- multfbyi(partial, 2, partial);
- nowarning = FALSE; /* reset flag */
- }
- else if(lessthan(temp, one))
- {
- while(lessthan(temp, one))
- {
- multfbyi(partial, 2, partial);
- multfbyi(temp, 2, temp);
- }
- }
- else /* division by 1 or -1 */
- {
- assignto(z, x);
- z[0] = sign;
- return;
- }
- /* Now, delete fractional part of estimate */
- for(i = HIGHGUARD + 1; i < ARRAYSIZE; i++)
- partial[i] = 0;
- multiply(copyy, partial, temp);
- while(greaterthan(temp, copyx))
- {
- subtract(partial, one partial);
- subtract(temp, copyy, temp);
- }
- subtract(copyx, temp, copyx);
- multfbyi(copyx, 2, copyx);
- /* partial now holds integer part of result */
-
- /*
- * Now, proceed by long division to divide by
- * the fractional part - using the subscript
- * (less 1) as the estimate at each position
- */
- for(i = HIGHGUARD + 2, j = 2;
- !equalto(copyx, zero) && (i < ARRAYSIZE);
- i++)
- {
-
- estimate = j - 1;
- do {
- multfbyi(copyy, estimate-, temp);
- } while(greaterthan(temp, copyx));
- subtract(copyx, temp, copyx);
- partial[i] = ++estimate;
- multfbyi(copyx, ++j, copyx);
- }
- normalize(partial);
- if(sign == MINUS)
- partial[0] = MINUS;
- assignto(z, partial);
- }
-
- /*
- * ASCII to factorial-base conversion
- * Just like ASCII to binary conversion!
- */
- atofact(s, z)
- char s[];
- int *z;
- }
- int i, j, sign;
- int ipart[ARRAYSIZE];
- int fpart[ARRAYSIZE];
-
- assignto(ipart, zero);
- assignto(fpart, zero);
- i = 0;
- sign = PLUS;
- while(s[i] == ' ' s[i] == '\t')
- i++;
- if(s[i] == '-' s[i] == '+')
- {
- if(s[i++] == '-' )
- sign = MINUS;
- }
- for(; s[i] >= '0' && s[i] <= '9'; i++)
- {
- multfbyi(ipart, 10, ipart);
- ipart[1] = s[i] - '0';
- normalize(ipart);
- }
- if(s[i] == '.')
- {
- i++;
- j = 0;
- for( ; s[i] >= '0' && s[i] <= '9'; i++)
- {
- multfbyi(fpart, 10, fpart);
- ++j;
- fpart[1] = s[i] - '0';
- normalize(fpart);
- }
- while(j-)
- divfbyi(fpart, 10, fpart);
- add(ipart, fpart, ipart);
- }
- ipart[0] = sign;
-
- assignto(z, ipart);
- }
-
- /*
- * Convert the fractional part of x to ASCII.
- * Up to count characters go to stdout.
- */
- fractoa(x, count)
- int *x;
- int count;
- {
- int i;
- int temp[ARRAYSIZE];
-
- assignto(temp, x);
- for(i = 1; i <= MAXFBINTEGER; i++)
- temp[i] = 0; /* erase integer part */
- temp[0] = PLUS; /* always positive */
- if(equalto(temp, zero))
- return; /* no fractional part to print out */
- putchar('.');
- while(count- && !equalto(temp, zero))
- {
- multfbyi(temp, 10, temp);
- putchar('0' + 6 * temp[3] + 2 * temp[2] + temp[1]);
- /* Now erase the integer part */
- temp[3] = temp[2] = temp[1] = 0;
- }
- }
-
- /*
- * CAUTION - altering the size of outbuff requires
- * some art. If MAXFBINTEGER == 100, it must be
- * large enough to hold the 160 decimal digit integer
- * 9.4259 * 10^159. If MAXFBINTEGER == 180, it must
- * be large enough for the 332 decimal digit integer
- * 3.6362 * 10^331. If you want to deal with really
- * big numbers and increase MAXFBINTEGER, you'll have
- * to give some thought as to how large the conversion
- * buffer is going to have to be.
- */
- /* Allow a little slack */
- char outbuff[MAXFBINTEGER*2] = { 0 };
- int outptr; /* Actually an index and not a "ptr" */
-
- /*
- * Factorial-base to ASCII conversion, integer
- * part end up to count characters of fractional
- * portion go to stdout.
- */
- facttoa(x, count)
- int *x;
- int count;
- {
- int i, j, sign;
- int temp[ARRAYSIZE];
- int val[ARRAYSIZE];
-
- outptr = 0;
-
- assignto(val, zero);
- assignto(temp, x);
- if((sign = temp[0]) == MINUS)
- {
- temp[0] = PLUS;
- putchar('-');
- }
- for(i = ARRAYSIZE - 1; i > HIGHGUARD; -i)
- temp[i] = 0; /* erase fractional part */
- while(!equalto(temp, zero))
- {
- divbyi(temp, 10, temp);
- for(i = HIGHGUARD + 2, j = 1; j <= 4; i++, j++)
- {
- val[i] = temp[i];
- }
- multfbyi(val, 10, val);
- outbuff[outptr++] =
- '0' + 6 * val[3] + 2 * val[2] + val[1];
- val[3] = val[2] = val[1] = 0;
- /*Now erase fractional portion of temp */
- temp[i-1] = temp[i-2] = temp[i-3] = temp[i-4] = 0;
- }
- if (outptr == 0) /* if no integer part */
- putchar ('0');
- else
- {
- while(outptr-)
- putchar(outbuff[outptr]);
- }
- fractoa(x, count); /* to print fractional portion */
- putchar('\n');
- }
-
- */ Remainder of file is RPN calculator & display */
- #ifdef DEBUG
- /*
- * Print a factorial-base number in factorial-base.
- * "digits" are printed between '<' and '>',
- * output goes to stdout.
- */
- facprint(x)
- int *x;
- {
- int i, j;
- if(x[0] == MINUS)
- printf("-");
- /* Delete any leading zeroes */
- for(i = MAXFBINTEGER; i >= 1; i-)
- {
- if(x[i] != 0)
- break;
- }
- /* Print any integer portion */
- for( ; i >= 2; )
- printf("<%d>", x[i-]);
- /* Make sure to print at least one digit */
- printf("<%d>", x[1]);
- printf(".");
-
- /*
- * Print fractional part, deleting any trailing
- * zeroes but printing at least one digit
- */
- i = HIGHGUARD + 2; /* start at 2! */
- printf("<%d>", x[i++]);
- for(j = 0; i < ARRAYSIZE; i++)
- {
- if(x[i] == 0)
- j += 1;
- else
- {
- while(j)
- {
- printf("<0>");
- -j;
- }
- printf("<%d>", x[i]);
- }
- }
- printf("\n");
- }
-
- /*
- * A simple but VERY slow reverse Polish calculator.
- * Commands +, -, *, /, D to print in decimal,
- * F to print in factorial, C to clear the stack,
- * S to display the whole stack in decimal,
- * a decimal number to use as an operand,
- * only 1 operator or operand per line!!
- */
-
- #define IPSIZE 256
- char input[IPSIZE];
- #define STACKSIZE 8
- int stack[STACKSIZE][ARRAYSIZE];
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- int x, i, prompt, depth;
-
- prompt = (argc > 1) ? TRUE : FALSE;
- depth = 0;
- for( ; ; )
- {
- if(prompt)
- printf(%d> ", depth);
- if(fgets(input, IPSIZE, stdin) == NULL)
- break;
- switch(x = input[0])
- {
- case 'c': /* clear the stack */
- case 'C': depth = 0;
- continue;
- case 'f': /* print top of stack in factorial */
- case 'F': if(depth < 1)
- {
-
- printf("empty stack\n");
- continue;
- }
- printf("%d: ", depth - 1);
- facprint(&stack[depth-1][0]);
- continue;
- case'd': /* print top of stack in decimal */
- case 'D': if(depth < 1)
- {
- printf("empty stack\n");
- continue;
- }
- printf("%d: ", depth - 1);
- facttoa(&stack[depth-1] [0], 50);
- continue;
- case's': /* display contents of stack */
- case 'S': if(depth < 1)
- {
- printf("empty stack\n");
- continue;
- {
- for(i = 0; i < depth; i++)
- }
- printf("%d: ", i);
- facttoa(&stack[i][0], 50);
- }
- continue;
- case '+': if(depth < 2)
- }
- printf("stack will underflow\n");
- continue;
- {
- add (&stack [depth-2] [0],
- &stack [depth- 1] [0],
- &stack[depth-2] [0] );
- -depth;
- continue;
- case '/': if(depth < 2)
- {
- printf ("stack will underflow\n");
- continue;
- }
- if (equal to(&stack [depth-1][0], zero))
- {
- printf ("division by zero\n");
- /* allow the 0 to be discarded */
- }
- else
- {
- divide (&stack [depth-2][0],
- &stack [depth-1][0],
- &stack[depth-2][0]);
- }
- -depth;
- continue;
- case '*': if(depth < 2)
- {
- printf ("stack will underflow\n");
- continue;
-
- }
- multiply(&stack [depth-2][0],
- &stack [depth-1] [0],
- &stack[depth-2] [0]);
- -depth;
- continue;
- case '-': if(input[1] == '\n')
- {
- if(depth < 2)
- {
- printf(
- "stack will underflow\n");
- continue;
- }
- subtract(&stack[depth-2][0],
- &stack[depth-1][0],
- &stack(depth-2][0]);
- -depth;
- continue;
- } /* else a negative number */
- else
- break;
- default: if(x != '-' && x != '.' &&
- (x < 'o' x > '9'))
- {
- printf("invalid entry\n");
- continue;
- }
- break;
- /* to convert and stack a number */
- }
- if(depth >= STACKSIZE)
- {
- printf("stack will overflow\n");
- continue;
- }
- atofact(input, &stack[depth++][0]);
- continue;
- }
- }
- #endif
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Wildcard Subdirectory Searches
-
-
- Toby Popenfoose
-
-
- Toby Popenfoose is a system analyst developing image processing applications
- for a large commercial printer. Prior to that, he was an Air Force Instructor
- Pilot. Toby has a B.S.E.E. from Purdue University and a M.S.C.S from
- Midwestern State University. You may contact him at 5720 East 200 North,
- Warsaw, IN 46580.
-
-
- I have often been asked "How many C source modules do we have in our Image
- Processing system?", or "Is there an easy way to delete all of the *.SAV files
- in all 40 subdirectories?". These two questions motivated me to develop a
- general-purpose wildcard subdirectory search utility function.
- I had several design goals. First, I wanted to make the function general
- enough to put in my utility library. Second, I wanted to maintain wildcard
- search capability. I wanted the function to perform a subdirectory search
- based on a switch. In addition, I wanted to minimize machine dependencies so I
- could port it to my AMIGA workstation. Last, I wanted the function to provide
- a totals switch.
-
-
- Design
-
-
- Looking at the available functions in Microsoft C v5.1, I decided to use
- _dos_findfirst and _dos_findnext functions, because they support wildcard
- searches. They also fill in a file info block (find_t structure) with the
- matching file's name. The drawback with this choice is that these functions
- are machine dependent. Two other non-portable machine dependent functions that
- I used are _splitpath() and _makepath(). Note, in Listing 1 the four machine
- dependent functions that I have used begin with an underscore. The ANSI
- standard reserves underscores for secret names with external linkage [1]. Now
- these really are not secret names or they would not have been documented in
- [2]. Looking at my other target platform, the AMIGA, I had two choices. I
- could use the AMIGA_DOS built-in functions Examine and ExNext which would
- require a directory Lock call at each subdirectory level (since the AMIGA is a
- true multitasking workstation) or I could use the well-respected public domain
- resident library ARP.LIBRARY (see sidebar, [3], [4], [5], [6] and [7]) with
- FindFirst and FindNext. I chose the latter because it would also give me
- wildcard capability, perform vertical subdirectory searches, and it more
- closely mimicked the Microsoft MS-DOS _dos_findfirst and _dos_findnext.
- I designed my utility using a recursive main that returns an int value to
- itself. I did this to prove that a main can be recursive because it is a
- function and behaves as any other function would [8]. The pseudo code follows.
- if (subdirectory switch)
- {
- push all subdirectory names onto a FIFO;
- while(subdirectories left)
- {
- get subdirectory off FIFO;
- recursively search that subdirectory;
- }
- }
-
- find wildcard matches within this directory;
- call user function with path argument
- My first pseudo code was with a stack for the subdirectory names, but I have
- actually implemented a FIFO (first in first out) buffer. If the subdirectories
- have been sorted is some order, that order will be maintained as it
- recursively searches each subdirectory.
- For the switches, I elected to use argv[2] with /S/T. /S is for subdirectory
- searches to be included and /T is for totals to be printed. For output, my
- main routine would call an external function subfunc(char *path) with a path
- pointer as an argument. I decided a maximum of 127 subdirectories per
- directory would be the upper limit of the stack size.
- With some trial and error, I have molded the code in Listing 1. After the
- first attempt at coding this, I realized that recursive functions must use the
- heap (malloc) and not the stack (auto variables) for array storage. A static
- array will not work for this application because a new array is needed at each
- recursive function call entry. For example, in Listing 1 after the main I have
- declared char *path and then malloced out space with path = malloc(_MAX_PATH);
- this uses the heap for my path array data storage. My first attempt used the
- stack with a declaration such as char path[_MAX_PATH].
- The first line of Listing 1 declares main to return an int. You may be
- surprised to see main() return something other than void. This int returned
- keeps track of the total as it transverses the subdirectories.
- The code fragment shown in Listing 2 deserves a more detailed explanation.
- Both _dos_findfirst and _dos_findnext return a 0 if a match is found. My if
- statements use the logical negation of the return value. If there is a match,
- it will evaluate to TRUE. The _dos_findfirst needs the _A_SUBDIR flag argument
- to include subdiretory names in its search. If a match is found the file info
- block (fib) is filed in. This structure is defined in DOS.H. Next, I check to
- make sure the subdirectory is not one of the two special MS-DOS subdirectories
- . or .. and that the matched name is in fact a subdirectory. To see if it is a
- subdirectory name, I look at the file info block attribute and test it for
- being a subdirectory. The bitwise & has a higher precedence then the logical
- && so it requires no parentheses. If I do have a valid subdirectory, its name
- is pushed into the FIFO buffer. This continues until I have no more directory
- entries or until I have no more room in my FIFO subdirectory buffer. Next, I
- terminate the FIFO with a NULL and reset my FIFO pointer to the start of the
- FIFO.
- Now while there is more subdirectories, I pop one out and recursively search
- it with the
- total += main(argc, argv);
- call.
-
-
- Alternative Implementations
-
-
- I have also implemented this recursive main as a recursive function int
- recurs(). It takes as arguments, a character string for the wildcarded file
- names to match and two Boolean flags (see Listing 6). These flags are for
- subdirectories to be included in the search and/or totals to be printed out,
- which allows the command line argument logic to be contained in a void main().
- A more elegant approach to command line arguments would be a variation of [9].
- I have developed and tested Listing 1, Listing 3, Listing 4, and Listing 6 on
- a PC clone with an INTEL 386 CPU and IBM-DOS v3.30 using Microsoft C v5.1.
- Listing 3, Listing 4, and Listing 5 have developed and tested on an AMIGA 1000
- with a Motorola 68000 and AMIGA-DOS v1.3 using MANX AZTEC C v5.0.
- The only trouble porting was due to non-ANSI functions that were
- machine/operating system/compiler dependent.
- Here are a few examples of how I have incorporated this wildcard subdirectory
- search: a carriage return, line feed corrector for porting ASCII source to and
- from IBM-DOS. I have also used Listing 1 in a zaptime utility to zero the time
- stamp on existing files. I have modifed Listing 1 to remove directories that
- are empty. I have used Listing 6 to modify the SGREP source supplied from [10]
- along with adding the feature of the output being to a file with a ~ prepended
- to the extension. This allowed me to convert over 700 C source files in over
- 33 subdirectories in less than 15 minutes. From the following two line matches
- #include <proto.h>
- #include "proto.h"
- to
- #include <libproto.h>
- #include "locproto.h"
- which was much faster than the time it took to put the prototype includes in
- the first time.
-
-
- Bibliography
-
-
-
- [1] Plauger, P. J. "Library Ground Rules". The C Users Journal, August 1990.
- [2] Microsoft C 5.1. Run-Time Library Reference, 1987.
- [3] Manx Software Systems. Aztec C Reference Manual Version 5.0, 1989.
- [4] Commodore Business Machines. AMIGA ROM Kernal Reference Manual: Exec,
- Addison-Wesley, 1986.
- [5] Berry, John Thomas. Inside the AMIGA with C, Howard W. Sams, 1988.
- [6] Peck, Robert A. PROGRAMMER's Guide To The AMIGA, SYBEX, 1987.
- [7] Anderson, Rhett and Randy Thompson. "MAPPING the AMIGA," Compute!, 1990.
- [8] Kernighan, Brian W. and Dennis M. Ritchie. The C Programming Language,
- Prentice Hall, 1978.
- [9] Colner, Don. "An Object-Oriented Approach to Command Line Options." The C
- Users Journal, July 1990.
- [10] C USERS GROUP disk #236, Highly Portable Utilities (CUG Starter Disk).
- Resident Libraries
- Resident libraries are a dynamic mechanism facilitated by the AMIGA-DOS
- Executive. These libraries are linked at runtime contrary to the normal
- compile, link cycle that links a runtime library object module into a binary
- executable at link time. This runtime linking is not truly a link but rather a
- jumptable that jumps to a relative offset from the libraries' base pointer.
- The base pointer is returned from the OpenLibrary() call.
-
-
- Advantages
-
-
- There are three big advantages to a resident library:
- 1. Using resident libraries, you can reduce the size of executable files
- because the object code necessary for a particular function is contained in
- the resident library. Also, when more than one task is operating in memory
- there is not duplicated code taking up precious memory.
- 2. It takes less time to load an executable because part of the executable is
- already in RAM memory. When there are several tasks multitasking, the
- AMIGA-DOS Executive keeps a list of libraries. Subsequent tasks that try to
- access a particular library that is already on the list are speeded up by the
- executive looking at the list and seeing that it is already resident.
- 3. There is true runtime versioning control on the library themselves. The
- OpenLibrary(char *LibName, long Version) system call allows programs at
- runtime to specify a version number that the present library must be equal to
- or greater than to have a successful open. If versioning is not required, a
- version of 0 as an argument to the OpenLibrary call will allow whatever
- version is present.
-
-
- Typical AMIGA DOS 1.3 LIBS
-
-
- arp.library is obtainable through most BBS's such as BIX, COMPUSERVE and
- PEOPLES LINK. It falls into the "freeware" category since it is copyrighted by
- ARP Authors (represented by Microsmith's Inc. It contains a multitude of
- functions that allow a programmer and/or user more capability then the
- corresponding AMIGA DOS 1.3 library functions might.)
- Table 1
- Library Name Variable Name Base Address Contents
- -------------------------------------------------------------------------
- clist.libary ClistBase Character string routines
- diskfont.library DiskFontBase Disk-based font routines
- exec.library ExecBase All Exec functions
- dos.library DosBase DOS functions
- graphics.library GfxBase Graphics functions
- icon.library IconBase Workbench functions
- intuition.library IntuitionBase Intuition interfacing
- layers.library LayersBase Window/Layers functions
- mathffp.library MathBase Basic math functions
- mathtrans.library MathTransBase Trancendental math functions
- mathieeedoubbas.library MathIeeeDoubBasBase Double precision IEEE
- timer.library TimerBase Timer funtions
- translator.library TranslatorBase Voice synthesis functions
- arp.library ArpBase DOS functions
-
- Listing 1
- /*
- A recursive descent main to search directories and subs
- with wildcard and path capabilities -> then pass the
- path name to a user supplied function called "subfunc".
- 2 switches are presently supported: /t for totals and
- /s for subdirectories.
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <malloc.h>
- #include <string.h>
-
- #include <dos.h>
-
- #define MAX_SUB 128 /* max subdirectory width for stack */
- /* subfunc is user module to link to */
- extern void subfunc(char *path);
-
- int main(int argc, char **argv)
- {
- struct find_t *fib;
- char *path, *drive, *dir, *fname, *ext;
- char *tmppath, *tmpdir, *tmpargv, *subfifo;
- char (*subfifoptr)[_MAX_FNAME];
- int total = 0;
- /* malloc so we don't blow up the stack */
- if (!((fib = malloc(sizeof(struct find_t))) &&
- (path = malloc(_MAX_PATH)) &&
- (drive = malloc(_MAX_DRIVE)) &&
- (dir = malloc(_MAX_DIR)) &&
- (fname = malloc(_MAX_FNAME)) &&
- (ext = malloc(_MAX_EXT)) &&
- (tmpdir = malloc(_MAX_DIR)) &&
- (tmppath = malloc(_MAX_PATH))))
- { /* return resources to DOS */
- free(fib), free(path), free(drive), free(dir);
- free(fname), free(ext), free(tmpdir), free(tmppath);
- printf("NOT ABLE TO MALLOC SPACE!\N");
- exit(-1);
- }
-
- if (argc < 2)
- { /* there was no command line argument */
- _splitpath(argv[0], drive, dir, fname, ext);
- printf("\n\t%s <filespec> [/s/t]\n", fname);
- printf("\tFilespec can have DOS wildcards!\n");
- printf("\t/S switch includes subdirectories.\n");
- printf("\t/T switch gives a total.\n");
- }
- else
- {
- if (argc > 2 && (strstr(strupr(argv[2]), "/S")))
- { /* if S switch - do subdirectories first */
- tmpargv = argv[1]; /* save argv[1] for future use */
- if (!(subfifo = *subfifoptr =
- (char *)malloc(_MAX_FNAME * MAX_SUB)))
- { /* return resources to DOS */
- free(fib), free(path), free(drive), free(dir);
- free(fname), free(ext), free(tmpdir), free(tmppath);
- printf("UNABLE TO MALLOC FOR INTERNAL FIFO!\N");
- exit(-1);
- }
- _splitpath(argv[1], drive, dir, fname, ext);
- _makepath(path, drive, dir, "*", "*");
- if (!_dos_findfirst(path, _A_SUBDIR, fib))
- do
- {
- if (fib->name[0] != '.' &&
- fib->attrib & _A_SUBDIR)
- /* push on FIFO */
- strcpy(*subfifoptr++, fib->name);
-
-
- } while (!_dos_findnext(fib) &&
- *subfifoptr <
- &subfifo[(_MAX_FNAME - 1) * MAX_SUB]);
- **subfifoptr = NULL; /* terminate FIFO */
- *subfifoptr = subfifo; /* reset FIFO pointer */
- while(**subfifoptr) /* while not at the end */
- {
- strcpy(tmpdir, dir);
- _makepath(path, drive,
- strcat(tmpdir, *subfifoptr++), fname, ext);
- argv[1] = path;
- /* recursive part of program */
- total += main(argc, argv); /* check next level */
- }
- free(subfifo);
- argv[1] = tmpargv; /* restore argv[1] */
- }
- /* look for files */
- if (!_dos_findfirst(argv[1], _A_NORMAL, fib))
- do
- {
- _splitpath(argv[1], drive, dir, fname, ext);
- strcpy(tmppath, drive);
- strcat(tmppath, dir);
- /* now call the work function */
- subfunc(strcat(tmppath, fib->name));
- total++;
- } while (!_dos-findnext(fib));
- if (argc > 2 && strstr(strupr(argv[2]), "/T"))
- printf("\ntotal = %d\t%s files\n", total, argv[1]);
- }
- /* return resources to DOS */
- free(fib), free(path), free(drive), free(dir);
- free(fname), free(ext), free(tmpdir), free(tmppath);
-
- return total;
- }
- /* End of File */
-
-
- Listing 2
- if (!_dos_findfirst(path, _A_SUBDIR, fib))
- do {
- if (fib->name[O] != '.' &&
- fib->attrib & _A_SUBDIR)
- strcpy(*subfifoptr++, fib->name);
- } while (!_dos_findnext(fib) &&
- *subfifoptr <
- &subfifo[(_MAX_FNAME - 1) * MAX_SUB]);
- **subfifoptr = NULL; /* terminate FIFO */
- *subfifoptr = subfifo; /* reset FIFO pointer */
-
- /* End of File */
-
-
- Listing 3
- /*
- LISTER: a subfunction to link to recursive main for
-
- listing wildcard file matches.
- */
-
- #include <stdio.h>
-
- void subfunc(char *path)
- {
- printf("\n%s", path);
- }
-
- /* End of File */
-
-
- Listing 4
- /*
- REMOVE: a subfunc to link to recursive main to
- do wildcard file deletes.
- */
-
- #include <stdio.h>
-
- void subfunc(char *path)
- {
- remove(path);
- }
-
- /* End of File */
-
-
- Listing 5
- /*
- A wildcard search main for the AMIGA workstation.
- 2 switches are presently supported: /t for totals
- and /s for subdirectories.
- */
-
- #include <functions.h> /* contains pragmas for system */
- #include <arpbase.h> /* contains pragmas for ARP calls */
-
- #define MAX_PATH 256
-
- struct ArpBase *ArpBase;
- struct AnchorPath anchor;
- char pattern[128];
- int total;
-
- void subfunc(char *path);
-
- void main(int argc, char **argv)
- {
- if (argc < 2)
- {
- printf("\n\t%s <filespec> [/s/t]\n", argv[O]);
- printf("\tFilespec can have DOS wildcards!\n"
- "\t/S switch includes subdirectories.\n"
- "\t/T switch gives a total.\n");
- exit(0);
- }
-
-
- ArpBase = OpenLibrary("arp.library", 39);
- if (!ArpBase)
- {
- printf("ERROR: couldn't open ARP!!!\n");
- exit(-1);
- }
-
- PreParse(BaseName(argv[1]), pattern);
- strcpy(BaseName(argv[1]), "*");
- anchor.ap_Flags = APF_DoWild;
- anchor.ap_StrLen = MAX_PATH;
-
- if (!FindFirst(argv[1], &anchor))
- do {
- if (anchor.ap_Info.fib_DirEntryType > 0)
- {
- if (!(anchor.ap_Flags & APF_DidDir) &&
- argc > 2 && strstr(argv[2], "/S"))
- anchor.ap_Flags = APF_DoDir;
- anchor.ap_Flags &= ~APF_DidDir;
- }
- else
- if (PatternMatch(pattern,
- anchor.ap_Info.fib_FileName))
- {
- total++;
- subfunc(anchor.ap_Buf);
- }
- } while(!FindNext(&anchor));
-
- if (argc > 2 && strstr(argv[2], "/T"))
- printf("\tTOTAL = %d\n\n", total);
-
- CloseLibrary(ArpBase);
- }
- /* End of File */
-
-
- Listing 6
- /* RECUR is a recursive descent subroutine to search
- directories and subs with wildcard and path
- capabilities -> then pass the path name
- to a user supplied function called "subfunc".
- 2 switches are presently supported: totals
- and subs for subdirectories.
- */
- #include <dos.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <malloc.h>
- #include <string.h>
-
- #define MAX_SUB 128
-
- extern void subfunc(char *path);
-
- int recurs(char *path, int subs, int totals)
- {
- struct find_t *fib;
-
- char *drive, *dir, *fname, *ext;
- char *tmppath, *tmpdir;
- char (*subfifoptr)[_MAX_FNAME];
- char *subfifo;
- int total = 0;
- /* malloc so we don't blow up the stack */
- fib = malloc(sizeof(struct find_t));
- drive = malloc(_MAX_DRIVE);
- dir =malloc(_MAX_DIR);
- fname = malloc(_MAX_FNAME);
- ext = malloc(_MAX_EXT);
- tmpdir = malloc(_MAX_DIR);
- tmppath = malloc(_MAX_PATH);
-
- if (subs)
- {
- /* if subs switch - do subdirectories first, RECURSIVELY */
- subfifo = *subfifoptr =
- (char *)malloc(_MAX_FNAME * MAX_SUB);
- _splitpath(path, drive, dir, fname, ext);
- _makepath(tmppath, drive, dir, "*", "*");
- If (!_dos_findfirst(tmppath, _A_SUBDIR, fib))
- do
- {
- if (fib->name[0] != '.' && fib->attrib &
- _A_SUBDIR)
- strcpy(*subfifoptr++, fib->name);
- } while (!_dos_findnext(fib) && *subfifoptr <
- &subfifo[(_MAX_FNAME - 1) * MAX_SUB]);
- **subfifoptr = NULL; /* terminate fifo */
- *subfifoptr = subfifo; /* reset fifo pointer */
- while(**subfifoptr) /* while not at the end */
- {
- strcpy(tmpdir, dir);
- _makepath(tmppath, drive,
- strcat(tmpdir, *subfifoptr++), fname, ext);
- total += recurs(tmppath, subs, totals);
- }
- free(subfifo);
- }
- if (!_dos_findfirst(path, _A_NORMAL, fib))
- do
- {
- _splitpath(path, drive, dir, fname, ext);
- strcpy(tmppath, drive);
- strcat(tmppath, dir);
- subfunc(strcat(tmppath, fib->name));
- total++;
- } while (!_dos_findnext(fib));
- if (totals)
- printf("\ntotal = %d\t%s files\n", total, path);
-
- free(fib), free(drive), free(dir), free(fname);
- free(ext), free(tmpdir), free(tmppath);
- return total;
- }
- End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Curve Fitting By Chebyshef And Other Methods
-
-
- Timothy Prince
-
-
- Tim Prince has a B.A. in physics from Harvard and a Ph.D. in mechanical
- engineering from the University of Cincinnati. He has 25 years of experience
- in aerodynamic design and computation. He can be contacted at 39 Harbor Hill
- Dr., Grosse Pointe Farms, MI 48236.
-
-
- The problem of fitting a curve to data as a convenient means for interpolating
- values has almost as many different solutions as there are people trying to
- find solutions. While there are accepted methods (such as Fourier transforms)
- for certain specialized applications, the field is wide open for many common
- applications.
- Some of the most common methods fall in the regression category, where you
- pick a form of curve, perhaps a polynomial of degree N, and fit it to the data
- by least squares. Typically, you may have little idea whether the family of
- curves you selected is appropriate for the application. Choosing too high an
- order of polynomial makes the shape of the curve too sensitive to
- uncertainties in the data points. To avoid the problem you can fit
- successively higher order polynomials, using statistical tests to decide where
- to stop. Least squares methods cannot fit a curve with much more than half the
- precision of the underlying computer arithmetic operations.
- Vendors of CAD systems have found their own ways to fit curves and surfaces to
- geometrical data. Their problem is complicated by the need to deal three
- dimensions, but the methods are generally based on two dimensional curves. The
- B(ezier) splines are popular in this field, but require a certain art in
- finding ways to develop and apply the software. No doubt the experts see the
- artistic content as an advantage. Obviously, satisfactory surfaces can be
- drawn by interactive graphic methods. On the other hand, blindly shoving data
- in is likely to give poor results.
-
-
- Cubic Splines
-
-
- Typical engineering software uses linear or spline curve interpolation between
- data points. The standard spline methods fit a curve with continuous slope and
- curvature passing exactly through each data point (referred to as knots).
- Appropriate conditions must be chosen for the first and final data points, and
- this is often difficult to do. General purpose cubic spline functions, such as
- the published de Boor method, offer a choice of three types of end condition:
- specified slope
- specified curvature
- "not-a-knot" smooth ends
- The specified curvature condition is often misused. Zero curvature at the end
- is called the "natural" condition, with reference to the physical spline
- analogy, but that doesn't mean you should use it. If there is no way to know
- what the curvature should be, the "not-a-knot" end condition is usually
- better. This adjusts the end curvature and slope to give the smoothest
- possible curve. In general, the third derivative of the spline (analogous to
- shear stress in a beam) changes abruptly at each knot. With the "not-a-knot"
- end condition, this discontinuity is eliminated at the knot next to the end,
- analogous physically to putting torque on the end of the spline so that it
- does not need restraint to pass through the knot next to the end.
-
-
- Spline Software Issues
-
-
- Even with intelligent use of splines, problems remain. When all the end
- condition options are included, it may be difficult to figure out how to
- structure code. Much published software is less efficient than it could be.
- Consider the spline evaluation code from Numerical Recipes In C (Press,
- Flannery, Teukolsky, and Vetterling
- a= (xa[khi]-x)/(h=xa[khi]-xa[klo]);
- b= (x-xa[klo] )/h;
- *y= a*ya[klo]+b*ya[khi]+((a*a*a-a)*y2a[klo]
- + b*b*b-b)*y2a[khi])*h*h/6;
- and the algebraic equivalent
- b= x-xa[klo];
- a= xa[khi]-x;
- h= xa[khi]-xa[klo];
- *y= ((ya[klo]-y2a[klo]/6*b*(a+h))*a+(ya[khi]
- -y2a[khi]/6*a*(b+h))*b)/h;
- The second version saves four operations and the corresponding roundoff
- errors.
- The spline fit function in Numerical Recipes In C has a problem with a
- potential aliasing of array elements which can force the compiler to generate
- less efficient code. The problem can be alleviated simply by changing the
- order of lines in the code. The prototype reads
- void spline(float x[]
- float y[],int n,float y2[])
- Only the array y2[] is to be changed by spline(). But the C compiler does not
- know that the array arguments do not overlap, so the compiler must assume that
- any change to y2[] could also affect x[] or y[], preventing it from reusing
- intermediate values. Therefore, the assignment to y2[i] should occur last in
- the loop body. This change allows the compiler to perform normal
- optimizations, short of taking advantage of loop unrolling. Such aliasing is
- an issue for any C compiler trying to combine common subexpressions.
- Intermediate results can be saved where they will be used again on the next
- step. Since the algorithm is recursive anyway, the additional recurrences will
- not pose an obstacle to any known compilers, as they might in parallelizable
- code. The improvement in speed is not as large as might be expected;
- sequential processors do not have enough registers and must use memory storage
- of these temporaries, and pipeline processors can handle many of the duplicate
- floating point operations by increasing parallelism.
-
-
- Use Of Splines
-
-
- You may wish to use the fitted curve to find the slope of the data. Averaging
- the slope of the cubic spline curve and the slope of the linear interpolant
- gives a good estimate at points half way between knots, but this slope does
- not match the slope of any smooth curve through the data. As end slope is
- determined by your choice of end condition, slopes closer to the end points
- are as unreliable as extrapolating the curve beyond the end points.
- Integration of the spline fit to find the area under a curve gives slightly
- over half the error of Simpson's rule integration using the same data points.
- This improvement is hardly worth the extra effort unless it is impossible to
- get the data spaced appropriately for Simpson's rule.
- A cubic spline curve exaggerates errors or uncertainty in the data points.
- Usual ways of dealing with this are to use the "spline in tension" method, or
- to put "springs" between the curve and the knots and allow the curve to adjust
- to greater smoothness by deviating slightly from the input data. Tension or
- stiffness of the springs is an arbitrary parameter, requiring human
- interaction to get a visually satisfactory result. Repeated smoothing keeps on
- changing the data; unlike the Chebyshef smoothing, the adjusted splines never
- produce a curve which doesn't want to be adjusted further.
- In practice, there is a fairly low limit (around 15) to the number of points
- which can be spline fit reliably without provision for tension or data
- adjustment. Demanding geometric problems, like fitting the contour of an
- airplane wing, can be handled by fitting splines in several segments.
- A tactic which may be useful for geometric curve fitting by splines or any
- other method is to parameterize x, y, and z as a function of stair step
- distance. Ideally, the arc length along the curve would be a parameter; the
- stairstep distance is much easier to obtain. Fitting two or three functions of
- one independent variable is not much more time-consuming.
-
-
- Chebyshef Polynomials
-
-
-
- Chebyshef polynomials provide a well founded way of fitting a curve which
- approximates a function within a finite interval. The method seems almost too
- magical to trust, requiring less arbitrary input and often giving more
- satisfactory results than alternative methods. Dropping a term from a
- Chebyshef fitted polynomial introduces errors which are nowhere larger than
- the associated coefficient, so you can easily see which terms to keep to reach
- the desired accuracy of curve fit. When additional terms don't yield further
- convergence, it is good evidence that the background noise in the data or the
- limited accuracy of the arithmetic is swamping the remaining terms.
- The theory is covered well in the textbooks, and a reliable set of C functions
- are presented in Numerical Recipes In C. I will concentrate on ways to use
- Chebyshef fits, and some implementation issues which should be addressed for
- best results.
- Determination of Chebyshef coefficients requires the function values to be
- supplied at points dictated by the method, where the polynomial will match the
- specified values. Using specific points may seem awkward when the control
- points consist of arbitrary data at arbitrary locations, but Chebyshef fitting
- has been applied successfully by using linear interpolated values. Placement
- of the input points is less critical than for splines, although, since the
- function evaluations are concentrated toward the ends of the interval, the
- data are weighted more heavily there. The number of function evaluations
- needed for Chebyshef fitting is the minimum required to specify a given number
- of polynomial terms, so this method is as economical as any for functions
- which are expensive to evaluate. A dozen Chebyshef terms are likely to be
- sufficient for close approximation to any smooth curve.
- The authors of Numerical Recipes in C advise against converting the Chebyshef
- polynomial into an ordinary polynomial before evaluation. This advice makes
- sense when there will be few evaluations of the polynomial, or when little is
- known about its behavior. Existing software often violates this advice,
- possibly because Clenshaw's algorithm was not well known until recently.
- Clenshaw's algorithm is 30 percent faster than evaluation by more obvious
- methods on sequential processors; even with careful coding for best
- performance, the difference drops to 20 percent on typical pipeline
- processors, but the small advantage in accuracy remains.
-
-
- Chebyshef Performance Tuning
-
-
- The routine for calculating the Chebyshef coefficients consumes the most time
- and generates most of the roundoff error when all arithmetic operations are
- done at the same precision. cos() is evaluated with arguments up to n*PI for a
- Chebyshef fit of order n. If the arguments have a relative error of order
- DBL_EPSILON, the results may be in error by order n*DBL_EPSILON. Summing up
- the results introduces smaller errors, which are still significant if accurate
- results from cos() are obtained. If these roundoff errors can be controlled,
- the overall roundoff errors in the Chebyshef fit and evaluation can be held to
- about DBL_EPSILON. Unfortunately, most compilers fail to take advantage of the
- ability of 8087 or 68881 processors to hold the errors generated in the inner
- loop to the order of n*DBL_EPSILON. Even without extra precision, the accuracy
- of Chebyshef fitting is far better than least squares methods.
- On co-processor architectures which have a built-in cosine function, you
- should avoid going through subroutine calls that narrow arguments and results
- to double precision. The greatest accuracy improvement comes from use of a
- long double value for PI. If the system does not support true long double
- constants, you may be able to generate a long double value which can be
- obtained by tricks such as
- #define PI
- (sqrt(3.125*3.125)+.01659265358979323846)
- Putting a function call in the expression prevents most compilers from
- performing the arithmetic and narrowing the result. Using a long double value
- for PI reduces the roundoff errors, typically by a factor of two compared to
- errors generated by using a normal double value for PI. Roundoff error may be
- reduced by an additional factor of three by taking the assembly code output
- from the C compiler and changing the cos() calls to direct numeric processor
- instructions. Sun's inline function compilation option doesn't do the job,
- since it still requires "arguments" and "returns" narrowed to double and
- pushed on the stack. With full use of register variables, the 8087 and 68881
- families are excellent for Chebyshef analysis.
- Pipelined processors should have a pipelined version of cos(), since it should
- be possible to calculate a group of four cosines as fast as two individual
- cosines. An intermediate level of performance may be obtained by supplying
- source code for cos() and using an inlining compiler option. While cosf() can
- be handled by a macro, the greater number of terms in cos() makes such an
- approach impractical. The subject of group math library functions could fill
- another article.
- The recursive algorithms for evaluation of Chebyshef polynomials can be
- speeded up on superscalar architectures by determining which array element is
- needed first, and writing the code to fetch that element prior to the loop
- iteration in which it is needed. This is particularly true of the Chebyshef
- derivative coefficient evaluation. As presented in Numerical Recipes In C,
- this function again suffers from aliasing, since the compiler cannot be sure
- that the read-only input array c[] will not be overwritten. In order to keep
- the pipeline running, the potentially aliased array element has be fetched
- before the results of the current iteration are stored. In addition, all but
- two iterations of the second loop can be moved into the first loop where they
- will fill otherwise empty pipeline slots. There are enough registers even on
- an 8087 to eliminate all redundant memory references, further reducing the
- aliasing problem. Listing 1 shows the details.
- These changes are detrimental to readability but can triple the performance
- and reduce the size of the generated code. Overlapping of stages of a
- sequential calculation from different loop iterations is reminiscent of CDC
- 6600 style code. In accordance with the old saw, a FORTRAN programmer can
- write FORTRAN in any language.
- The aliasing problem could be eliminated by storing results in a temporary
- array, then copying this array to the destination. As long as a loop does not
- involve reading and writing different external arrays, with one array accessed
- via a pointer, there is no aliasing problem. The temporary vector solution is
- common for vectorizable code, but in simple problems involving recurrence,
- better results can be obtained with pump priming methods as shown previously.
-
-
- Chebyshef Economized Polynomials
-
-
- A common end use of Chebyshef polynomials is in computational approximations
- for math library functions. In this application, the goal is to fit a
- polynomial with the minimum number of terms needed to give the required
- precision. Typically, the base interval is chosen to be symmetric about zero,
- so that there are only odd power terms. The interval will be one in which the
- Taylor series converges well, and a Chebyshef economized polynomial converges
- even better. The caution about possible inaccuracy of polynomial evaluation
- does not apply.
- In order to get a series of sufficient accuracy to use in a library function,
- the whole Chebyshef fitting process has to be carried out in a higher
- precision. long double precision should be sufficient to calculate
- coefficients for double precision function approximations.
- If a Chebyshef series is fitted directly to a function which has zeros, there
- will be errors near the zeros which are large relative to the local value of
- the function, even though they are less than the errors at the end of the
- interval. One way of getting around this is to subtract off the linear term of
- a series expansion around the zero, and make the Chebyshef fit to the
- remainder. Similar tactics might be applied where the function is known to be
- approximated by a more complicated function.
- A better method for the standard library functions is to divide out the zero,
- so that the Chebyshef polynomial is fit to a function such as sin(x)/x. The
- Chebyshef polynomial will have only even terms, with the size of the odd terms
- calculated being an indication of numerical error. The end result will be an
- approximation which has less relative error over most of the interval than a
- minimax polynomial obtained by laborious iteration to minimize the maximum
- relative errors. The general strategy is to approximate a function by a known
- dominant term times a Chebyshef polynomial which takes care of the fine
- adjustments.
- Most Chebyshef economized polynomials can be determined by straightforward
- programming using a system with long double arithmetic or a multiple precision
- package. In some cases, as with log((1+x)/(1-x)), it is better to derive a
- Taylor series which matches the form of the planned approximation and fit the
- Chebyshef polynomial to this series. Working out the cancelling terms
- algebraically rather than numerically avoids much of the roundoff error.
- Normally, Chebyshef economized polynomials would be used only as a software
- development tool, to provide a best fit, most economical polynomial for quick
- evaluation of a function. If the number of evaluations of a curve is not
- great, or the convergence properties of a polynomial are unknown, it is better
- to use the Clenshaw algorithms for evaluation. Still, an economized polynomial
- might well be better than a least squares fit regression, and one might want
- to find such a polynomial for comparison.
-
-
- Rational Polynomials
-
-
- Rational polynomials form a better fit to some types of functions. A clue that
- this may be the case is an inversion symmetry, as in
- exp(x) = 1/exp(-x)
- or
- tan(PI/2-x) = 1/tan(x)
- Both of these functions are best fit by rational polynomials. Unfortunately,
- there is no accepted method for making such numerical approximations. You can
- usually guess the form of the series; since
- tan(x) = -tan(-x)
- the rational polynomial approximation will have odd power terms in the
- numerator and even powers in the denominator. An approximation for exp()
- should be of the form
- (p(x)+q(x))/(p(x)-q(x))
- with p having only even power terms and q having only odd power terms, in
- order to exhibit the required symmetry. This is equivalent to an approximation
- for tanh(x) of the form
- q(2x)/p(2x)
- which is a way to evaluate tanh() for small x without losing accuracy.
- You could arrive at similar results by deriving continued fraction
- approximations and converting to rational polynomials, a process which might
- be educational but wouldn't help in terms of practical results. The
- coefficients obtained analytically for rational polynomials (for unbounded
- intervals) are likely to be far from optimum for a closed interval.
- People who play with these approximations have their favorite iterative
- numerical methods for adjusting a reasonable guess at the coefficients to
- greater accuracy. These calculations take almost twice as many bits of
- precision as the desired final accuracy; you would be lucky to get an
- approximation accurate to 12 decimal places on an 8087. Even IBM 3081 quad
- precision is marginal for obtaining a rational approximation for tan(). The
- author has had satisfactory results from a method acquired from Ruzinsky,
- which runs 10 to 20 seconds on a VAX 8550. With a multiple precision software
- package, higher accuracy fits should be feasible but time-consuming.
-
-
- Conclusion
-
-
- Several popular methods for curve fitting have applications for which they are
- well suited; some have been used where they work less well. The simplicity of
- the popular spline methods is gained at a cost in reliability. Spline methods
- work well when the end conditions can be specified accurately, and the data
- points are accurate. Rational polynomials are best, in the author's
- experience, only when they model a known symmetry. The Chebyshef methods are
- effective and deserve wider use. Chebyshef methods can be used for smoothing
- and interpolating discrete data which are moderately uncertain, as well as for
- evaluation of fits to known functions. Recursive algorithms for accurate
- evaluation of Chebyshef polynomials can be organized for superscalar
- performance on pipelined architectures.
- References
- Deboor, C. A Practical Guide to Splines, Springer-Verlag, 1978.
- Press, W.H., Flannery, B.P., Teukolsky, S.A., Vetterling, W.T., Numerical
- Recipes In C, Cambridge University Press, 1988.
- Ruzinsky, Steven A., "A Simple Minimax Algorithm," Dr. Dobb's Journal, #93,
- pp. 84-91, July 1984.
-
- Fundamentals Of Curve Fitting
- Steve Graham
- Curve fitting is something of a misnomer; a better name might be "function
- finding" -- the goal is to find a function that satisfies certain constraints.
- The constraints normally take one of two forms: a list of control points
- (samples from some process to be modelled), or a specific function (too
- difficult or costly to evaluate). These constraints mirror the two goals of
- curve fitting: interpolation and approximation.
- Interpolation is the practice of using function values at some points (usually
- samples, with the actual function unknown) to estimate the value of the
- function at other points. If the function argument to be evaluated lies
- outside the range of the sampled points, the process is referred to as
- extrapolation. The simplest example is linear interpolation: find the two
- control points nearest the argument, assume the function corresponds to a
- straight line between the two points, and calculate the estimated function
- value. This may be familiar from interpolating log tables. Graphically, linear
- interpolation produces a "connect-the-dots" image from the control points.
- Because of the cost of evaluating some functions, it is preferable to use a
- fast approximating function, as long as the approximation is sufficiently
- accurate. Since the original function is known, the implementor has more
- control over the number and placement of control points, as well as a standard
- to measure the accuracy of the fit against.
- When fitting a curve to control points, there are two preliminary choices. The
- curve can be created from all the control points, or it can be created in
- segments, short curves between adjacent control points. The curve can be
- required to match the control points exactly or the curve may be allowed to
- diverge, creating local errors in the interest of minimizing global error.
- For N control points, a unique polynomial of degree N-1 can match the points
- precisely. Lagrange's interpolation formula provides an expression for the
- polynomial
- Equation 0
- Suppose we want to find a quadratic polynomial through: (1,0), (2,1), (3,4).
- Lagrange's formula yields
- Click Here for Equation
- which simplifies to
- Click Here for Equation
- Spline techniques treat segments individually, producing a composite curve
- from the segments. A spline was a flexible ruler used in drafting. The spline
- was anchored to the control points (referred to as "knots"), allowing a smooth
- curve to be drawn. The functions fitting individual segments are generally
- simple. A popular choice is a cubic polynomial (hence, "cubic splines"), of
- the form
- P (x) = ax3 + bx2 + cx + d
- Cubic splines produce a relatively smooth curve, since adjacent segments are
- normally forced to have identical slope (first derivative) and curvature
- (second derivative) at the knots. Constraints may be stated by requiring
- continuity of the first and second derivatives of the composite function at
- the knots. The constraints and the knot coordinates, can be used to determine
- the coefficients for the fitting cubic polynomial. The first and last knots
- cause a problem, since there is no adjacent segment to constrain the values
- for the slope and curvature; other criteria must determine the coefficients
- for the end segments, such as the "natural" spline that sets the curvature at
- the first and last knots to zero.
- Two popular measures of the quality of a curve fit are the least squares
- method and the minimax method. The least squares techniques seek to minimize
- the sum of the squares of the divergence between the approximating function
- and the control points or original function. Minimax techniques use the
- alternative aim of minimizing the maximum error between the new function and
- the source data or function.
- Chebyshef (also, Chebyshev, Chebichev or Tchebycheff) fitting relies on a
- combination of Chebyshef polynomials to approximate the function. A Chebyshef
- polynomial has the form
- Tn(x) = cos (n arccos x)
- Note the recurrence relation between subsequent Chebyshef polynomials
- To (x) = 1
- T1 (x) = x
- T2 (x) = 2x2 - 1
- T3 (x) = 4x3 - 3x
- Tn+1 (x) = 2xTn (x) - Tn-1 (x) n >= 1
- Clenshaw's recurrence formula provides an efficient way fo evaluating such
- equations.
-
-
- Aliasing And Optimization
-
-
- When a programming language allows two apparently different access mechanisms
- to address the same data elements, this is referred to as aliasing. Having
- multiple names for the same objects is considered bad style and discouraged,
- since it adds to the complexity of understanding a program. The possibility of
- aliasing is almost impossible to eliminate from programming languages:
- subroutine calls with one data object used for two arguments and mapped to two
- different formal parameters can cause aliasing. The most common cause for
- aliasing problems, is access to data through a pointer, where two different
- references could access the same data.
- Besides interfering with comprehension, aliasing becomes an optimization
- problem for subroutines. The simplest and most common optimization performed
- by compilers is the reuse of values. If X has already been loaded into a
- register for use by the previous statement, it needn't be loaded again. If an
- intermediate value (a subexpression), say B*B - 4*A*C, has been calculated
- once, it needn't be calculated again. Two problems can interfere with reusing
- values: unsufficient registers to hold the values and possible changes to
- constituent variables. The available registers are a simple consequence of the
- CPU's architecture and the compiler's conventions for register use. Possible
- changes arise because of aliasing and side effects. The compiler must
- guarantee correctness, so if aliasing is possible and a write occurs, saved
- values must be flushed, since they are no longer reliably accurate. Similarly,
- if a function call occurs that could cause side effects -- even if the
- function call mechanism preserves register contents -- the saved values are no
- longer necessarily correct.
- In practice possible aliasing and function calls should be a concern in tight,
- inner loops that are executed repeatedly. Inline code and careful statement
- ordering can permit compilers to perform optimizations that otherwise might
- introduce errors.
- The term "aliasing" is also used in graphics and sampling to refer to
- artifacts created by the granularity of the sample or representation; such
- aliasing may create jagged edges in images.
-
- Listing 1
- void chder(a,b,c,cder,n)
- double a,b,c[],cder[]; /* c[] and cder[] must not overlap */
- int n;
- {
- register int j;
- register double con,cj1,cdj2,cdj1,cdj;
- con = 2/(b-a);
- cdj1 = 2*(n-1)*c[n-1];
- cj1 = 2*(n-2)*c[n-2];
- cder[n-1] = cdj2 = 0;
- for(j = n-2; -j >= 0;){
- cdj = cdj2+cj1; /* the recursive result of first loop */
- cj1 = 2*j*c[j]; /* prefetch for next loop iteration */
- cder[j+1] = con*(cdj2=cdj1); /* complete final loop */
- cdj1 = cdj; /* compiler doesn't worry about aliasing of locals */
- }
- cder[0] = con*cdj1;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- Primitives For <stdio.h>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
- ANSI C standardMs committee, X3J11, and convenor of the ISO C standards
- committee, WG14. His latest book is The Standard C Library, published by
- Prentice-Hall. You can reach him at PJP@wsa.oz; or uunet!munnari!wsa.oz!pjp.
-
-
-
-
- Introduction
-
-
- Last month, I began discussing how to implement the functions declared in the
- header <stdio.h>. I showed my version of that header file and described the
- FILE data structure. I also walked through the code needed to open and close
- files. (See "Implementing <stdio.h>," Standard C, CUJ January, 1992.)
- This month, I present the low-level "primitive" functions in <stdio.h> that
- must be tailored to each operating system. A primitive is a function that
- performs some essential operation. No other combination of library functions
- can substitute. You can keep most of the library portable if you isolate
- system dependencies in a minimum set of primitives.
- As I mentioned last month, this implementation has nine such functions Each
- primitive function must perform a standardized operation for the Standard C
- library. Each must also be reasonably easy to tailor for the divergent needs
- of different operating systems.
-
-
- The Primitives
-
-
- Three of the primitives are also standard functions:
- remove -- Remove a named file.
- rename -- Change the name of a file.
- tmpnam -- Construct a reasonable name for a temporary file.
- Each of these functions is small and very dependent on the peculiarities of
- the underlying operating system. It is not worth writing any of them in terms
- of lower-level primitives. You can often find versions in an existing C
- library that do the job nicely.
- Three of the primitives are macros defined in the internal header "yfuns.h".
- It defines macros and declares functions needed only within the Standard C
- library to interface to the outside world. Only certain functions written for
- this implementation need include "yfuns.h". (The internal header <yvals.h>, by
- contrast, must be included in several standard headers.)
- The three macros look like internal functions with the declarations
- int _Fclose(FILE *str);
- int _Fread(FILeE *str, char *buf, int size);
- int _Fwrite(FILE *str, const char *buf, int size);
- Their semantics are:
- _Fclose -- Close the file associated with str. Return zero if successful.
- _Fread -- Read up to size characters into the buffer starting at buf from the
- file associated with str. Return the number successfully read, or zero if at
- end-of-file, or a negative error code if a read error occurs.
- _Fwrite -- Write size characters from the buffer starting at buf to the file
- associated with str. Return the number of characters actually written or a
- negative error code if a write error occurs.
- Many operating systems support functions that have declarations very similar
- to these. You can often find existing functions that the macro expansions can
- call directly.
- The last three primitives are internal functions. One function is declared in
- "xstdio.h". Two are used in masking macros, and hence are declared in
- <stdio.h>. (See last month's presentation for a listing of this standard
- header file.) Their declarations are:
- short _Fopen(const char *name, unsigned short mode,
- const char *mods);
- long _Fgpos(FILE *str, fpos_t *fpos);
- int _Fspos(FILE *str, const fpos_t *fpos,
- long offset, int way);
- Their semantics are:
- _Fopen -- Open the file with name name and mode mode (possibly using the
- string mods as well). Return a non-negative handle if successful.
- _Fgpos -- If fpos is not a null pointer, store the file-position indicator at
- fpos and return zero. Otherwise, encode the file-position indicator as a long
- and return its value. Return the value EOF if not successful.
- _Fspos -- If way has the value SEEK_SET, set the file-position indicator from
- either fpos or offset. (If fpos is not a null pointer, use the value stored in
- fpos. Otherwise, decode offset to determine the file-position indicator.) If
- way has the value SEEK_CUR, add offset to the file-position indicator.
- Otherwise, way must have the value SEEK_END. Set the file-position indicator
- to just beyond the last character in the file, plus offset. If successful,
- return zero and clear _MEOF, _MREAD, and _MWRITE. Otherwise, return the value
- EOF.
- You are less likely to find existing functions that you can commandeer to
- implement part or all of these three functions. Each involves data
- representations that are probably peculiar to this implementation.
-
-
- File Positioning Functions
-
-
-
- Old C hands are probably comfortable with most of these primitives. They bear
- a strong resemblance to a fistful of functions not included in the C Standard.
- These have names such as close, open, read, and write. They are based on
- system calls in the UNIX operating system. Early ports of C to other operating
- systems retained this heritage as much as possible. The old primitives were
- omitted from the C Standard for good reasons. Nevertheless, many of them still
- provide a good foundation for the rest of the library.
- The primitives you are likely to find strangest are _Fgpos and _Fspos. They
- bear only a loose resemblance to their ancestor, called lseek. Most of the
- difference is designed to accommodate two new file-positioning functions,
- fgetpos and fsetpos. These have no exact analog in UNIX.
- To show you why these new primitives take the form they do, I simply show how
- the library uses them. The function fseek, for example, is simply
- int fseek(FILE *str, fpos_t *p)
- {
- return (_Fspos(str, NULL,
- off, smode));
- }
- Its brother ftell is
- long ftell(FILE *str)
- {
- return (_Fgpos(str, NULL));
- }
- And rewind is
- void rewind(FILE *str)
- {
- _Fspos(str, NULL, OL, SEEK_SET);
- str->_Mode &= ~_MERR;
- }
- Similarly, the new function fgetpos is simply
- int fgetpos(FILE *str, fpos_t *p)
- {
- return (_Fgpos(str, p));
- }
- And fsetpos is
- int fsetpos(FILE *str,
- const fpos_t *p)
- {
- return (_Fspos(str, p,
- OL, SEEK_SET));
- }
- The results speak for themselves. These two primitives make the five
- file-positioning functions trivial.
-
-
- UNIX Primitives
-
-
- By implementing these interface primitives, you can use this library in
- conjunction with any reasonable operating systems. As I mentioned last month,
- I have written sets of primitives for several popular operating systems. For
- completeness, I show here primitives for just one environment. Please
- remember, however, that these represent but one of many possibilities.
- For simplicity, I sketch here primitives that interface to many versions of
- the UNIX operating system. That is often the easiest system to use as a host
- for the Standard C library. The C language has moved to many other
- environments. Still, as I indicated above, much of the library design was
- shaped by the needs and capabilities of UNIX. The files I show are only
- sketches because they often can be augmented to advantage.
- In all cases, I assume the existence of C-callable functions that perform UNIX
- system calls without violating the name-space restrictions of Standard C. I
- take the conventional UNIX name, make the first letter uppercase and prepend
- an underscore. Thus, unlink becomes _Unlink. You may have to write these
- functions in assembly language if your UNIX system supplies no adequate
- substitutes.
- For example, Listing 1 shows the file remove.c that defines the function
- remove. This version simply invokes the UNIX system call _Unlink. A more
- careful version would verify that a program with super-user permissions is not
- doing something rash.
- Listing 2 shows the file rename, c. It defines a simple version of rename that
- simply manipulates links to the file. That typically works only if both the
- new and old file names are within the same filesystem (on the same logical
- disk partition). A more agressive version might choose to copy a file when the
- link system service fails.
- Listing 3 shows the file tmpnam.c. It defines a simple version of tmpnam that
- concocts a temporary file name in the directory /tmp, the customary place for
- parking temporary files. It encodes the current process-id to make a family of
- names that should be unique to each thread of control.
- Listing 4 shows the file xfopen.c that defines the function _Fopen. It maps
- the codes I chose for the mode indicators to the codes used by the UNIX system
- service that opens a file. A proper version of this program should not include
- all these magic numbers. Rather, it should include the appropriate header that
- UNIX provides to define the relevant parameters.
- UNIX makes no distinction between binary and text files. Other operating
- systems may have to worry about such distinctions at the time the program
- opens a file. Similarly, UNIX has no use for any additional mode information.
- (_Fopen could insist that the mode argument be an empty string here. This
- version is not so particular.)
- Listing 5 shows the file xfgpos.c that defines the function _Fgpos. It asks
- the system to deliver the file-position indicator for the file, then corrects
- for any data buffered on behalf of the stream. A file-position indicator under
- UNIX can be represented in a long. Hence, type fpos_t, defined in <stdio.h>,
- is a structure that contains only one long member. (I could have defined
- fpos_t as type long directly, but I wanted to keep the type as restrictive as
- possible.) In this case, the functions fgetpos and fsetpos offer no advantage
- over the older file-positioning functions. The difference can be important for
- ther systems, however.
- _Fgpos is simpler under UNIX in another way. No mapping occurs between the
- internal and external forms of text streams. Hence, the correction for
- characters in internal buffers is simple. Consider, by comparison, a system
- that maps text streams. Say it terminates each text line with a carriage
- return plus line feed instead of just a line feed. That means that _Fread must
- discard certain carriage returns and _Fwrite must insert them. It also means
- that _Fgpos must correct for any alterations when it corrects the
- file-position indicator. The problem is manageable, but it leads to messy
- logic that I choose not to show at this point.
- Listing 6 shows the file xfspos.c that defines the function _Fspos. It too
- benefits from the simple UNIX I/O model in the same ways as _Fgpos. Output
- causes no problems, since the function flushes any unwritten characters before
- it alters the file-position indicator.
- The remaining three primitives are macros. All expand to calls on functions
- that perform UNIX system services directly. The UNIX version of "yfuns.h"
- contains the lines:
- #define _Fclose(str) \ _Close((str)->_Handle)
- #define _Fread(str, buf, cnt) \
- _Read((str)->_Handle, buf, cnt)
- #define _Fwrite(str, buf, cnt) \
- _Write((str)->_Handle, buf, cnt)
- int _Close(int);
- int _Read(int, unsigned char *, int);
- int _Write(int, const unsigned char *, int);
-
-
-
- Summary
-
-
- That's the underpinnings of this implementation of the header <stdio.h>. I can
- now go on to show you how to perform various unformatted reads and writes
- using this machinery. And as a grand finale, you can see at least some of the
- code needed to perform formatted I/O. The story continues.
- This article is excerpted in part from P.J. Plauger, The Standard C Library,
- (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
-
- Listing 1 (remove.c)
- /* remove function -- UNIX version */
- #include "xstdio.h"
-
- /* UNIX system call */
- int _Unlink(const char *);
-
- int (remove)(const char *fname)
- { /* remove a file */
- return (_Unlink(fname));
- }
-
- /* End of File */
-
-
- Listing 2 (rename.c)
- /* rename function -- UNIX version */
- #include "xstdio.h"
-
- /* UNIX system calls */
- int _Link(const char * const char *);
- int _Unlink(const char *);
-
- int (rename)(const char *old, const char *new)
- { /* rename a file */
- return (_Link(old, new) ? -1 : _Unlink(old));
- }
-
- /* End of File */
-
-
- Listing 3 (tmpnam.c)
- /* tmpnam function -- UNIX version */
- #include <string.h>
- #include "xstdio.h"
-
- /* UNIX system call */
- int._Getpid(void);
-
- char *(tmpnam)(char*s)
- { /* create a temporary file name */
- int i;
- char *p;
- unsigned short t;
- static char buf[L_tmpnam];
- static unsigned short seed = 0;
-
- if (s == NULL)
- s = bur;
- seed = seed == 0 ? _Getpid() : seed + 1;
- strcpy(s, "/tmp/t");
- i = 5;
-
- p = s + strlen(s) + i;
- *p = '\0';
- for (t = seed; 0 <= --i; t >>= 3)
- *--p = '0' + (t & 07);
- return (s);
- }
-
- /* End of File */
-
-
- Listing 4 (xfopen.c)
- /* _Fopen function -- UNIX version */
- #include "xstdio.h"
-
- /* UNIX system call */
- int _Open(const char *, int, int);
-
- int _Fopen(const char *path, unsigned int smode,
- const char *muds)
- { /* open from a file */
- unsigned int acc;
-
- acc = (smode & (_MOPENR_MOPENW)) ==
- (_MOPENR_MOPENW) ? 2
- : smode & _MOPENW ? 1 : 0;
- if (smode & _MOPENA)
- acc = 010; /* 0_APPEND */
- if (smode & _MTRUNC)
- acc = 02000; /* 0_TRUNC */
- if (smode & _MCREAT)
- acc = 01000; /* 0_CREAT */
- return (_Open(path, acc, 0666));
- }
-
- /* End of File */
-
-
- Listing 5 (xfgpos.c)
- /* _Fgpos function -- UNIX version */
- #include <errno.h>
- #include "xstdio.h"
-
- /* UNIX system call */
- long _Lseek(int, long, int);
-
- long _Fgpos(FILE *str, fpos_t *ptr)
- { /* get file position */
- long loff = _Lseek(str-> _Handle, OL, 1);
-
- if (loff == -1)
- { /* query failed */
- errno = EFPOS;
- return (EOF);
- )
- if (str-_Mode & _MWRITE)
- loff += str->_Next - str->_Buf;
- else if (str->_Mode & _MREAD)
- loff -= str-> Nback
- ? str-> _Rsave - str-> _Next +
-
- str-> _Nback
- : str->_Rend - str->_Next;
- if (ptr == NULL)
- return (loff); /* ftell */
- else
- { /* fgetpos */
- ptr->_Off = loff;
- return (0);
- }
- }
-
- /* End of File */
-
-
- Listing 6 (xfspos.c)
- /* _Fspos function -- UNIX version */
- #include <errno.h>
- #include "xstdio.h"
-
- /* UNIX system call */
- long _Lseek(int, long, int);
-
- int_Fspos(FILE *str, const fpos_t *ptr, long loff, int way)
- { /* position a file */
- if (fflush(str))
- { /* write error */
- errno = EFPOS;
- return (EOF);
- }
- if (ptr)
- loff += ((fpos_t *)ptr)->_Off; /* fsetpos */
- if (way == SEEK CUR && str-> _Mode & _MREAD)
- loff -= str-> _Nback
- ? str-> _Rsave - str-> _Next + str-
- >_Nback
- :str->_Rend - str-> _Next;
- if (way == SEEK_CUR && loff != 0
- way != SEEK_SET loff != -1)
- loff = _Lseek(str->_Handle, loff, way);
- if (loff == -1)
- { /* request failed */
- errno = EFPOS;
- return (EOF);
- }
- else
- { /* success */
- if (str->_Mode & (_MREAD _MWRITE))
- { /* empty buffer */
- str->_Next = str->_Buf;
- str->_Rend = str->_Buf;
- str->_Wend = str->_Buf;
- str->_Nback = 0;
- }
- str->_Mode &= ~(_MEOF_MREAD_MWRITE);
- return (0);
- }
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Doctor C's Pointers(R)
-
-
- Data Structures, Part 9
-
-
-
-
- Rex Jaeschke
-
-
- Rex Jaeschke is an independent computer consultant, author and seminar leader.
- He participates in both ANSI and ISO C Standards meetings and is the editor of
- The Journal of C Language Translation, a quarterly publication aimed at
- implementors of C language translation tools. Readers are encouraged to submit
- column topics and suggestions to Rex at 2051 Swans Neck Way, Reston, VA 22091
- or via UUCP at rex@aussie.com.
-
-
- A stack is a list in which nodes can be added only at one end. Nodes can be
- accessed or removed only from that same end. When a new node is added it hides
- the previous end node, so in order to access that previous node the new node
- must first be removed. That is, the list works in a Last-In First-Out (LIFO)
- manner. The most recently added node is the first available for use, while the
- first node added is the last one available for use.
- An often used example of a stack is that of a stack of plates in a cafeteria.
- The plates are stacked one on top of the other inside a spring-loaded hopper.
- As you take a plate off the top of the stack the weight of the whole stack
- decreases and the spring raises the remaining stack up to the top of the
- hopper. Since the term stack implies some sort of vertical orientation we
- often talk about stacks having a top and a bottom. And the act of adding to
- the top is commonly referred to as pushing onto the stack while that of
- removing from the top is called popping off the stack.
- A stack need not have any particular orientation, but the terms push and pop
- are still typically used. For example, a paper napkin dispenser can push
- backward and pop forward; while a paper cup dispenser, as found at airplane
- water fountains, pushes upward and pops downward. In these mechanical stacks,
- push and pop operations typically do physically move the underlying stack.
- However, in a stack of memory this is not always the case. Although pushing
- onto a stack in a computer program results in a new top node being added, it
- typically will not affect the underlying nodes. To do so would be most
- inefficient and is equivalent to inserting and deleting a node from the
- beginning of an array.
-
-
- Some Applications
-
-
- Like other kinds of data structures, a stack has its strengths and weaknesses.
- When comparing one method of representation over another you must look at the
- kind of data being stored as well as the methods in which you will need to
- access it. Apart from storing plates, just how might a stack be used?
- Applications involving tree structures can lend themselves to using stacks.
- Consider a nested hierarchy that starts from the top and branches out below in
- a number of directions. Each of those directions branches out into yet other
- directions, etc. An example of such a model would be a program written in a
- procedural language such as Fortran, COBOL, Pascal, C, or C++. The main
- program calls subroutines which in turn call other subroutines, etc. A block
- structured language, such as Pascal or C, also allows nesting of blocks within
- a single subroutine. And C++ provides a complex data and function nesting
- capability via its inheritance mechanism.
- Such a language uses a stack by selecting a particular logic path in the
- hierarchy and following it down to its end branches, pushing things onto the
- stack as it goes. When it reaches the end of the path, it reverses direction,
- popping items off the stack as it traverses the path back to the beginning,
- where it takes another downward path.
- Computers handle a very common problem, that of solving arithmetic
- expressions, using a hierarchical organization. Consider the four binary
- operators: addition (+), subtraction (-), multiplication (*), and division
- (/). Mathematically, you can write arbitrarily complex expressions involving
- just these operators and their operands. For example,
- a + b - c * d + e / f - g
- Just as in mathematics, computer languages have rules for operator precedence.
- In the case of C and C++, multiplication and division have the same
- precedence. Their precedence is higher than that shared by addition and
- subtraction. To determine the precedence of two operators at the same
- precedence level, C and C++ provide the concept of associativity. Operators
- with the same precedence associate left to right. Therefore, in C and C++, the
- grouping of terms in the previous expression is interpreted as
- ((((a + b) - (c * d)) + (e / f)) - g)
- The tree in Figure 1 represents this expression, clearly showing the
- hierarchy.
- To evaluate such as expression, a C or C++ program needs to get to the leaves
- at the bottom of the branches. Going from left to right, it first evaluates a
- + b then saves the result in some temporary location. Next it evaluates c + d
- and subtracts the result from the value stored in the temporary location. It
- then stores the new result back in the temporary location. It continues to
- traverse the tree like this until it puts the final result back in the
- temporary location.
-
-
- A Primitive Expression Evaluator
-
-
- A C or C++ program can implement such an expression evaluator using a stack.
- It first pushes both operands on the stack, pops them off to form the result
- after applying their operator, and pushes that result back on the stack where
- it can be used later. So the value on the top of the stack represents the
- result of the expression processed thus far.
- A study of a primitive implementation of an expression evaluator begins with a
- look at the stack manipulation functions as shown in Listing 1.
- Assume all terms have values that can be represented correctly in a signed
- int. As such, the stack is an array of int and the size of the stack is
- determined by the macro STACK_TOP. The variable stack_ptr maintains the index
- of the next available slot on the stack. (It takes its name from the Stack
- Pointer, a special purpose hardware register used in many CPUs to maintain the
- current stack top.) Since the stack is initially empty, stack_ptr is
- initialized to 0. The type of stack_ptr is size_t, an unsigned integer type
- defined in stdio.h and other headers. Because the stack is not very large, the
- type int could just have easily been used. However, using size_t makes the
- solution maximally robust regardless of the value of STACK_TOP.
- Because the stack need only be directly accessed by the functions push () and
- pop (), it, along with the stack pointer, is static. This allows these two
- objects and two functions to be isolated from other source modules.
- To push a value on the stack, a C or C++ program first checks to see if the
- stack is full. If the stack is not full, the program pushes the new value on
- the stack and increments the stack pointer. Otherwise, it complains that the
- stack is full.
- To pop a value off the stack, a C or C++ program first checks to see if the
- stack is empty. If the stack is not empty, it removes the value and decrements
- the stack pointer. Note that when popping, a program does not actually remove
- a value from the array; it simply adjusts the stack pointer so that subsequent
- push operations can overwrite the values previously popped. If the stack is
- empty, the program complains, returning a dummy value of zero.
- The four functions in Listing 2 correspond to the four operators in Listing 1.
- Addition and multiplication are almost identical and they are the simplest.
- When applying an operator, a C or C++ program assumes that the two operands
- have already been pushed on the stack, left operand first. So to add two
- values, the program simply pops them off the stack, adds them together, and
- pushes the result back on the stack. Note that the order of evaluation of the
- two terms across the four binary operators is unspecified by C. That is, in
- pop() + pop(), it doesn't know which call to pop () will be made first. It so
- happens, that it doesn't matter for addition and multiplication, since these
- operators are commutative. That is, a + b is equivalent to b + a, and likewise
- for a * b and b * a.
- Because subtraction and division are not commutative, a program must know
- exactly which operand it is getting when calling pop(). To do this, it needs
- to have a sequence point between the two calls to pop(). The most common way
- to achieve this is to put them into different statements. This does, however,
- require the temporary variable temp.
- A useful feature for divide would be to check for a zero denominator and, on
- finding one, complain and return a zero value just like in the case of stack
- underflow.
- Listing 3 contains the main program that exercises these functions.
- Some sample inputs and outputs are
- Enter expression: 12 + 56
- Result = 68
- Enter expression: 23 - 40
- Result = -17
- Enter expression: 123 * 12
- Result = 1476
- Enter expression: 12345 / 7
- Result = 1763
- You might think the four operator functions are trivial enough they could be
- implemented as macros. The most obvious attempt at this results in something
- like Listing 4.
-
- The problem comes with subtract () and divide() where you need a temporary
- int. To get this you must make the macro definition a block and that precludes
- its use from a number of common places, including the main program in Listing
- 3. The fragment involving the call to subtract
- else if (op[0] == '-')
- subtract();
- else if (op[0] == '*')
- expands to the code in Listing 5, resulting in the true path of the if having
- two statements -- a block statement and a null statement. As such, the
- compiler complains about the dangling else that follows.
- The program in Listing 3 doesn't actually evaluate nested expressions, just
- one pair of operands and an operator at a time. The program in Listing 6 does
- more but only for a specific instance of the expression
- (a + b) * (c - d);
-
-
- Tokenizers And Parsers
-
-
- Handling arbitrarily complex expressions, as does a compiler or interpreter,
- requires machinery beyond the scope of this column. For example, it requires a
- routine to break free-form source into tokens. Such a routine is often called
- a tokenizer. While it is not too difficult to write your own, the lex utility
- (available with UNIX systems, as a third-party tool from MKS and others) and
- the public domain version Flex, can be used to automatically generate
- tokenizers.
- To handle all binary, unary, and ternary operators in arbitrarily complex
- expressions we need a parser. And again, while you could write your own, yacc
- is a commonly available parser generator (as are GNU's bison and Holub's occs,
- among others).
- If you have interest in pursuing tokenizing and parsing further you may wish
- to refer to the book The UNIX Programming Environment from Prentice-Hall by
- Kernighan and Pike. The whole of chapter 8 is dedicated to using these tools
- to implement a quite powerful calculator called hoc. And it uses a stack with
- push () and pop () functions along the way.
-
-
- Hardware Stacks
-
-
- Many computers have one or more stacks implemented in hardware. These are used
- to pass information into, and sometimes back from, a function. A common
- implementation of C involves allocating all automatic data on such a stack on
- entry to its parent function. In such cases though, the variables are not
- popped off the stack each time they are used. Instead, the compiler generates
- references to them using the stack pointer and an offset. For example, when
- the program
- void f(void)
- {
- char c[5];
- int i = 3;
- unsigned int u = 4;
-
- c[1] = 'A';
- c[3] = 'B';
- }
- is compiled by Borland's C compiler, the relevant part of the code generated
- is shown in Listing 7.
- Because automatic variables are often stored on a stack some interesting
- things can happen. Consider the program in Listing 8.
- The function f() is called three times in succession and each time on entry,
- the compiler allocates space on the stack for j and frees it on exit. It is
- very likely in this case that each time j is allocated, it occupies the exact
- same location in memory and so the contents it had from the previous iteration
- will be seen at the start of the next. As such, the output resulting could be
- j = 110
- j = 111
- j = 112
- Note though that when automatic data is allocated on the stack, it has no
- guaranteed default initial value. That is, while space is allocated on the
- stack for j it isn't really pushed. That would have resulted in an explicit
- value being placed there.
- A more interesting possibility is the following
- j = 0
- j = 1
- j = 2
- Here the garbage initial value just happens to be zero, as if j had been
- static.
- A smarter compiler would likely recognize that it can throw away the increment
- to j since j is automatic and, as such, does not retain its value across
- function calls. This then would result in something like
- j = 1515
- j = 1515
- j = 1515
- As we have said, even though values are popped off a stack, they still
- actually reside there. So when that stack space is allocated for another
- purpose, unless it is initialized, it will take on the residual value left
- from previous uses. And this can give rise to some interesting addresses when
- you allocate an automatic pointer without initializing it.
-
-
- Other Considerations
-
-
- The above solutions and discussion skip over or bypass a number of important
- considerations. First, the stack is limited to ints. In reality we would want
- something else. Certainly, an array of doubles significantly increases the
- possibilities, but it would lose precision on large integers. Changing push ()
- and pop() to handle double values is trivial. Try it. You might also think
- about having an array of unions where the union's member set comprises all the
- types you intend to store on the stack. You might also find it useful to
- record the current type stored in the union as well.
- By implementing the stack as an array you have a fixed limit on its size. And
- while you could obtain the array at runtime using malloc(), it still might be
- useful to have a more general solution. For example, you could implement a
- stack as a singly-linked list. The root always points to the top node and each
- node points forwards to the next. The stack pointer would then be the address
- of the next available node.
- When using a linked list, it might be inefficient to actually allocate a node
- on each push request and free one on each pop. It might be better to only
- allocate a new node when the current list is full but never free any. You
- might also wish to allocate nodes in clusters rather than to do them one at a
- time.
- Whatever approach you take to representing the stack, you should always have a
- way of detecting and handling stack overflow and underflow. In the DOS world,
- it is common to find C compilers calling a stack probe routine immediately on
- entry to a C function. The probe checks to see if allocating the automatic
- variables needed by this function will overflow the stack. If it will, the
- probe function terminates the program with a message to that effect. If it
- will not overflow, the probe returns, the space is allocated, and the program
- continues. If you disable this stack probe via a compiler option or a #pragma
- directive, watch out, since now stack overflow may well cause your system to
- hang.
- Figure 1
-
- Listing 1
-
- #include <stdio.h>
-
- #define STACK_TOP 50
-
- static int stack[STACK_TOP];
- static size_t stack_ptr = 0;
-
- void push(int value)
- {
- if (stack_ptr < STACK_TOP)
- stack[stack_ptr++] = value;
- else
- fprintf(stderr, "Stack full: discarding
- %d\n", value);
- }
-
- int pop(void)
- {
- if (stack_ptr > 0)
- return stack[--stack_ptr];
- else {
- fprintf(stderr, "Stack empty\n");
- return 0;
- }
- }
-
- /* End of File */
-
-
- Listing 2
- void add(void) { push(pop() + pop()); }
- void subtract(void) { int temp = pop(); push(pop() - temp); }
- void multiply(void) { push(pop() * pop()); }
- void divide(void) { int temp = pop(); push(pop() / temp); }
-
- /* End of File */
-
-
- Listing 3
- main()
- {
- char op[2];
- int i1, i2;
-
- while(1) {
- printf("Enter expression: ");
- if (scanf("%d %s %d", &i1, op, &i2) == EOF)
- break;
-
- push(i1);
- push(i2);
-
- if (op[0] == '+')
- add();
- else if (op[0] == '-')
- subtract();
- else if (op[0] == '*')
- multiply();
- else if (op[0] == '/')
-
- divide();
- else {
- fprintf(stderr, "Unknown operator >%s<\n", op);
- pop(); /* discard both operands */
- pop();
- push(0); /* substitute a zero result */
- }
-
- printf("Result = %d\n", pop());
- }
-
- return 0;
- }
- /* End of File */
-
-
- Listing 4
- #define add() (push(pop() + pop()))
- #define subtract() {int temp = pop(); (push(pop() - temp))}
- #define multiply() (push(pop() * pop()))
- #define divide() {int temp = pop(); (push(pop() / temp))}
-
- /* End of File */
-
-
- Listing 5
- else if (op[0] == '-')
- {int temp = pop(); (push(pop() - temp))};
- else if (op[0] == '*')
-
- /* End of File */
-
-
- Listing 6
- #include <stdio.h>
-
- main()
- {
- int a = 8, b = 2, c = 6, d = 3;
-
- push(a);
- push(b);
- add(); /* t1 = a + b */
- push(c);
- push(d);
- subtract(); /* t2 = c - d */
- multiply(); /* t3 = t1 * t2 */
-
- printf("Result = %d\n", pop());
-
- return 0;
- }
-
- Result = 30
-
- /* End of File */
-
-
- Listing 7
-
- ; void f(void)
-
- _f push bp ; save base ptr
- mov bp,sp ; copy stack ptr to bp
- sub sp,10 ; allocate 10 bytes for autos
-
- ; {
- ; char c[5];
- ; int i = 3;
-
- mov word ptr [bp-8],3 ; access i
-
- ; unsigned int u = 4;
-
- mov word ptr [bp-10],4 ; access u
-
- ; c[1] = 'A';
-
- mov byte ptr [bp-5],65 ; access c[1]
-
- ; c[3] = 'B';
-
- mov byte ptr [bp-3],66 ; access c[1]
-
- ; }
-
- mov sp,bp ; throw away 10 bytes
- pop bp ; restore bp saved on entry
- ret
- _f endp
-
- /* End of File */
-
-
- Listing 8
-
- #include <stdio.h>
-
- void f(void);
-
- main()
- {
- int i;
-
- for (i = 0; i <= 2; ++i)
- f();
- }
-
- void f(void)
- {
- int j;
-
- printf("j = %d\n", j);
- ++j;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Using The Conditional Operator ?:
-
-
-
-
- Ken Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language
- courses for corporations. He is the author of C Language for Programmers and
- All On C, and was a member on the ANSI C committee. He also does custom C
- programming for communications, graphics, image databases, and hypertext. His
- address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax
- questions for Ken to (919) 489-5239. Ken also receives email at
- kpugh@dukemvs.ac.duke.edu (Internet).
-
-
- Q
- Our company has banned the use of the conditional expression operator? :.
- What's wrong with it?
- Bill Malone
- Worcester, MA
- A
- This same question has floated around Usenet. As I see it, the problem is not
- with the operator itself, but with the potential for combining it into
- expressions which become indecipherable.
- The operator itself is not necessarily bad. In my C classes, I usually suggest
- avoiding it as there is the potential for confusion. I give the students an
- example such as:
- i = (7 > 5 ? (10 > 9 ? 4 : 7)
- : (3 > 1 ? 6 : 2);
- Most of the students take a few minutes to determine the value assigned to i.
- The solution can be found using ifs in only few seconds.
- if (7 > 5)
- if (10 > 9)
- i = 4;
- else
- i =7;
- else
- if (3 > 1)
- i = 6;
- else
- i = 2;
- The feeling of complexity is not universal. A group that I taught at Lotus
- found the conditional operator rather simple, since its form matches that used
- by 1-2-3 formulas.
- I once did a port of a C program from an IBM-PC to an Apple II. As the program
- was compiling, it aborted with an error message that it was out of expression
- space. The problem was with an expression that used the conditional operator.
- The creator of the program must have fallen in love with this operator. The
- expression had the operator nested ten deep. Luckily the compiler had an
- option to increase the expression space and I did not have to rewrite the
- statement.
- One example of the conditional operator that was discussed on the net looked
- like:
- strcpy (foo, x ? xvar : yvar);
- This was deemed preferable by some to:
- if (x)
- strcpy (foo, xvar);
- else
- strcpy (foo, yvar);
- Another example on the net looked something like Listing 1. Some responders
- liked the compactness of this statement. A proposed alternative for code
- similar to Listing 1 was code like that seen in Listing 2.
- Some people felt that Listing 2 was too wordy and had other problems. However,
- I would propose an even more complicated, but more maintainable organization
- (to my mind). This is shown in Listing 3.
- I feel Listing 3 is more maintainable for several reasons. First, the values
- of the string might be used in more than one place in the program. Keeping
- them in a data item makes them accessible to any statement that needs them.
- Second, this is closer to the organization which most windowed systems now
- employ. There is a separation of data from logic. The strings themselves could
- be read in from a resource file, if that were available. Notice that if you
- keep all your strings in the declaration portion of the program, a maintainer
- can easily find them for translation into another language or simply another
- phrase in the same language.
- The arguments on the net ranged around the ideas that "if it's in C, then a C
- programmer should use it. If you don't understand it, then you shouldn't be
- programming in C". My response to that is that I can create monsters of
- expressions that are indecipherable using C operators. Just because a feature
- is there does not mean I have to abuse it.
- Q
- The K&R Book says (on page 37) that all automatic variables are initialized
- each time the function is entered. I have a piece of a program below in which
- p is initialized only once. Please explain what I am missing here.
- The answer that I can think of is that the following two declarations
- char *p = "Old Value" ;
- and
- static char *p = "Old Value" ;
- are equivalent. Are they really? Also this behavior is not observed for other
- types of pointers.
- Abhay B. Joshi
- Syracuse, NY
-
- A
- The two declarations are sort of equivalent. Regardless of which one you use
- you will get the same value. The variable p is a pointer to char. The string
- "Old Value" is a string constant, or as I like to describe it, an unnamed
- array of chars identified by an address.
- When you initialize a pointer to char with a string, the address of that array
- of char is stored into the pointer. The difference between your static and
- non-static declarations is that this initialization takes place either every
- time the function is entered (for the automatic variable) or just once (for
- the static variable). If your function looked like
- example_function()
- {
- char *p = "Old Value" ;
- static char *ps = "Old Value";
- printf("p is %s ps is %s", p, ps);
- p = "Another Value";
- ps = "Another Value";
- }
- then the first time through it would print Old Value for both variables. Each
- subsequent time, it would print Old Value for p and Another Value for ps.
- Now let's take a look at what your main function is doing. getstr() returns
- the value of p, which contains the address of Old Value. You perform a strcpy.
- This copies New Value to the address where Old Value is stored. You have wiped
- out the constant string.
- The next time getstr() returns, it returns the same address. But the contents
- of that address is now New Value.
- Q
- How does one truncate a file under UNIX. I'm looking for a routine similar to
- DOS's chsize() routine that will allow me to set the size of a file. The size
- that I pass to the function needs to be smaller than the length of the file
- itself (i.e., shorten it)
- Would appreciate any snippets of code, etc. Thanks.
- Brooks Cutter
- Largo, FL
- A
- BSD UNIX has ftruncate(fd, length), which works just like chsize( ). It also
- has truncate(path, length), which applies to a non-opened file. However, if
- you are using System V, that doesn't help too much. I suggest something like
- Listing 5, which works on a non-opened file. If you do not need to worry about
- multiple links to the file, then you could simplify it by eliminating the copy
- back to the original file. You would unlink the original file and then rename
- the temporary file.
- To avoid a lot of nested logic, I have used gotos. gotoless coders are invited
- to send in their own version. Using multiple return statements counts the same
- in my book as using gotos.
- Q
- In the report provided byCHKDSK there is something called "Volume Serial
- Number". The format utility also reports this same thing and evidently format
- creates a new volume serial number each time a disk is formatted. My question
- is this, where is that serial number stored and what is its intended use?
- Rob Buck
- Fairfield, IA
- A
- I can give you an exact quote from, the MS-DOS Programmer's Reference Manual.
- I couldn't say it better myself.
- "To help distinguish one removable disk or tape from another, the format
- command creates a unique identifier for each volume... as it formats the
- volume. Programs can also create their own unique identifiers by using Set
- Media ID (Interrupt 21h Function 440Dh Minor Code 46h) to set the volume
- label, volume serial number, and file-system type.
- "Since the user can change the volume in a removable-media drive at any time,
- programs that read from or write to removable media need ways to prevent
- inadvertently reading from or writing to the wrong volume. Some drives have
- change-line capability that helps MS-DOS automatically detect media changes...
- If a drive does not have change-line capability, MS-DOS checks for the proper
- volume before read and write operations"
- I'm not sure which versions of MS-DOS really do this volume checking. Version
- 3.2 does not (at least how I would expect). You can do a simple check by
- typing
- more < a:long_file
- At the pause, remove the disk and put in a different one. You should get
- either garbage (after a few more screenfuls) or a warning message. Version 3.2
- gave me garbage.
-
-
- Readers' Responses
-
-
-
-
- floats And doubles
-
-
- I just finished reading your response to my previous note in the September C
- Users Journal, and I'm afraid we're not communicating. Consider the following
- code
- sub1()
- {
- float f;
- sub2(f);
- }
- sub2(newf)
- float newf;
- {
- sub3(&newf);
- }
- Now, according to both K&R and ANSI, when sub1 calls sub2, f is converted from
- a float to a double and the double is passed to sub2. The key question is --
- exactly what happens in sub2?
- The declaration of newf is float, but the parameter being passed is actually a
- double, so what happens? K&R says that the declaration is "adjusted to read
- double." Thus, despite the fact that the programmer declared newf as a float,
- the compiler takes it upon itself to quietly "adjust" that declaration and
- thus make newf a double instead of a float.
- In this case, sub3 gets passed a pointer to double which is clearly not what
- the programmer intended. A number of compiler implementers adopted an
- alternative solution to the conflict. Rather than adjusting the declaration of
- new, they actually convert the double parameter back into a float.
- That is, they behave as if the function had been written,
- sub2(temp)
-
- double temp;
- {
- float newf = temp;
- sub3(&newf);
- }
- In this case sub3 is passed a pointer to float as the programmer intended, but
- there may be extra work involved in converting the double back into a float.
- (There also may not be. Some floating point formats allow a double to be
- "converted" to a float by just ignoring the extra bytes.)
- Since this approach seems to be more faithful to what the programmer asked
- for, it is the one that was adopted for the ANSI standard. (Although I believe
- that the "asif" rule would still allow the adjustment as long as the variable
- never has its address taken.)
- Larry Jones
- Milford, OH
- I think we are in agreement on this, except for a particular compiler's
- implementation of K&R. The question revolved around the Microsoft compiler.
- For your example, which uses the K&R style function header, Microsoft treats
- the parameter declaration as if it were stated as double. The variable newf is
- eight bytes long.
- If you declare it with the new style function header as:
- sub2(float newf)
- {
- sub3(&newf);
- }
- it would be truly a float.
- In this case, you would need to include a prototype for sub2 for the calling
- routine, e.g.:
- int sub2(float newf);
- This would prevent the default widening of f in sub1. If the prototype is not
- there, then the function may fail (ANSI Rationale 3.3.2.2).
- As stated in the Rationale section 3.7.1, this type rewriting of parameters
- from float to double is no longer permissible. However Microsoft appears to
- have left the old style alone. This is non-ANSI behavior and should not be
- allowed if the compiler is run in ANSI-compliant mode. (KP)
-
- Listing 1
- printf("The value is '%s' \n", (x == 0 ? "zero" :
- (x == 1 ? "one" :
- (x == 2 ? "two" :
- "lots")));
-
- /* End of File */
-
-
- Listing 2
- char *out_string;
- switch (x)
- {
- case 0:
- out_string = "zero";
- break;
- case 1:
- out_string = "one" ;
- break;
- case 2:
- out_string = "two" ;
- break;
- default:
- out_string = "lots";
- break;
- }
-
- printf ("The value is '%s'\n", out_string)
-
- /* End of File */
-
-
- Listing 3
- int index;
- #define SIZE_OUT_STRINGS 4
-
- char *out_strings[SIZE_OUT_STRINGS]
- = {"zero", "one", "two", "lots"};
-
-
- if (x < 0 x >= SIZE_OUT_STRINGS - 1)
- index = SIZE_OUT STRINGS - 1;
- else
- index = x;
- printf ("The value is '%s'\n", out_strings[index]);
-
- /* End of File */
-
-
- Listing 4
- char *getstr();
- main()
- {
- char *str ;
- printf("%s\n",str = getstr()) ; /* prints "Old Value" */
- strcpy(str,"New Value") ;
- printf("%s\n",str = getstr()) ; /* prints "New Value" */
- }
-
- char *getstr()
- {
- char *p = "Old Value" ;
- return p ;
- }
-
- /* End of File */
-
-
- Listing 5
- #include <stdio.h>
- #define SIZE_BUFFER 1024
- #include <sys\types.h>
- #include <sys\stat.h>
- #ifdef BSD
- #include <sys\file.h> /* This header name varies */
- #else
- #include <fcntl.h>
- #endif
- static int copy_files();
-
- truncate_file(path, length)
- /* Truncates a file to the length specified */
- char *path;
- int length;
- {
- int input_file, temp_file;
- char temp_file_name[L_tmpnam];
- struct stat file_status;
- int ret;
-
- ret = 0;
- if (length < 0)
- {
- ret = -10;
- goto end;
- }
-
- /* If 0 length, then just open/close it */
-
- if (length == 0)
- {
- input_file = open(path, 0_WRONLY 0_TRUNC, 0666);
- if (input_file < 0)
- {
- ret = -1;
- goto end;
- }
- close (input_file);
- goto end;
- }
-
- input_file = open(path, 0_RDONLY, 0);
- if (input_file < 0)
- {
- ret = -1;
- goto end;
- }
-
- /* Check the file length - no sense copying if shorter already */
- fstat(input_file, &file_status);
- if (length > file_status.st_size)
- {
- close (input_file);
- ret = -3;
- goto end;
- }
-
- /* Open a temporary file to hold the contents to be copied back */
- tmpnam(temp_file_name);
- temp_file = open(temp_file_name, 0_RDWR 0_CREAT, 0666);
- if (temp_file < 0)
- {
- ret = -2;
- goto end;
- }
-
- /* Make a copy of the file */
- ret = copy_files(input_file, temp_file, length);
- if (ret == 0)
- {
- lseek(temp_file,0L,0);
- close(input_file);
- input_file = open(path, 0_WRONLY 0_TRUNC, 0666);
- if (input_file < 0)
- ret = -12;
- else
- {
- ret = copy_files(temp_file, input_file, length);
- close(input_file);
- }
- }
-
- close (temp_file);
- unlink(temp_file_name);
-
- end:
- return ret;
- }
-
-
- static int copy_files(file_in, file_out, length)
- /* Copies from file in to file out */
- int file_in;
- int file_out;
- int length;
- {
- int ret;
- char buffer[SIZE_BUFFER];
- int read_length;
- int write_length;
- int length_to_read;
- int length_to_write;
- ret = 0;
-
- while (length > 0)
- {
- if (length > SIZE_BUFFER)
- length_to_read = SIZE_BUFFER;
- else
- length_to_read = length;
- length -= length_to_read;
- read_length = read(file_in, buffer, length_to_read);
- if (read_length != length_to_read)
- {
- ret = -4;
- goto end;
- }
- length_to_write = read_length;
- write_length = write(file_out, buffer,
- length_to_write);
- if (write_length != length_to_write)
- {
- ret = -5;
- goto end;
- }
- }
- end:
- return ret;
- }
-
- main()
- {
- printf("\n Ret is %d", truncate_file("a:temp", 1));
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On The Networks
-
-
- Where To Get The Sources
-
-
-
-
- Sydney S. Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, lecturer, author,
- professor, and president of Datacomp Systems, Inc., a consulting and contract
- programming firm specializing in databases, data presentation and windowing,
- transaction processing, networking, testing and test suites, and device
- management for UNIX and MS-DOS. He can be contacted care of Datacomp Systems,
- Inc., 3837 Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail
- on the Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
- Internet addressing).
-
-
- Another year has gone by, and once again, it's time to update my general
- information column. In January 1990 (CUJ Vol. 8, No. 1), I wrote about the
- internet, Internet, USENET, and Network News. In January 1991 (CUJ Vol. 9, No.
- 2), I wrote about obtaining USENET Network News. Both of those two columns are
- still valid, and I refer you to your libraries of back issues. (Maybe next
- year it will be time to update them once again).
- However, as a quick review of what was covered, this column normally reports
- on freely distributable software that has recently been released via USENET
- Network News. Freely distributable means that one can freely (and for free)
- make copies of the software, use it as they see fit, and give it away as they
- desire. It does not mean the software is in the public domain. Most of the
- software is copyrighted. This means you cannot pretend you wrote it, or
- include code from it in a product you are selling. However, the authors have
- allowed you to use and distribute it for free. If you make changes, most
- authors do not let you call the changed version by the name of the original.
- This is to avoid confusion as to what is and is not part of the product and to
- reduce the authors' support headaches.
- The sources mentioned in this column are released via several groups
- distributed as part of USENET Network News. USENET Network News performs two
- roles. It is a method of distributing information among a very large group of
- computers, and it is also a somewhat organized collection of that same
- information into news groups that are distributed via the news software.
- First, the software. The current version of the software is named C News,
- because it follows A News and B News as the third rewrite of the transport
- software (not because it was written in C). It supports transfer of the news
- articles (the individual messages) between every member of the USENET network.
- At present there are about 40,000 computers exchanging network news, and over
- two million readers. News works by transmitting all articles that a site
- wishes to read (its subscription list) to that site. Unlike CompuServe, or a
- BBS system, users read the articles directly on their own computer. Thus you
- do not need to dial into anywhere to read News, if your computer is a member
- of the network, you read news directly on your own computer. (For large sites
- this is a simplification, they generally designate a single computer in their
- local network as the news host and then use news reader software that
- transparently accesses that news host as if it was local.)
- To achieve this, News uses a flood algorithm. Each member of the network only
- communicates with a few neighbors (not all 40,000 members). When the member
- site receives an article from one of its neighbors, it then sends it to all of
- its other neighbors that have not already seen that article. This method
- allows the article to be posted (created) at any individual site, and appear
- at all sites in a very short time. Major backbone sites exchange news via
- NNTP, the Network News Transfer Protocol, running on top of TCP/IP over the
- Internet. This allows for very fast exchange of the messages and most backbone
- sites have received a new posting within minutes of its entry onto the
- network. Smaller sites usually use the UNIX UUCP protocol to exchange the
- information, although there is no requirement that the UUCP protocol be used.
- At present over 25MB of News are exchanged daily.
- Twenty-five megabytes per day is too much for all but the major backbone sites
- to handle, so most sites no longer get a "full feed." Instead, they only
- receive a limited selection of the newsgroups. This brings us to the second
- item, the organization of the information in News.
- News articles are separated into divisions called newsgroups. Each division is
- supposed to limit itself to a single topic, and the name of the group is
- supposed to give you some idea as to the content of the group. These groups
- are then organized into hierarchies of related topics. USENET Network News
- started out with just two hierarchies, mod and net. The mod hierarchy had
- those groups that had a person as the moderator to edit and control the
- information. The net hierarchy handled all other groups. With the release of B
- News and its ability to have any single group be moderated or open, the great
- renaming was undertaken. So, for the last several years, News has consisted of
- seven main or official hierarchies: comp (related to computers or computing),
- misc (anything that didn't belong elsewhere), news (related to network news
- itself, the software, distribution, groups or its administration), rec
- (recreational topics), sci (scientific topics), soc (social groups), and talk
- (discussion on controversial topics). In addition, there are several "private"
- hierarchies including: bionet (biological science including the Human Genome
- project), bit (bitnet sites and mailing lists, bitnet is another academic
- network that is not the Internet), biz (business, a commercial hierarchy),
- clari (paid subscription news including UPI style information), ddn (Defense
- Data Network reports), gnu (reports from the Gnu's Not Unix project), ieee
- (Institute for Electrical and Electronic Engineers), u3b (the AT&T 3B computer
- line) and vmsnet (items of interest for DEC Vax/VMS users). There are also
- unofficial hierarchies including the alt (alternate) hierarchy and a whole
- host of regional hierarchies that specialize in regional news. At present
- there are over 1800 different newsgroups to choose from.
- This column normally reports on articles in the comp.sources and alt.sources
- sub-hierarchies. These include: comp.sources.games, comp.sources.misc,
- comp.sources.reviewed, comp.sources.unix, and alt.sources. Each of these is an
- individual news group. All but alt.sources are moderated. For the moderated
- groups, the authors of the software submit their packages to the moderator for
- posting. Each group has its own rules for acceptance. Alt.sources is
- unmoderated and a free-for-all. Sources in that group are posted directly by
- the author.
-
-
- What Happens Next?
-
-
- With that much information flowing into each site every day, most sites cannot
- keep the information on their local disks for very long. Usually only a couple
- of days. So, by the time you ready my articles, the sources have been deleted
- from the machines in the network to make room for newer articles. So, what
- good does it do to report on what was available (or that is no longer
- available)?
- Most of the information posted on News is worth deleting in even less time
- that it remains on the local disk. Source code, however, is not one of those
- low value items. Many sites desire to keep it around so they and others can
- make use of it. What those sites do is archive the packages so others can
- access them. These sites are called, archive sites (surprise!). Since the
- moderated source groups are purely source postings, with no other traffic, the
- archiving task can be pretty automatic and many sites around the world have
- agreed to do it.
- Alt.sources is a different problem. Since it's a free-for-all, often items
- other than source code end up in the group, and it takes more work to archive
- it. Most sites do not archive this group. However, since the better postings
- in alt.sources are previews of items to be posted later in one of the
- mainstream moderated groups, there is hope that the source will be available
- in those group archives.
- The problem then, is finding out which sites archive which groups, and how to
- access these archives. I would like to give credit to Jonathan I. Kames of the
- Massachusetts Institute of Technology for gathering much of the data on how to
- find the sources and posting it as a "Frequently Asked Questions" article to
- the comp.sources.wanted newsgroup and the new moderated newsgroup
- news.answers.
-
-
- How To Find Sources
-
-
- First you have to decide what it is you are trying to find. If it's one of the
- source postings mentioned in my column, I always list the volume and Issue
- numbers of the articles in the moderated groups, or just the posting date for
- alt.sources. I also list a name in italics, this is the archive name of the
- posting. It's a big help in finding the files once you find a site that
- archives the information.
-
-
- OK, Here Are The Steps:
-
-
- 1. Figure out in what group, Volume, and Issue the posting appeared. Also try
- and determine its archive name. If you know these items, it's usually easy to
- find an archive site that keeps that group. Most archive sites keep their
- information in a hierarchy ordered first on the group, then on the volume, and
- last on the archive name. These together usually make up a directory path, as
- in comp.sources.unix/volume22/elm2.3. In that directory you will find all of
- the articles that made up the 2.3 release of the ELM Mail User Agent that was
- posted in Volume 22 of the comp.sources.unix newsgroup. If you do not know the
- archive name, but do know the volume, each volume also has an Index file that
- can be retrieved and read to determine the archive name. One common publicly
- accessible archive site for each of the moderated groups in this article is
- UUNET.
- 2. If you do not know which sites archive the groups, or if any site is
- archiving that item, even though they are not archiving the entire group,
- consult Archie. (CUJ August 1991, Vol. 9, No. 8). Archie is a mail response
- program that tries to keep track of sites reachable via FTP (file transfer
- protocol, a TCP/IP protocol used by internet sites) that have sources
- available for distribution. Even if you cannot access the archive site
- directly via FTP, it is worth knowing that the archive site exists because
- there are other ways of retrieving sources only available via FTP.
- 3. If you know the name of the program, but do no know what group it was
- posted in, try using Archie and search based on the name. Since most sites
- store the archives by group and volume, the information returned will tell you
- what newsgroup and volume it was posted in. Then you can retrieve the item
- from any archive site for that newsgroup.
- 4. If you do not know the name, but know you are looking for source code that
- performs a particular task, retrieve the indexes for each of the newsgroups
- and see if any of the entries (usually listed as the archive name and a short
- description of the function) look reasonable. If so, try those. Or, make a
- query to Archie based on some keywords from the function of the software and
- perhaps it can find items that match. If the task is a mathematical item or
- algorithmic program that is commonly solved by computer, checkout the netlib
- archive at AT&T (mentioned later in this column) available via both FTP or
- electronic mail.
-
-
- How To Transfer The File
-
-
- Ok, you have now found the machine that has the software you need, how do you
- actually get it back to your machine?
- First you have to determine what access methods the archive machine allows to
- retrieve software. Most archive sites are internet based, and support the FTP
- service. If you have access to FTP on the internet, this is the easiest, and
- fastest way of retrieving the sources. If you don't, perhaps a local college
- is a member of the internet and can assist you in using FTP to retrieve the
- sources you need.
- Other sites support anonymous UUCP access. This is access via the UUCP
- protocol where you don't need to register in advance to call in. UUNET
- Communications Services supplies this using the (900) GOT-SRCS number at
- $.40/minute for non subscribers. Many other archive sites provide it for just
- the cost of your long distance telephone call. If you cannot use FTP, this is
- the next best method to use.
- Many anonymous UUCP archive sites list what they carry in the Nixpub listing
- of public access Unix sites maintained by Phil Eschallier. The Nixpub listing
- is posted in comp.misc and alt.bbs periodically. If you don't get News and
- need a copy, it can be retrieved via electronic mail using the "periodic
- posting mail based archive server." This is run by MIT on the system
- pit-manager.mit.edu. To use the server, send an electronic mail message to the
- address mail-server@pit-manager.mit.edu with the subject "help" and it will
- reply with instructions on how to use the server.
- If you are not on USENET, sending electronic mail to sites on the internet is
- also possible via the commercial mail services. CompuServe, MCI-Mail, ATT-Mail
- and Sprint-Mail all support sending messages to Internet addresses. Contact
- the support personnel at the commercial mail service you use for details on
- how to send messages to Internet addresses.
- The last way to access the sources is via electronic mail. Several sites also
- make their archives available via automated mail-response servers. Note, this
- can be a very expensive way of accessing the information, and due to the load
- it places on the networks, most archive servers heavily restrict the amount of
- information they will send each day. This can lead to long waits for access to
- the source you are trying to retrieve. The following are some of the sites
- that maintain mail based archives:
-
- hrc!archives: This site requires that you issue two commands to get the help
- message. Place these lines in the body of your electronic mail message:
- send path <address>
- send help
- The <address> should be a UUCP path from a well known site to your mailbox.
- netlib@uunet.uu.net: E-mail access is provided to most of the sources archived
- by UUNET. Place the word "help" in the body of your message to receive
- information on using the server.
- ftpmail@decwrl.dec.com: Digital Equipment Corporation runs a mail based
- archive server that will retrieve sources via FTP and then mail them to you.
- To find out how to use this service, send it a message with the word "help" in
- the body.
- netlib@research.att.com: All of the algorithmic and mathematical software
- available via ftp in the netlib archives at AT&T is also available via
- electronic mail. Again, send a message with the word "help" in the body for
- further instructions on using the service.
- There are also other servers that specialize in particular sources. Send a
- message to these with the word "help" in the body for further information.
- Selected entries from Jonathan's posting in comp.sources,wanted are:
- archive-server@ames.arc.nasa.gov: Space archives (also accessible via
- anonymous ftp to ames.arc.nasa.gov)
- archive-server@athena-dist.mit.edu: MIT Project Athena papers and source code
- (also accessible via anonymous ftp to athena-dist.mit.edu)
- archive-server@bcm.tmc.edu: UUCP maps, source-code for BCM WHOIS database, NFS
- and PC-NFS information and source-code, Unisys U-series information and source
- code, other stuff
- archive-server@cc.purdue.edu: NeXT stuff (also accessible via anonymous ftp to
- sonta.cc.purdue.edu or nova.cc.purdue.edu)
- archive-server@chsun1.uchicago.edu: Computer Underground Digest and references
- archive-server@cs.leidenuniv.nl: IPX, patch for MS-DOS, sps diffs for SunOS
- 4.1
- archive-server@dsi.com: elm, patch
- archive-server@ecletic.com: Mac-security digest, information about Eclectic,
- other stuff
- archive-server@ncsa.uiuc.edu: NCSA stuff, especially telnet and tcp for MAC
- and PC compatibles.
- archive-server@rice.edu: Sun-spots, sun-source and sunicons, plus other
- software written or influenced by people at Rice (also accessible via
- anonymous ftp to titan.rice.edu)
- archive-server@sun.soe.clarkson.edu: IBM and other good stuff (also accessible
- via anonymous ftp to sun.soe.clarkson.edu)
- info-server@cl.cam.ac.uk: Various random stuff, including bmx, btoa, c-nrs,
- gdb, soft-gen, spad, top, unix-niftp, ups (Unix PostScript interpreter)
- info-server@doc.ic.ac.uk: USENET source newsgroups, GNU, X11, news software,
- other stuff
- info-server@hp4nl.nluug.nl: Macintosh, Sun, IBM-PC, Unix sources, some
- documents, GNU, graphics, USENET archives (or lots of newsgroups), X window
- system, TeX, programming languages (LISP, ICON, ABC, others), news sources,
- network sources, other stuff
- mail-server@cs.ruu.nl: GIFs, Atari ST software, random documentation, ELM
- sources, USENET FAQ postings, GNU software HP-UX software, NN sources, SGI
- software, TeX software and TeXhax and TeXmag archives, random UNIX software,
- X11 software, other stuff (also accessible via anonymous ftp to
- praxis.cs.ruu.nl
- mail-server@rusmv1.rus.uni-stuttgart.de: German TeX archives; benchmarks,
- journal indices, RFCs, network info, UNIX info; X, mac, pc, sun, aix, vax, and
- other software (also accessible via anonymous ftp to
- rusmv1.rus.uni-stuttgart.de)
- mailserv@garbo.uwasa.fi: Frequently asked questions in various areas, some
- USENET source archives, some PC software archives
- netlib@draci.cs.uow.edu.au: Australian Netlib (also accessible via anonymous
- ftp to draci.cs.uow.edu.au)
- netlib@ornl.gov: Similar to the AT&T netlib archive
- netlib@ukc.ac.uk: UK netlib server (mostly same contents as AT&T's netlib)
- (some files also accessible via anonymous ftp to harrier.ukc.ac.uk {username
- guest})
-
-
- Very Important
-
-
- It is considered very poor form to use a mail based archive server if any
- other method is available to you. In addition, accessing any service that is
- on a different continent than your own via electronic mail is also frowned
- upon. It is very expensive to transmit information across the oceans. If you
- use electronic mail via one of these archive-servers, you are forcing someone
- else to pay those charges. Abuse of this will cause the sites to remove their
- archive servers, which will hurt everyone. So, please, those in the United
- States and Canada, please restrict your accesses to those servers in the US
- and Canada, and those in Europe to those in Europe, and those in Australia, to
- those in Oz.
- But how do I tell where the site is located? Internet addresses do give you a
- clue. You can tell the country an Internet named site (those with @ in their
- addresses) is located in by the abbreviation at the end of the address string.
- Thus .ca is Canada, .ge is Germany, and .au is Australia. Please don't spoil
- access for everyone else (and your own future access) by tieing up the
- transoceanic links with mail based archive server traffic.
- Hopefully, this special edition of my column has given you a hint as to how to
- track down the sources. Note, I have been asked many times if I can make
- floppies or tapes containing the software mentioned in my column. I cannot
- spare the time to do this. I also have to work for a living, and if I started
- doing this, I could easily spend all my time trying to fulfill the requests
- and never get any of my work done.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- Well, it's a new year. I am somewhere in my second year of editing The C Users
- Journal. R&D Publications has moved to new quarters, which I have yet to
- visit. And the C programming language is drifting into its third decade of
- existence.
- I must say that I have mostly enjoyed editing this magazine. Working via
- e-mail from Australia was often a challenge. Some issues I feel I could have
- polished better with a bit more effort. But an occasional kind word from a
- reader goes a long way in this business. The hardest part for me is reading
- the other kind of letters. I find that a little criticism also goes a long
- way. The trick is not to overreact, in either direction.
- R&D seems to be prospering as well, at least from my vantage point as a
- semi-insider. Besides The C Users Journal, they also put out several other
- quality technical publications. (These are not as important as CUJ, of course,
- but you might want to check them out anyway.) And they run The C Users' Group
- and The C Users Bookstore, two valuable services. I expect all of these
- endeavors to become more important as the C community grows.
- In the early 1970s, that community was confined to two floors of one building
- at Bell Labs, Murray Hill, New Jersey. To say that it has grown is the
- grossest of understatements. C is ubiquitous. It is probably the programming
- language in widest use today. We all know that C is not perfect. That's one
- reason why people keep tinkering with it and extending it. But the list of
- successful products written in C keeps growing.
- Not all my reflections on past and future are equally rosy. Certainly, the
- world is wallowing through times of economic uncertainty. Not even the rapidly
- growing computer business is immune to setbacks. And economic hard times have
- a way of depressing everything else.
- Still, it's a new year. I can't help but look on the coming months with
- optimism, at least for CUJ, R&D, and C. I hope you can too.
- P.J. Plauger
- pjp@plauger.uunet
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- Supercomputer For The 90s
-
-
- Cray Research, Inc. has introduced a parallel vector computer system, the CRAY
- Y-MP C90 supercomputer. The system has 16 central processing units (CPUs), and
- on actual customer problems, operates four times the speed of Cray Research's
- previous fastest supercomputers.
- The CRAY Y-MP C90 system features a CPU with a peak performance of one billion
- floating-point operations per second (Gflop/s). With 16 of these powerful CPUs
- and 256 megawords (2 gigabytes) of central memory, the CRAY Y-MP C90 system
- has a peak performance of 16 Gflop/s.
- The CRAY Y-MP C90 system uses a balanced parallel, vector-scalar architecture
- to maximize sustained performance. A significant feature of the CRAY Y-MP C90
- system is a dual-vector pipeline that allows each of the system's 16 CPUs to
- deliver two vector results per functional unit every clock period. With its
- 64-way parallelism and efficient multiprocessing capabilities, the CRAY Y-MP
- C90 system can deliver a total of 64 vector results per clock period, or four
- times that of Cray Research's previous top-end systems.
- For more information contact Cray Research, Inc., 655A Lone Oak Dr., Eagan, MN
- 55121, (612) 683-7100.
-
-
- New Generation Of Anti-virus Software
-
-
- Fifth Generation Systems, Inc. has released Untouchable, a line of anti-virus
- software that provides virus protection without the need for frequent virus
- signature updates. Untouchable stand-alone and network products are designed
- to operate on DOS-based PCs, compatibles, and networks.
- Untouchable consists of three distinct anti-virus technologies that constantly
- interact to detect and eradicate known and unknown viruses. The file scanner
- incorporates the principal of modification detection. This
- mathematically-proven technique safeguards against known and unknown virus
- threats by making use of file signatures rather than the commonly-used virus
- signatures. A TSR monitor checks system memory for known boot sector and
- partition table viruses.
- In addition, a virus scanner/remover can detect and eradicate hundreds of
- known viruses.
-
-
- Untouchable Network
-
-
- Untouchable Network provides Novell LAN administrators with the ability to
- centrally monitor and control the entire network for viruses
- The program uses the same methods of protection as the stand-alone version,
- but incorporates enhanced LAN management features. Untouchable Network
- currently provides its enhanced LAN management facilities for the Novell
- Netware operating system. A network savvy version of the product, without the
- LAN management features, is available to run on Microsoft LAN Manager, Banyan
- Vines, 3Com, and other PC network operating systems.
- Untouchable is available for $165. An Untouchable Network Starter Kit is
- available for $695. The starter kit includes the server package of full
- supervisor documentation and software and ten node license agreements. License
- agreements for additional nodes may be purchased for $85 per node.
- While Untouchable does not require virus signature updates to ensure complete
- protection against undiscovered new viruses, Fifth Generation Systems
- recognizes that some users may want them. The company will provide quarterly
- virus signature updates for the virus scanner and TSR monitor, for a service
- charge of $15 per quarter.
- For more information contact Fifth Generation Systems, 10049 N. Reiger Rd.,
- Baton Rouge, LA 70809, (504) 291-7221.
-
-
- ROMable Operating System
-
-
- Datalight has released a new version of ROM-DOS, the small, low-cost,
- modifiable operating system that can run from within ROM or on a floppy or
- hard disk. The new version of ROM-DOS is compatible with MS-DOS v3.31 and has
- many added features such as support for Datalight FLASH Memory Disk,
- Installable File Systems, configurable memory disks and hard disks up to 512
- Megabytes.
- ROM-DOS compatibility with common desktop DOS has been certified and
- documented by VeriTest, an independent testing agency. VeriTest successfully
- ran a collection of 28 widely used MS-DOS applications under ROM-DOS,
- including: AutoCAD, dBASE IV, Freelance Plus, Lotus 1-2-3, Microsoft Word,
- Microsoft Works, Multimate, Paradox, R:BASE, Sidekick, Turbo Pascal, Ventura
- Publisher, and Word Perfect.
- ROM-DOS allows a developer to write the application program in the desired
- language, then convert the program into a ROMable EXE (named RXE) that will
- execute the code directly from ROM. The RXE is loaded into ROM along with
- ROM-DOS to run in the target system with no further modifications required.
- Developers can place the memory intensive program code in ROM and use valuable
- RAM space only for the remaining program data.
- ROM-DOS resides in about 34K ROM, and uses as little as 10K RAM when running.
- ROM-DOS can be further minimized when customized to include only specifically
- needed functions. ROM-DOS can also be run with Datalight's mini-BIOS which
- requires less than 3K of ROM. The ROM-DOS Developer's Kit contains the tools
- and utilities needed to port ROM-DOS to the developer's choice of hardware
- platform. Also included are numerous DOS utilities, source code for all device
- drivers, and source code for the mini-BIOS.
- ROM-DOS can be purchased in quantity for as low as $5 per copy. The
- Developer's Kit is available for $495. License to the source code is available
- for $10,000. For more information contact Datalight, 17455 - 68th Ave NE,
- Suite 304, Bothell, WA 98011, 1-800-221-6630.
-
-
- UNIX SVR4 For Independent Software Vendors (ISVs)
-
-
- Motorola Inc. has released its enhanced UNIX System V Release 4, v3
- (SVR4)-compatible operating system. UNIX SYSTEM V/88 Release 4.0, v3.1
- supports Motorola's MC88000 based reduced instruction set computer (RISC)
- systems architecture and complies with UNIX Systems Laboratory's Application
- Binary Interface (ABI); X/Open's Portability Guide, Issue 3 (XPG) Issue 3)
- from X/Open; and Binary Compatibility Standard (BCS) and Object Compatibility
- Standard (OCS) criteria established by the 88open Consortium. This
- compatibility provides seamless migration from Motorola SYSTEM V/88 Release
- 3.2 v2 and v3.
- UNIX SYSTEM V/88 Release 4.0, v3.1 includes the BOS, NSE and DeltaWINDOWS 1.2.
- The early access release is now shipping to ISVs for development of
- off-the-shelf software applications and other key customers of the MCG.
- For more information contact Motorola, Inc. Computer Group, 2900 South Diablo
- Way, Tempe, AZ 85282, (602) 438-3576.
-
-
- Improved Laser Printer Output And Mouse Event-driven GUI's
-
-
- Clarion Software is now shipping v4. 1 of its Graphics Language Extension
- Module (LEM) for the Clarion Professional Developer. Version 4.1 improves that
- quality of output to laser printers and allows users to create mouse
- event-driven graphical user interfaces (GUIs).
-
- Version 4.1 of the Graphics LEM provides functions that allow users to print
- full-page, 300 dpi vector output to Hewlett-Packard LaserJet Series III and
- PostScript printers, eliminating jagged lines and enhancing graphics
- resolution. Version 4.1 also is better equipped to handle .PCX images. An
- updated function correctly sets the color palette from the header of the .PCX
- file and allows the display of .PCX images larger than the screen. New
- functions also now allow users to remap the standard Graphics LEM 16 colors to
- any color supported by the EGA or VGA card in use. For instance, to display a
- .PCX image that has eight shades of blue, users can remap 8 of the 16 colors
- to the various shades of blue. Though users may continue to display only a
- maximum of 16 colors, these colors can range outside the standard Graphics LEM
- color palette.
- The simplify creation of GUIs, 4.1 of the Graphics LEM can save and restore
- partial graphics screens to create popdown, tile, icon, and many other screen
- styles, all using only the Graphics LEM's capabilities. Another new function
- identifies a rectangular area of the screen as an active clipping region.
- Parts of objects extending outside the designated area are not drawn. New
- vector printing functions allow users to print only the "clipped" area of the
- screen.
- Version 4.1 also includes a number of new and improved utilities and fonts.
- Utilities allow users to debug .PCX-based image-handling applications, improve
- the look of applications, speed application design, and allow for more
- flexible handling of custom created or edited fonts.
- Enhancements to the GFONT icon and font editor allow users to map fonts to
- equivalent fonts native on PostScript or Hewlett-Packard LaserJet Series III
- printers. They also provide improved scaling capabilities. The ability to
- import a .PCX file to create new icons or fonts also has been improved.
- Included in the new graphics functions is a 3-D "caged" bar-chart function
- that creates a 3-D rendering of a "caged" background. The ability to display a
- time-series graph also has been improved. For first-time buyers, v4.1 of the
- Graphics LEM is priced at $199. Current registered users of the Graphics LEM
- can upgrade for $50. For registered users who purchased the product on July 1,
- 1991, or later, the upgrade is free.
- For more information contact Clarion Software, 150 East Sample Road, Pompano
- Beach, Fl 33064, (800) 354-5444.
-
-
- Picture and Document Imaging Library
-
-
- Videotex Systems has released v3 of T-BASE, its advanced picture and document
- imaging library. Bundled with T-BASE is Chroma Tools, an advanced color
- manipulation and imaging conversion utility. ChromaTools is also form Videotex
- Systems, Inc. T-BASE allows developers to add pictures and document images to
- their database management applications written in C, C++, and virtually every
- Xbase dialect. T-BASE supports any image in the PCX file format.
- T-BASE is also hardware independen, so it does not limit you to certain types
- of video cards or other equipment T-BASE automatically detects your hardware
- configuration and adjusts itself to work in that configuration. ChromaTools,
- which is bundled with T-BASE, is an advanced color manipulation and image
- conversion utility that makes it easy to convert images in a varieyt of file
- formats into a single format for inclusion in desktop presentations, CD-ROM
- libraries, picture databases, demo disks, bulletin boards, advertising,
- kiosks, and more. ChromaTools is a $249 value.
- T-BASE has a suggested retail price of $495 and is available from dealers
- nationwide as well as from Videotex Systems, Inc. For more information contact
- Videotex Systems, Inc., 8499 Greenville Ave, Suite 205, Dallas, TX 75231,
- (800) 888-4336, Fax (214) 348-3821
-
-
- Upgrade To Sourcerer's Apprentice
-
-
- Solution Systems has released v2.0 of its Sourcerer's Apprentice configuration
- management system. Sourcerer's Apprentice enables developers and managers to
- organize a project according to file extension -- by source, by object, by
- headers -- or however they choose, allowing them to get at selected modules
- immediately and intuitively.
- Sourcerer' s Apprentice professional version carries security, compression,
- archiving, and additional reporting features Project leaders or managers can
- secure Sourcerer's Apprentice from unauthorized access by assigning
- permissions based on the user I.D. and module name. Archiving allows users to
- save versions to an external medium, such as a floppy disk, tape or server.
- For programmers working off a Novell server, Sourcerer's Apprentice offers
- automatic file server support.
- Sourcerer's Apprentice requires IBM 286, 386, or compatibles. DOS version
- requires DOS 2.0 or higher; OS/2 requires OS/2 v1.2 or higher; Windows
- requires Microsoft Windows 3.0 or higher. For more information contact
- Solution Systems, 372 Washington St, Wellesley, MA 02181, (800) 677-0001,
- (617) 431-2313, Fax (617) 740-0089.
-
-
- Gimpel Software Releases PC-Lint 5.0
-
-
- Gimpel Software has released v5.0 of PC-lint, a source code analysis tool for
- the C programming language. PC-lint analyzes C programs and reports on bugs,
- glitches, and inconsistencies. Two important new features of PC-lint v5.0 are,
- a Strong Type checking facility and a control-flow based analysis of variable
- initialization. The Strong Typing facility is based on typedef types. In
- addition, Boolean operations can be restricted to a Boolean type, and
- subscript types can be validated. A natural type hierarchy is supported and
- can be supplemented by the user. The control-flow based analysis will report
- on possibly uninitialized variables. The analysis takes into account all C
- control structures as well as the properties of system functions such as free
- and exit and user-specified equivalents. Other constructs that are more
- closely scrutinized in v5.0 are constant expressions, initializers, and
- expressions involving overflow or lost information.
- For more information contact Gimpel Software, 3207 Hogarth Lane, Collegeville,
- PA 19426, (215) 584-4261.
-
-
- GIF Image Support Announced For Windows Applications
-
-
- Black Ice Software, Inc. has released the Graphics Interchange Format (GIF)
- Software Development Kit (SDK) for Windows. The GIF graphics format was
- designed several years ago to allow the exchange of high-resolution color
- images between different hardware platforms. Introduced into the public domain
- via the CompuServe network, tens of thousands of GIF images are now available
- on bulletin boards and information network systems across the country. The GIF
- SDK is designed to satisfy the growing commercial demand for a simple means of
- adding support for this versatile graphics format within Windows-based
- applications.
- The routines included in the GIF SDK make it easy to load GIF files into
- Device Independent Bitmaps (DIBs) or save a bitmap in the GIF format with a
- single function call. Packaged in the form of a DLL (Dynamic Link Library),
- the GIF DSK is compatible with a variety of development environments, such as
- SQL Windows, Actor, Borland's C++, Microsoft's Visual Basic, and others.
- The GIF SDK is priced at $149, and includes complete documentation for use in
- application development. For more information contact Black Ice Software,
- Crane Rd., Somers, NY 10589, (914) 277-7006, Fax (914) 276-8418.
-
-
- Inductive Logic
-
-
- Inductive Logic has released v1.3 of InCommand, productivity enhancement
- utilities for DOS. Version 1.2 was chosen a Publisher's Pick by PCM Magazine
- in October, 1991. New in v1.3 is the ability to match, or exclude, several
- wildcard patterns in one command, and a complete on-line DOS command
- reference, as well as several other enhancements to existing functions.
- InCommand is intended for DOS command line users at all levels. It provides
- commands to list directories (including locating lost files), copy files, move
- files and directories without copying, delete files, directories, and entire
- trees, change file attributes, set file dates and times, locate external DOS
- commands, search files for a text string, and more.
- The InCommand utilities are designed for both interactive and batch use, and
- provide a complete set of options for omitting confirmation prompts and file
- lists. All utilities return consistent exit codes, for full automation in
- batch files. System requirements are an IBM compatible PC, DOS 3.0 or higher,
- and 256K RAM. A hard disk is strongly recommended. For more information
- contact Inductive Logic, P.O. Box 26238, San Diego, CA 92196-0238, (619)
- 578-5146.
-
-
- Liant Software Announces X Window Support For "C-Space 3.2C UNIX"
-
-
- With C-scape and "Look & Feel," developers can create sophisticated
- applications for X
- Liant Software Corp today announced X Window support for C-scape, the
- company's comprehensive library of C routines for creating professional user
- interfaces. C-scape 3.2C UNIX enables C-scape developers to write
- sophisticated user interfaces for graphical applications under the X Window
- System. Existing C-scape applications can also be recompiled under X. C-scape
- applications offer programmers a variety of graphical features, including menu
- systems, borders and pop-up windows. Text editing functions include word wrap,
- search and replace, and block commands. Mouse support allows users to
- manipulate windows or select menu choices.
- These features can be incorporated into screens using Look & Feel, Liant's
- screen designer which comes with C- scape. C-scape 3.2C UNIX has a suggested
- retail price in the U.S. of $1499. Versions of C-scape for VMS, MS-DOSX, OS/2
- and QNX are also available. Each version of C-scape comes with a set of
- example programs and source code which users can modify according to their
- needs.
- For more information contact Liant Software Corp, (508) 626-0006.
-
-
- C++ Version Of Linpack Announced
-
-
- Rogue Wave Software, Inc. announces today that it has started shipping two new
- C++ class libraries, Matrix.h++ and Linpack.h++. These new C++ class libraries
- extend the C++ language to include numerical algorithms that were previously
- available only in FORTRAN.
-
- Linpack.h++ is an object-oriented C++ version of the widely used FORTRAN
- library. Linpack.h++ includes 100% of the functionality of the FORTRAN
- version, plus much more. Because Linpack.h++ is written in C++ it has
- capabilities that far exceed the FORTRAN version.
- Rogue Wave's new libraries take full advantage of C++'s strengths: operator
- overloading, object-orientation, and speed. Programmer productivity is boosted
- because you work with whole objects that represent numerical data rather than
- low-level "DO" loops. The result is fewer, but more expressive, lines of code.
- Both Matrix.h++ and Linpack.h++ are completely compatible with Rogue Wave's
- other C++ class libraries, Tools.h++ and Math.h++. Matrix.h++ and Linpack.h++
- are available for most machines, from MS-DOS to UNIX, including a vectorized
- version for the CRAY. Matrix.h++ includes all the functionality of Math.h++.
- Matrix.h++ adds specialized matrix classes such as banded, symmetric,
- positive-definite, Hermitian, tridiagonal, etc. Because Matrix.h++ includes
- Math.h++, it can take advantage of Math.h++'s optimized lowlevel assembly
- routines.
- Linpack.h++ includes all of Matrix.h++, plus all of the functionality in the
- original and well-established FORTRAN version; including solutions of systems
- of equations for a variety of matrix types, solutions of over- and
- under-determined systems or equations, incremental least squares solvers, etc.
- But, Linpack.h++ is a true object-oriented library, not just a C version that
- compiles under C++: the FORTRAN version has been replaced with high-level
- objects.
- The classes are available now. Prices range from $199 to $995 for Matrix.h++
- and $299 to $1195 for Linpack.h++. For more information contact Rogue Wave
- Software Inc., 1325 NW 9th Street, Corvallis, OR 97330, (503) 757-2311.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Sir:
- I am a computer science instructor for one of the numerous universities that
- use Pascal as the primary teaching language. Recently, at a curriculum
- planning group meeting, I suggested that we give serious consideration to
- adopting C as our primary language. For a brief time I replaced the USSR as
- America's principal adversary. All sorts of reasons, including difficulty and
- treachery, were paraded as reasons why we shouldn't even consider such change.
- And yet I persist, for I feel that at graduation CSS students should take
- useful knowledge into the world.
- Now, the question: do you good folk at The C Users Journal know of any schools
- that currently use C or contemplate using C as their primary language? If
- there are any, I need to review their experience and particular problems, so
- that I can make a better case for my own agenda. Please tell me what you know.
- I will appreciate it.
- Sincerely,
- Casy Ver Berkmoes
- Coordinator, Undergraduate Computer
- Science Department
- The University of Southern Mississippi
- Long Beach, MS 39560
- I suspect P.J. Plauger might answer this differently, but since I'm here and
- he's in Europe, and I've taught quite a bit of introductory programming, I'll
- slip in my two cent's worth.
- I don't recommend using C as a primary teaching language, for several reasons.
- -C is not a "safe" language. The lack of run-time type and bounds checking
- allows the student to commit errors that can't be found without fairly
- advanced understanding of the run-time environment. It's not reasonable to
- make an understanding of compiler code generation, operating system linkages,
- assembly language debugging tools, and other advanced topics be a prerequisite
- to effective debugging. Not only will students fail to find the bug, the
- seeming randomness of certain C bugs will encourage students to trust to
- uncontrolled trial and error debugging and foster a belief in "black magic"
- explanations.
- -Appreciation of some of C's most important strengths requires extensive
- knowledge of the environment. Separate compilation, direct manipulation of the
- hardware, ease of assembly language integration, bit-wise operators,
- pointer/array equivalences -- these are some of C's greatest strengths, but
- are not even remotely related to the kinds of problems with which a beginning
- student should be struggling.
- -Many important effects are achieved through non-obvious means. For example,
- by putting subsystems into separate files, creating the right kinds of
- headers, and labelling certain functions static, the programmer can create an
- encapsulated type. The indirectness of the implementation only complicates the
- teaching of the core concept -- a concept that is fairly difficult to
- communicate anyway.
- -The elegance of the language can't be appreciated until you've tried to solve
- fairly demanding problems in other languages. A student who is asked to do
- classroom exercises in C will probably invest a lot of energy asking "why
- can't I just have a string type". On the other hand, the student who has tried
- to pass a Pascal string to a "Pascal-hostile" operating system will just be
- thankful he has the tool. It's unwise to push the advanced tool onto the
- student before they've experienced at least some of the situations that
- created the need. Otherwise, the student isn't developmentally prepared to
- internalize the nuance of using the tool.
- Having said all this, I must admit that I have talked with faculty who use C
- as the primary teaching language. Unfortunately, I don't recall any names. If
- they are reading, I hope they'll contact you and share their experiences. --
- rlw
- Dear Mr. Plauger:
- With regard to the article "Doing Fractions in C++" in the November 1991
- issue, I found the topic to be interesting and relevant, and commend Mr.
- Zeidler for doing the programming and writing. Unfortunately, Mr. Zeidler did
- this work while learning C++, and made a serious mistake in his design. I am
- afraid that his mistake may be duplicated by other C++ newcomers who read the
- article; hence this letter to point out the problem and propose an
- alternative.
- The problem I am referring to is the class structure that has the fraction
- class inherit the gcd_cls class.
- Actually, the gcd_cls should not have been used at all. A gcd is simply a
- number (integer), and the gcd operation is performed on two integers. In C++,
- the compiler handles integers (or longs in the case of these fractions) as an
- elementary data type. The gcd operation should be introduced simply as a
- function with two unsigned long arguments that returns an unsigned long, thus
- extending the set of functions available to operate on the built-in unsigned
- long data type, as in
- unsigned long gcd ( unsigned long,
- unsigned long );
- A fraction is not a special case of a gcd. There is a relationship between
- fractions and gcds, but only in the sense that the gcd operation (function) is
- needed to help keep fractions in simplest terms.
- Why all the fuss, if the approach used by Mr. Zeidler "works?" Well, it works,
- but incorrectly. Each fraction instance contains the numerator, the
- denominator, plus an instance of gcd_cls. An instance of gcd_cls contains
- three unsigned longs (u, v, and r). In a typical implementation, these
- fractions would require 20 bytes of storage, instead of only the eight bytes a
- proper design would need to store just the numerator and the denominator.
- Obviously, the storage needed by the gcd function does not have to be present
- in every fraction, and can be obtained (automatically) from the run-time stack
- when the gcd function is called.
- If we want to keep this particular gcd function separate from any other gcd
- function that might be lying around in a library somewhere, it would be
- acceptable to implement gcd as a member function of the fraction class. In
- that case, it might be implemented as function that returns the gcd of the
- denominators of the two fractions, as in
- fraction3.denominator =
- fraction1.gcd( fraction2 );
- Inheritance is an especially attractive feature of object-oriented programming
- and C++, but we must not let ourselves fall into the trap of using it
- inappropriately because it usually has a cost associated with it. One simple
- test that may be applied is to ask whether the new class is a special case of
- the inherited class. If not, then the proposed inheritance is wrong.
- Sincerely yours,
- Herbert R. Haynes, Ph.D.
- 6630 Pharaoh Dr.
- Corpus Christi, TX 78412
- Dear Editor,
- Reading your journal always gives me a real pleasure. I read it from cover to
- cover, but articles about programming concrete problems in C are of great
- interest to me.
- I should like to thank you for the series "Doctor C's Pointers" and Stuart T.
- Baird's article "Using Large Arrays In Turbo C" in January 1991. It was very
- useful to me. I'm also interested in the shareware of The C Users' Group, but
- it's absolutely unattainable for me.
- The lack of attention to programming artificial intelligence in low-level
- languages, specifically to expert systems, is regrettable. If you are
- interested, I could offer you a programming example of a simple rule-based
- expert system in C.
- It is also regrettable that The C Users Journal is not widely available in
- Russia. Despite the fact that it is only accessible at a few libraries, it is
- very popular in Russia. I would be very glad to expand the readers' circle of
- your journal in Russia.
- Warmest Regards,
- Dmitry N. Ivanov
- Udaltsova, 57-43
- Moscow, Russia
- I assure you we've made no editorial policy against AI in C. In fact, AI is
- one of my personal interests. I should think a rule-based system in C would be
- very interesting -- but remember, the overall quality of the manuscript is
- always the determining factor in whether a story gets into print.
- The biggest reason we haven't run much on AI is that we've seen few good AI
- manuscripts. I take that to be an indication that AI is still finding limited
- application. -- rlw
- Dear Mr. Plauger,
- It was with a great deal of interest that I read yet another reply to my
- August 1991 request for help with defining/declaring global variables. The
- comments and suggestions from both yourself and readers have been most
- helpful. Dr. Purdum's method is one I have been using for quite some time: it
- works well, and seems to be in common usage. The method proposed by Terence
- Griffin (November 1991, p. 131) which uses a macro instead of a #ifndef/#endif
- to accomplish the same end result seems to be a bit cleaner, however I've not
- yet used this technique. And of course, the method you've suggested is also
- very workable; however I don't favor it since it places the declaration and
- definition in separate files. I suppose that this variety of solutions is just
- one more example of the numerous ways to accomplish the same thing in C (and
- yet another example of why C is so confusing to newcomers).
- But "how to define a global variable" was not my original question. Perhaps I
- did not phrase it clearly enough, but I do believe that my question is
- important enough to look at again. In my example I had a large number of
- source files and the customary number of global variables declared and defined
- in a common header file. One particular file, containing the macro processing
- functions, had a number of global (to that file only) variables defined in it.
- As a matter of fact, the way the files and functions were broken down, all the
- variables for macro processing were confined to that particular file -- the
- functions in the other files had no need to know of the existence of any of
- these variables. To me, this presented a neat and tidy package with all the
- variables defined in one block.
- The problem which came up was that one other function (an error trap routine)
- needed to know the value of one of the macro processing variables. I presented
- my solutions to this problem -- all of these work but I wasn't pleased with
- any of them. I suppose it boils down to a matter of programming style and just
- how defensive one wishes to be. I was looking for comments from others who've
- dealt with this, surely not uncommon, problem.
- Hope this clears up some of the puzzlement over the debate.
- Yours truly,
- Bob van der Poel
- Bob van der Poel Software
- P.O. Box 57
- Wynndel, B.C.
- Canada V0B 2N0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Building An Embedded System
-
-
- Keith W. Cox
-
-
- Keith Cox is currently employed at NH Research Inc. of Irvine, CA, where he is
- the software lead for the S6000 project. He has over six years experience with
- ATE-oriented embedded systems based on Motorola 8-and 16-bit microprocessors.
- Keith was a member of Who's Who in the Computer Industry in 1989-90 and is the
- president of Perfect Circle Computing, a private San Diego consulting firm.
-
-
- For the conventional C programmer, creating an application consists of sitting
- at a keyboard, editing some code, compiling to an executable, typing the
- filename, and pressing ENTER. The operating system takes control, loads the
- program into memory where it belongs, and executes. The program relies heavily
- on library functions written by someone else to perform such tasks as
- interfacing with the hardware and communicating with the user. It is
- completely normal for the programmer to have little or no knowledge of how
- these tasks are actually accomplished. If there is a problem, the underlying
- support platform in most cases will announce it by sending a descriptive error
- message to the monitor. The bug can be edited out, the code recompiled, and
- program promptly executed again, in search of the next run time error.
- If this describes your current software development method, creating an
- application to run on an embedded system may pose a particular challenge. If
- you now face the prospect of working in this environment, gone are the days
- when the flip of a switch and a few seconds of power on testing would allow
- you to begin computing to your heart's content. In an embedded system, turning
- on the switch will do little for you. The best you can hope for is that the
- microprocessor and its peripheral chips will receive a reset pulse of the
- proper duration. After that, you become the proud operator of a collection of
- warming silicon devices, many of them in an unknown state. If there is a
- display, it will taunt you with gibberish. You can press the keys on the
- keyboard (again, if there even is one), and nothing will change. Where do you
- go from here?
- Embedded systems programming is a unique niche in the software development
- field. Most of today's embedded applications are sophisticated enough that a
- well meaning but improperly trained hardware engineer is not up to the task of
- implementing the system's intricate control schemes. On the other hand, a
- programmer familiar only with data structures and control statements,
- oblivious to the world of digital electronics, who knows little about the
- inner workings of a microprocessor and cares less, will find himself
- scratching his head in bewilderment when the time comes to make the hardware
- do something. The embedded systems programmer must have his feet firmly
- planted in both the hardware and software worlds to be successful.
- I have suffered through many a long night laboring over unforgiving components
- and their unintelligible documentation, trying to conjure up the exact
- micro-incantation that would bring the inanimate chips to life. From my
- mistakes I have learned a few secrets I wish I had known when I started. My
- purpose here is to present some some basic techniques of embedded systems
- programming by way of describing the implementation of a recently completed
- project, so that you who must now make the transition to this hybrid world
- might have at least some idea of where to start.
-
-
- The S6000 Power Subsystem
-
-
- The instrument I will use to illustrate the process of programming an embedded
- system is the S6000 Power Subsystem recently released by NH Research Inc. of
- Irvine, Ca. The S6000 provides modular AC and DC power and DC loads at
- precisely programmable levels and states for Automated Test Systems. Included
- in each production system is at least one and up to six chassis that
- communicate with the modules and each other via an RS422 serial protocol. Each
- of these chassis may contain up to six separate power devices.
- There is one CPU board per system, which contains the main control firmware.
- The board is populated by a Motorola 68000 family microprocessor, RAM, EPROM,
- EEPROM, serial and GPIB interface chips, and timers. Interface is provided for
- an optional front panel keyboard and 40 character by eight line LCD display.
-
-
- Defining The Project
-
-
- In any software development project one must first determine exactly what is
- expected of the envisioned executable. From consulting the engineers
- responsible for the hardware design, I learned that the CPU firmware was
- required to
- 1. Provide the system with an orderly power on sequence.
- 2. Perform a power on self test and report the results via front panel
- indicator lights.
- 3. Determine the installed system configuration, compare it with the
- configuration stored in EEPROM, and report discrepancies via the IEEE 488
- (GPIB) bus.
- 4. Command all installed modules to initialize and perform self test.
- 5. Enter a continuous loop whose purposes would be primarily to maintain
- system status information and supervisory control and secondarily to relay
- command data from the outside world to the modules and status information from
- the modules to the outside world.
-
-
- The Development Environment
-
-
- At the beginning of a project the programmer is faced with a number of
- fundamental design questions, some of which are faced by all software
- designers, and some which are unique to embedded systems. In the former
- category, a programming environment and its related tools must be selected,
- and in the latter it must be determined whether to use a real time kernel.
- While embedded systems as recently as four or five years ago were written
- almost exclusively in assembly language for the host microprocessor, C has in
- recent years gained a great deal in popularity among embedded firmware
- designers. There is good reason for this increased acceptance. C can be easily
- adapted to embedded systems. It is versatile enough to make possible the
- development of low-level hardware drivers in assembly language where
- necessary, while executing the bulk of the code in a high-level language. This
- makes development and debugging of firmware faster and easier than was
- possible when writing exclusively in assembly language, while providing the
- speed and control available only with assembler.
- Today there are many excellent C programming tools available from a variety of
- vendors for embedded programming. A rule of thumb to follow when investigating
- the choices is to select an environment that is either intended for or makes
- specific provision for firmware development. The environment chosen for the
- S6000 project was the Microtec ANSI C compiler for the Motorola 680X0 family
- microprocessors, but there are many others available for not only the Motorola
- microprocessors but those of other manufacturers as well. The firmware
- designer will find that for his purposes using one of these will be much less
- baffling than using a C environment created for a specific platform. In
- addition, such compilers generally come with Documentation directed
- specifically to problems encountered in implementing embedded firmware.
- One question a firmware designer is bound to encounter is whether to use a
- real-time kernel. A real-time kernel is software developed by a third party
- that provides some of the basic features of an operating system such as task
- prioritization and management, interrupt handling, and basic I/O. There are
- several of these available for use on a variety of host microprocessors. The
- decision to use one must be weighed between their high cost (both in initial
- expense and in licensing fees) and the complexity of the firmware system to be
- implemented. In a relatively simple system such as the S6000, which must
- switch between only two or three tasks, the expense of the real time kernel is
- unjustified. On the other hand, in a system where tens or maybe hundreds of
- events must be managed, the cost of implementing algorithms to handle them
- might easily exceed the cost of a packaged real-time operating system. My own
- experience with these systems is that they are expensive in terms of RAM, ROM,
- and dollars, are impossible to troubleshoot, and that their vendors provide
- less than adequate technical support. In my opinion, for small to medium
- applications such as the S6000 they are not worth the trouble and expense.
- With that in mind, no kernel was included in the S6000 design.
-
-
- The C/Assembly Language Interface
-
-
- It is rare for a completed embedded system to be programmed entirely in C.
- Usually, assembly language is used at least to enter the vectors, and often to
- perform the system startup and device initialization routines. Why use
- assembly language instead of C? In many cases the code directly interfacing
- with the hardware is required to be compact and fast. Depending on the tools
- used and the expertise of the programmer, it may be possible to accomplish
- this in C, but in reality the compiler is rarely able to provide assembly
- language output as tight as the code you can produce yourself. Additionally,
- with assembly language you have full control over the hardware and the data
- presented to it, something you may not have, or that you must be very careful
- to achieve, using C.
- When implementing a mixed language system, the assembly-language code is
- entered in separate files from the C code. Since compilation is generally a
- two-step process (from C to assembler, then from assembler to object code),
- the assembler simply skips the first part of the process when producing the
- object code. Your makefile should be set up to instruct the development tools
- how to process the assembly code. The linker will act on the object output of
- the assembler in exactly the same way as the output from the C compiler,
- because they are in exactly the same format.
- Something to keep in mind when calling functions written in assembly from the
- C code is that the compiler will often add an underscore (or in some cases a
- dot) to the names of functions located in and called from a C program. For
- example, a call to the function write_char(), located in an assembly language
- file, will cause the instruction
- jsr _write_char
- to be contained in the C module's assembly-language output. If your routine in
- the assembly-language file is named write_char instead of _write_char, the
- linker will not know where to find it, and will generate an error. To overcome
- this difficulty, equate the name of the assembly-language routine with the
- name the compiler will generate to call it. For example, the line
- _write_char equ write_char
- at the end of the write_char routine would enable the linker to find the
- correct address. An even simpler method is to merely add the underscore to the
- routine's declaration in the assembly-language file.
- There may be some instances when you would rather write some of the low-level
- access in C, and in this case it will be necessary to know how to perform
- direct memory reads and writes. The code fragment below illustrates the task
- of writing a character to a hardware address.
- void func(char c)
- {
- char *ch;
- ch = (char *) 0xFFFF0400;
-
- *ch = c;
- }
- Here, a pointer variable named ch has been declared. This variable could have
- been a pointer of any type, depending on the width of the hardware port being
- written (for example, short * for a 16 bit port, or long * for one that is 32
- bits wide). The first code line causes the variable ch to point to address
- 0xFFFF0400, an arbitrary address that could be any address in your memory map.
- The second code line causes the value of c to be written to the selected
- address.
- Reading a memory address is performed following a similar process, as shown
- below.
- char func()
- {
- char *ch;
- ch = (char *) 0xFFFF0400;
- return(*ch};
- }
- In this case the variable ch is declared and assigned as in the write
- operation. The last code line returns the 8-bit value located at address
- 0xFFFF0400 to the calling function.
-
-
- The Memory Map
-
-
- To aid in understanding the topics that follow it may be instructive to
- consider the memory map of the S6000 CPU board, which is representative of
- many 680X0 implementations. Figure 1 shows that the memory map can be divided
- into three sections: EPROM, RAM, and Hardware Ports. The EPROM, beginning at
- the lowest physical address, contains the initial vector table and all of the
- program code. The RAM, using all of the populated address space between the
- EPROM and the I/O ports, contains the final vector table, all of the static
- data, the ZEROVARS RAM section (which is used by the C runtime libraries), the
- heap, and the stack.
- For those who may be unfamiliar with the operation of the heap and stack, the
- stack provides temporary storage during runtime (such as for local variables),
- and grows from the highest address in RAM down toward the end of the static
- data. The heap consists of all unused RAM space and grows up from the end of
- the static data area toward the stack. The heap is the dynamic memory area
- managed by such functions as malloc() and free(). Improper management of this
- area may result in the infamous and unrecoverable Heap/Stack Collision error,
- for obvious reasons.
-
-
- The Vector Table
-
-
- When programming an embedded system the first code to be written is normally
- the vector table. This table is a series of pointers in a specific location in
- the microprocessor's address space. The vector table defines the system's
- fundamental behavior. The beginning of the 680X0's vector table is always
- located at physical address 0x00000000. Microprocessors are designed in such a
- way that when certain events occur control is passed to code pointed to by one
- of the vectors. For example, after the power-on reset pulse is applied to the
- 68000 microprocessor's reset pin, the Program Counter (PC) will automatically
- be filled with the contents of address 0x00000004 (the second vector, since
- each vector is four bytes wide), and program execution will begin at that
- address. The microprocessor will know where the initial stack is located, and
- where to find code to execute in case of such events as bus error, address
- error, illegal instruction, and divide by zero, by values located at
- predetermined addresses in the vector-table. A description of the vector table
- contents can be found in the documentation provided with the microprocessor.
- Although the vector table is located at address 0x00000000 in the physical
- address space and must initially be located in EPROM, many hardware designers
- redirect the vectors (other than the first two) to addresses in RAM. This
- allows the firmware designer to change the contents of the vector table on the
- fly, a feature which can be useful in a variety of cases, one of which is
- described later in this article.
- How do you actually write the vector table? The easiest way is to write it in
- assembly language as shown in Listing 1. At the top of the assembly language
- file, statements instruct the assembler that certain names (of functions and
- interrupt service routines) are to be found elsewhere. With most 680X0
- assemblers, this is done using the xref directive. Thus the assembler makes
- space for the vector table without actually knowing the actual data that will
- ultimately reside there. The linker will fill in the details at link time.
- After listing all external references, the ORG statement is used to tell the
- assembler where to put the code it is about to encounter (0x00000000).
- Following the ORG statement, all the vectors are defined using the define long
- constant directive or its equivalent for the assembler in question. At compile
- time, this module is assembled to a .obj or equivalent file which the linker
- can include in the final executable.
-
-
- The Application Startup Code
-
-
- The startup code begins at the address pointed to in the Initial PC vector.
- The purpose of this code will vary between applications. In the S6000, the
- startup process performs various self tests and initializes the hardware ports
- and devices.
- Because the hardware is in an unknown state at power on, it is wise before
- performing any other task to mask out external interrupts while initializing
- the system. This is accomplished by the first instruction in the startup code.
- The contents of the vector table are then copied to the first 400 (hex)
- addresses in RAM so that they can be changed as needed. Once this has been
- accomplished, a check is made to see how much RAM is installed in the system.
- The CPU board is designed in such a way that the amount of RAM it can hold
- varies between 16K bytes and 256K bytes in 16K byte blocks. It is important to
- know the actual amount installed so that the address of the top of the stack
- can be registered in the microprocessor. Determining the amount of RAM present
- is accomplished by writing a value to the first address of each 16KB boundary.
- If RAM is present, the address counter is incremented by 16KB and the
- operation repeated until the maximum address is reached. If there is no RAM
- present at any address being written to, a bus error occurs, and code at the
- address entered in the Bus Error vector is executed. In the S6000 this
- interrupt service routine assigns the stack pointer in the SP (A7) register to
- the value found by this process and changes the bus error vector (now located
- in RAM) to the value of the real Bus Error interrupt service routine.
- The startup code then performs a check of RAM and EEPROM memory, reporting any
- errors, and each of the initialization routines for the specific hardware
- devices are called. These routines might be provided by the hardware vendor or
- a third party, but most likely the system designer will have to create them.
- For the S6000, initialization and I/O routines were designed and implemented
- in assembly language for GPIB I/O, serial I/O, timer interrupt management,
- keyboard input, and display output.
- Finally, the external interrupts are unmasked, and control is passed to
- routines that will initialize the C runtime environment.
-
-
- The C Runtime Environment
-
-
- When programming for a platform such as a PC, which conforms to a hardware
- standard and for which the operating system is known to be present, a C
- compiler will add code at compile time that defines an environment for the C
- program to work in. This code performs initialization of the heap and makes
- assumptions about where data is likely to come from (the keyboard) and go to
- (the screen). Using these suppositions the compiler provides elementary code
- fragments to handle memory allocation, keyboard input, and screen output (the
- devices stdin for standard input, stdout for standard output, and stderr for
- standard error). In an embedded system, these presumptions cannot be made,
- since there may not be a keyboard, screen, or heap. Therefore, in order to be
- able to use standard library functions such as malloc and printf the
- programmer is obliged either to provide these routines or modify those
- provided with the environment.
- To take full advantage of the C run-time library it will be necessary to
- accomplish the following steps:
- 1. The heap pointer must be assigned to tell the compiler where the heap is
- located.
- 2. The ZEROVARS section must be cleared.
- 3. The standard input, output, and error devices must be initialized.
- Additionally, if file I/O is to be accomplished, the file structures must be
- assigned and initialized.
- 4. Code must be provided to access the devices which input and output
- characters, and the library routines must know where to find it.
- To locate the initial heap pointer in the S6000 a four-byte variable is
- declared in a section called HEAP. Using almost any linker the order of the
- code and data sections can be specified. In the case of the Microtec tools
- this is done using a linker command file. In this file, the HEAP section is
- declared to be last, after the ZEROVARS section, since the highest addresses
- the linker will be concerned with are located in RAM. This will cause the
- address of the variable declared in this section to be located at the end of
- the static variables used by the program. Thus, the address of this variable
- is in reality the first address of the unassigned memory between the static
- data area and the stack, or the heap. This address is assigned to a constant
- variable (called ????HEAP in the Microtec environment) that tells the compiler
- where the dynamic memory area begins.
- Static variables that need to be cleared at the beginning of the program are
- located in the ZEROVARS section. Among them are various heap-management
- pointers. In the Microtec environment, if the application intends to use
- dynamic memory allocation it is important that this area be filled with zeros,
- otherwise memory allocation will not work properly, probably with disastrous
- results. The ZEROVARS section is cleared as a matter of course in systems
- where the program startup code is provided by the compiler. Although the code
- to do this is usually available to the embedded system programmer, it is not
- automatically included in the program or invoked. It is up to the programmer
- to locate the code and incorporate it in the program. If the development
- environment selected is meant for embedded systems programming, the compiler's
- documentation should tell how to do this. If not, the code to do this will be
- among the startup code fragments provided with the compiler, and the system
- developer will need to peruse these files to find it and include it in the
- final executable. In the Microtec environment, this code is located in a file
- called ENTRY.S.
- In the Microtec compiler for the 68000 family there is a file called CSYS68K.C
- which contains the code for performing the rest of the startup sequence. The
- _START routine in this file initializes the heap pointers for malloc() and
- opens the stdin, stdout, and stderr devices. If different compiler tools are
- being used, there should be a file or files that will perform these same
- functions, again, hopefully described in the documentation.
- Additional skeleton functions must be provided for handling character input
- and output. The tasks consist of extracting characters from the proper
- hardware address and returning them to the calling function, or receiving
- characters for output from the library routines and actually writing their
- value to the proper address in memory. These routines are used by such library
- functions as printf and getch. Without them the library functions cannot work
- properly. It is possible to bypass the library functions, however this
- generally will make the programmer's job more difficult instead of less,
- because he will have to provide routines that perform tasks that have already
- been implemented.
-
-
- The Main Program
-
-
- At the end of the startup activities, control is finally passed to the
- program's main() function. The programmer may assume that if the above steps
- have all been done correctly and the code can be traced to the address of
- main(), writing the rest of the program will be very much the same as writing
- any other C program. Nevertheless, certain precautions should be adhered to
- which the programmer may not (but probably should) observe in other
- programming environments.
- Make sure that no variable is used that has not been explicitly initialized.
- The startup code cannot be relied on to zero all of the data space unless the
- programmer specifically directs it. Also, initialize variables in the body of
- the code rather than in the declaration. Some complex variables that are
- initialized in the declaration may be considered to be constants and placed in
- ROM, an unhappy situation that can be difficult to troubleshoot.
-
- A last word of advice to the would-be embedded systems programmer is to become
- familiar with the hardware involved. The idea of learning digital hardware is
- often distasteful to software engineers but the knowledge can be indispensable
- when debugging embedded code. All hardware devices are documented, some better
- than others, and most programmable chips contain sections aimed specifically
- at firmware developers. Because hardware designers are often unfamiliar with
- the devices themselves, and because errors frequently occur in preparing
- prototypes, becoming familiar with the hardware in question can save the
- embedded systems programmer countless hours of debugging a problem which may
- in the end be caused by improper hardware implementation.
- Embedded systems programming, while perhaps not as glamorous as other aspects
- of software development, can be an interesting and rewarding experience. With
- the knowledge gained from this endeavor the software designer will become more
- articulate with the actual inner workings of computers, which will make even
- unrelated programming tasks easier to understand and implement.
- Figure 1
-
- Listing 1
- .
- .
- .
- xref start
- xref aerr
- xref berr
- xref illegal
- xref div0
-
- ORG 0
-
- vctr000 dc.l STACKTOP * the top of the stack
- vctr001 dc.l start * the startup code address
- vctr002 dc.l aerr * the address error vector
- vctr003 dc.l berr * the bus error vector
- vctr004 dc.l illegal * the illegal instruction vector
- vctr005 dc.l div0 * the divide by 0 vector
- .
- .
- .
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- OOP Without C++
-
-
- Bill Bingham, Tom Schlintz, and Greg Goslen
-
-
- Tom Schlintz has a BSEE from Virginia Polytechnic Institute and has been
- working as a software engineer for Logical Design group for the past four
- years. His Interests are embedded and VME bus-based applications. Greg Goslen
- has a BA in Physics from Appalachian State University and has been working as
- a software engineer for Cardiovascular Diagnostics for the past year. He has
- nine years of embedded systems experience, four of them working in C. Bill
- Bingham has a BA in Biology from The University of Virginia, an MSEE from
- Berkeley, and MEng Bioengineering from Berkeley. He is currently the senior
- research and development engineer at Cardiovascular Diagnostics, where he has
- worked for the past two years.
-
-
- When most programmers think of object-oriented programming (OOP), they think
- of object-oriented languages. What we often forget is that OOP is more than
- just using a type of compiler. It is a programming concept -- a way of
- thinking about the way we structure our code.
- We're writing this paper to demonstrate three things: first, that OOP isn't as
- scary as you may have been led to believe; second, that no one person, group,
- or manufacturer has an exclusive on the implementation of OOP; and third, how
- we addressed a small embedded systems project, using an OOP approach, and made
- it work.
- Several months ago we were struggling with the code for a new medical device.
- We required that the code be clear, modular, and thoroughly testable, an ideal
- candidate for object-oriented programming techniques. Unfortunately, it also
- had to perform a large number of tests in limited ROM (48KB), limited RAM
- (8KB), and on a processor built for small, embedded applications, the 68HC11.
- To further complicate matters there was neither an OOP compiler for our
- platform nor the code space to support a full-blown OOP strategy. Still, we
- wondered if we could use a standard, high level language to get some of the
- benefits of OOP without sacrificing too much space.
- Once we began this project, the first thing we noticed was that we already
- knew OOP, we just didn't know that we knew. The concepts are not difficult, we
- had all used some of them before. The major difference is that in an OOP
- language the constructs are supported by the compiler.
-
-
- Implementation
-
-
- The classic object-oriented system creates objects in RAM, allows them to
- communicate with one another and selectively destroys them once they have
- finished their tasks. In an embedded system, RAM is precious, so we created
- object classes manually using typedefed structs, which we stored, along with
- object definitions and pointers to object methods, in ROM. In essence we did
- manually what an OOP compiler does for you.
- To further conserve ROM space we used the concept of inheritance to eliminate
- redundancies between tests. We began by creating a parent class, TEST_CLASS as
- a typedefed struct, to be the master template for our test objects. It is
- defined in the header file class.h, as shown in Listing 1, and is fixed at
- compile time. Listing 2 shows how, by creating variables of type TEST_CLASS,
- several objects, test_a, test_b, and test_c, inherit their basic structure
- from it.
- To further conserve ROM we created several subclasses of TEST_CLASS. We placed
- class specific methods and data into a common module and object specific
- methods and data into object specific modules. Objects of a given subclass
- inherit data and methods from that subclass.
- Thus, test_a and test_b are of the same subclass, class 1, and both use
- class_1_display, which is defined in Listing 3, class_1.c. This is an example
- of inheritance from a subclass. test_c is of a different subclass, class 2,
- and so inherits class_2_display. All the test objects define their own public
- data, e.g., Test_Name, and public methods, e.g., init_object.
- Using our protocol the names of all methods are fixed when a class is created.
- When a subclass inherits method names from the parent class we manually attach
- one of several functions to the pointer associated with that method name. By
- using this form of polymorphism we reduce the complexity of the main program
- and force implementation specific details to remain encapsulated within the
- objects.
- Our project specified twenty different tests, and that adding, changing, or
- deleting a test be easy and have no impact on existing tests. To achieve this
- we created a master array of pointers, test_ptr[], in the main program file.
- The elements of the master array point to the structure defining each test
- object. An index into test_ptr[] yields a pointer to the selected test object
- (see Listing 4). The selected object then performs the test via its methods.
- In order to compile properly, test_a, test_b, and test_c must be declared as
- externals above the declaration of test_ptr[]. The last, null entry in
- test_ptr[] is used as a termination to allow any number of tests to be
- incorporated. The Test_Name[] entry in every object of type TEST_STR allows
- the main program to search for the correct object by matching Test_Name to a
- string.
- The invoking program doesn't need to know which routine it is using, only
- that, for example, it is calling a display method. The display method handles
- the details. This makes it easy to add tests. It may seem like this is a lot
- of work to go through, when it would be much simpler to completely define test
- objects as structures throughout the program. If, however, we wish to add a
- new test with new functionality, this approach allows us to create new methods
- without changing data flow.
- Since we were already using separate modules to implement inheritance we
- decided we could go further and use the C specification to implement
- information hiding. C forces knowledge of variables declared at the beginning
- of a file to be confined to the routines in that file. This allowed us to
- define static variables at the top of the file that are freely accessible to
- all routines within the file, while hiding these variables from routines
- outside. Outside access to the data can be tightly controlled by routines
- within the file. We used this aspect of C to treat the contents of a file as
- an object, and the routines and data within the file as methods and private
- data of that object. By defining an object as files only the methods defined
- in that file have access to the object data.
- We recognized that data hiding necessitated some form of messaging. What we
- needed was a flexible and not too test specific, messaging scheme. Each test
- expected a different set of inputs and produced a different set of results. If
- these parameters were declared explicitly in the test method argument list,
- each test would have had to be a separate class. On the other hand, if we had
- used TEST_CLASS to define all of the tests, all instances of a given method
- would have had identical argument lists.
- Our solution was to establish global, generic floating-point arrays that could
- be passed as parameters to any method of the current test object (see Listing
- 5). In our case all data could be passed as floating point but a more generic
- interface could be constructed using an array of strings. Structures were
- typedefed at the top of each object file which described the format used by
- all the methods of that object. The objects used the structure to encode or
- decode the information stored in the arrays.
- The parameter array is allocated in global RAM and is bigger than the maximum
- number of parameters for any test. A similar array of floats is defined for
- passing results back from tests. The main program passes the pointers to the
- params and results arrays to the methods when it invokes them and never knows
- what is in these arrays. Its job is to coordinate the passing of the arrays
- from one method to the next. The arrays combined with the object specific
- template act like a secret decoder ring available only to the methods within
- the object and allowed us to use meaningful names instead of index numbers to
- refer to each parameter. The result was much clearer code.
- Listing 6 shows a test called test_a which requires that quantities called
- time and amplitude be passed to it. Notice that the parameter template maps
- the first two elements of params to these names. This template resides in the
- class specific module file (class_1.h shown in Listing 6).
- The methods shown in Listing 3 take these pointers and cast them with the same
- class specific template into a pointer to a structure of type PARAMETER_STR.
- In Listing 6 the method casts the PARAMETER_STR to the method specific
- pointer, ps. The method can now interpret the parameter string values
- according to its own definition and can refer to any of the parameters by
- name, as shown in Listing 6. A similar mechanism is used to place results in
- the results array.
-
-
- Summary
-
-
- The effort we expended to implement OOP in our system has been paid back by
- the ease of adding new tests to the system, the disappearance of cross-test
- interference, and greatly decreased debugging time. Once implemented, OOP
- techniques helped us to achieve a higher level of abstraction. Because
- implementation details were left up to the private methods within each of our
- objects, our code could ignore how things were implemented on lower levels.
- This freed us to concentrate on the design aspects of the problem.
- We would like to thank Murdock Taylor for developing the hardware platform we
- used in this project.
- Basic OOP Terms
- Before we could understand OOP we had to become familiar with the language.
- Understanding each of these terms, and particularly what separates theory from
- practice, was what allowed us to use them in our design.
- Method: A function by any other name. A method is code bound to an object. The
- code provides an interface for messages requesting an operation and a means to
- perform the requested operation.
- Object: A conceptual entity, implemented in software, which has distinct
- boundaries and contains both data and code. There are two types of code in an
- object -- methods, which transfer data across the object's boundaries, and
- internal functions, which provide private services to the object.
- Class: A class is the template from which an object is derived. It defines
- what data, functions and methods can be bound to the object. An object is
- formed by creating an instance of the class and filling in the blanks.
- Inheritance: This is the ability to derive a new class from an existing one.
- The child class can be a sub or super set of the parent. Multiple Inheritance
- is the ability to derive a new class from more than one parent class.
- Polymorphism: Polymorphism is when two objects bind a different method but
- access it by the same name. The classic example is to create two classes, car
- and boat. An object from either class can perform an operation "Move" but the
- method which carries out the operation is different. The use of the same
- command to generate similar effects using different code is polymorphism.
- Information Hiding: Information hiding denies outside entities direct access
- to the object's private data and functions. Instead it forces outsiders to
- access the data in a controlled fashion via the object's methods. As with some
- government agencies, if you don't need to know, your knowing will gum the
- works.
- Messaging: The act of communicating with an object to get something done. It
- consists of invoking a method, supplying that method with the information it
- requires, and receiving a reply from the method. Clearly, messaging can be
- something as simple as a function call.
-
- Listing 1 (class.h)
- /****************************************************
- * This is the definition of the class, TEST_CLASS.
- * It is defined as a typedefed struct. It possesses
- * public data: Test_Name, and public methods:
- * init_object, process_data and disp_results.
- * This object is compiled into ROM.
- ****************************************************/
-
-
- typedef struct {
-
- char Test_Name[8];
- void (*init_object) (void);
- void (*process_data) (float *parameters, float
- *results);
- void (*disp_results) (float *results);
-
- } TEST_CLASS;
-
- /* End of File */
-
-
- Listing 2
- /****************************************************
- * This code declares tests objects, test_a, test_b
- * and test_c. These objects attach their own
- * initialization, processing and display routines
- * to the pointers provided.
- * Each test has its own name and initialization methods,
- * but inherits its store and display methods from it's
- * respective class, ie. test_b from class_1, test_c from
- * class_2.
- *
- * Construct Name Location
- *
- * CLASS TEST_CLASS class header file ROM
- * SUBCLASS class_1, class_2 class specific files ROM
- * OBJECTS test_a, test_b object specific files ROM
- *
- * The "const" directive tells the compiler to place
- * the test objects in ROM.
- ****************************************************/
-
- file test_a.c
-
- /***** Definition of Object "A" *******/
-
- const TEST_CLASS test a = { "TEST A",
- object_a_init,
- class_1_process_data,
- class_1_display
- };
-
- file test_b.c
-
- /*****Definition of Object "B" ********/
-
- const TEST_CLASS test_b = { "TEST B",
- object b_init,
- class_1_process_data,
- class_1_display
- };
-
-
- file test_c.c
-
- /***** Definition of Object "C" ********/
-
-
- const TEST_CLASS test_c = { "TEST C",
- object c_init,
- class 2_process_data,
- class_2_display
- };
-
- /* End of File */
-
-
- Listing 3
- /****************************************************
- * Shows two class 1 specific methods, class_1_display
- * and class_1_process_data, defined in the class 1
- * module. It also shows an object specific method,
- * object_a_init, defined in the object module,
- * test_a.c
- *
- * This code is compiled to ROM.
- ****************************************************/
-
- file class_1.c
-
- void class_1_display(float *results)
- {
- - code -
- }
-
- void class_1_process_data(float *parameters, float *results)
- {
- - code -
- }
-
-
- file test_a.c
-
- void object_a_init(void)
- {
- - code -
- }
-
- /* End of File */
-
-
- Listing 4 (main.c)
- /****************************************************
- * This is an example of the array of pointers to test
- * objects. It shows how the array is defined and how
- * it can be searched for a specific test object. It
- * also shows how, once the index to the correct test
- * object is found, the pointer to the test object can
- * be used to invoke the object's methods.
- *
- * This code is compiled to ROM.
- ****************************************************/
-
- extern TEST_STR test_a();
- extern TEST_STR test_b();
- extern TEST_STR test_c();
-
-
- TEST_CLASS *test;
- TEST_CLASS (const *test_ptr[]) = {&test_a,
- &test b,
- &test_c,
- 0};
-
- /* assume name is a parameter set to */
- /* "Test A" select the Test A test object */
-
- test = test_ptr[0];
- while(test != 0) {
- if(strcmp(test->Test_Name,name) == 0) break;
- test++;
- }
-
- /* and use it to run the test */
- .
- .
- test->init_object();
- .
- .
- test->process_data(params,results);
- .
- .
- test->disp_results(results);
- .
- .
-
-
- /* End of File */
-
-
- Listing 5
- /****************************************************
- * This is an example of global, generic floating point
- * arrays which are used to pass parameters
- *
- * This code is compiled to RAM.
- ****************************************************/
-
- file main.h
-
-
- #define NUMPARAMS 20
- #define NUMRESULTS 12
-
- file main.c
-
- float params[NUMPARAMS];
- float results[NUMRESULTS];
-
- /* End of File */
-
-
- Listing 6
- /****************************************************
- * This is an example of messaging between methods.
- * The methods use an object specific template to
- * encode/decode messages being passed.
-
- *
- * This code is compiled to ROM.
- ****************************************************/
-
-
- file class_1.h
-
-
- typedef struct {
-
- float time;
- float amplitude;
-
- } PARAMETER_STR;
-
- typedef struct {
-
- float result_1;
- float result_2;
- float result_3;
-
- } RESULT_STR;
-
-
- file test_a.c
-
-
- #include class_1.h
-
- int test_a_process_data (float *params, float *results) {
-
- float value_1, value_2, value_3;
- float time, amp;
-
- PARAMETER_STR *ps;
- RESULT_STR *rs;
- .
- .
- /* apply the template to the */
- /* results and parameter arrays */
-
- ps = (PARAMETER_STR *) params;
- rs = (RESULT_STR *) results;
- .
- .
- .
- time = ps->time;
- amp = ps->amplitude;
- .
- /* test specific calculations */
- .
- rs->result_1 = value_1;
- rs->result_2 = value_2;
- rs->result_3 = value_3;
-
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- The Device Driver As State Machine
-
-
- Thomas Nelson
-
-
- Tom Nelson is an independent author, consultant, and part-time artist. You can
- reach him at 5004 W. Mt. Hope Rd., Lansing, MI 48917.
-
-
- A state machine is a useful logical construct for writing device control code.
- By mirroring the device's actual operation, the state machine makes the code
- easier to comprehend and therefore modify. Since a state machine usually
- relies heavily on initialized data in tables, the amount of logical branching
- in the associated code is minimized, thus helping the programmer avoid
- "spaghetti" code. When properly employed, a state machine adds a layer of
- abstraction between the user and the controlled device. It also isolates
- device-dependent code within a limited number of functions.
- From the point of view of an operating system, a device driver must present a
- consistent interface to application programs that access devices you may
- upgrade at any time or even to devices not yet on the market. The standard,
- installable device driver does all this and more. It functions as an
- extensible, user-modifiable part of an operating system.
- This article attempts to combine what is necessary (the device driver) and
- what is desirable from a coding standpoint (the state machine). The result is
- a method of coding DOS device drivers (either PC-DOS or MS-DOS) that provide a
- programmatic interface to a finite-state machine. The article presents a
- working device driver that controls an idealized tape backup unit. I left out
- the specifics of controlling an actual device (such as port assignments and
- bit settings) to focus on the connections you must make between an actual
- device and your application program.
-
-
- Driver Design Considerations
-
-
- Designing a DOS device driver is a relatively simple task, since any driver
- must follow a standardized design. Getting one to actually run is another
- matter. Many consider device drivers a specialized systems task, one to be
- tackled only by the programming elite. However, the art need not be
- mysterious, especially when you code mostly in C. I organized Listing 1 and
- Listing 2, which form the kernel of a DOS device driver, as a template from
- which you can build any device driver. The template lets you concentrate on
- the actual device controller code. If you use the Borland compiler/assembler
- combination, you don't even have to know assembly language, although some
- knowledge of it will help you follow the ensuing discussion. If you use
- another compiler and assembler, you will need to modify the code to some
- extent.
- I'll touch on a few basics of device drivers, but will concern myself mainly
- with some important design considerations in coding a driver in C, as well as
- some usage caveats. Many journal articles and book chapters deal with device
- drivers. I refer the reader to the References section.
-
-
- The Template
-
-
- Listing 1, start.asm, presents the assembly language start-up code. At this
- level, you must use assembly language in raw form (even inline assembly won't
- do) because C can't provide the necessary control. All drivers consist of one
- segment up to 64Kb in length, forcing all code and data into a single segment.
- Some compilers can produce a tiny model .COM file that satisfies these
- requirements. However, a device driver is a binary image file with an origin
- of (i.e., no Program Segment Prefix block), the first bytes of which are not
- executable. I believe few compilers could deal with such a situation.
- Listing 1 begins by defining segments common to both the Borland and Microsoft
- compilers. If your compiler uses different segment names, you must change
- them. The segments must also be defined in the proper order. You must place
- start.obj first in the link order so that segments declared in other modules
- will be ordered the way you specify here (see the MAKE file in Listing 7). A
- compiler will usually define DGROUP (in the small model default) as a group of
- all data segments, keeping the code segment separate. Here I define DGROUP by
- combining both code and data segments, the same as for a tiny model .COM
- program.
- I also defined another segment called ENDSEG, which is also part of DGROUP and
- will be linked in behind all other segments. The offset to ENDSEG allows DOS
- to calculate the driver's size (the break address) when DOS loads another
- driver behind yours.
- As mentioned earlier, all drivers have an origin of 0, specified by the org 0
- statement. You must place the driver's header block immediately after, at
- offset in . The four-byte pointer field in the header usually contains -1L
- (0xffffffff) unless your file contains code for more than one driver, in which
- case it points to the next driver's header block. Whatever the case, the
- pointer in the last driver's header block must contain -1L. DOS fills it with
- a pointer to the next driver's header in the chain at driver load time.
- DOS always communicates with its device drivers using a curious two-step call.
- Most authors assume this was intended as a bridge to the future when DOS would
- support full multitasking. In view of the present status of DOS, multitasking
- is at best a remote possibility. The first part of this two-step call, the
- _dev_strategy() routine, merely saves a pointer to the caller's request header
- block. To maintain backward compatibility, all DOS drivers must include this
- routine, which represents a bit of additional overhead.
- The real work of the driver is performed by the second of the two procedures
- called by DOS, the interrupt routine. Although not a true interrupt handler,
- it shares many characteristics with them. The main difference is that it
- terminates with a far ret rather than an iret. Like an interrupt handler,
- however, the routine saves the CPU state and performs other housekeeping
- chores. If you're going to support compiled C code, the startup procedures
- must occur here also. Your startup code must duplicate as closely as possible
- the environment created by the compiler's normal startup code. The difference
- is that startup occurs on every call to the driver, not just once when DOS
- executes an application.
- After saving the CPU state and caller's stack context, the interrupt routine
- reclaims its own data segment, contained in the CS register. Since code and
- data are contained in one segment, the routine loads the DS register with the
- same value. Loading ES is probably optional, since most DOS compilers seem to
- make no assumptions regarding ES.
- The interrupt routine sets up its own internal stack by loading the SS:SP
- register pair with the appropriate values. One difference here is that the
- driver's stack does not have its own segment, as is usually the case. The
- linker expects a separate stack segment, however, and will complain about it.
- This is one warning you can ignore. Although creating a separate stack for the
- driver is not strictly necessary, supporting a stack-intensive language like C
- makes separate stacks highly desirable. (I have seen attempts to program
- device drivers in C where all variables were declared global to avoid blowing
- the short stack provided by DOS, reportedly as short as 40-50 bytes.)
- Providing your device driver with its own stack is simple, and the benefits
- easily outweigh the small overhead in space. If you're really worried about
- memory usage, you should code the whole device driver in assembly language.
- Having set up the appropriate machine environment, the interrupt routine then
- branches into C code. It pushes a far pointer to the caller's request header
- (saved earlier by the strategy routine) and calls exec_command() in Listing 2.
- exec_command() serves as a central dispatch point where the call branches to
- the appropriate function, or command code routine. exec_command() uses the
- jump table (*Dispatch[])(), an array of pointers to the command code routines.
- Using a jump table seems more elegant and less code intensive than the more
- cumbersome switch statement.
- exec_command() also makes the caller's request header, defined in Listing 3,
- available globally, assigning it to the pointer Rh. exec_command() branches to
- the correct command code routine based on the command code Rh->cmd passed in
- the request header. The function uses the command code as an index into the
- Dispatch jump table.
- exec_command() expects all command code routines to return zero if they
- execute normally. If errors arise while a command code routine executes, the
- routine returns IS_ERROR plus the appropriate error code, defined in Listing
- 3. On return from the command code routine, exec_command() assigns the
- routine's return code to the request header. exec_command() also sets the done
- bit in the same status field in the header. This bit seems something of an
- anachronism, but you should nevertheless set it before returning from the
- interrupt routine. The remainder of the interrupt routine restores the
- caller's CPU and stack context.
- After loading your driver, DOS immediately calls the driver initialization
- routine, command code 0 (see init() in Listing 2). You must perform any driver
- setup procedures inside this routine. Note that you are restricted to DOS
- functions 0x01-0x0C and 0x30. Other than these functions, you should avoid
- making DOS calls anywhere in your driver code. In general, since device
- drivers serve as a bridge between applications and the BIOS, you should
- restrict your driver code to BIOS-level calls (or port in/out code if needed)
- and avoid DOS entirely. More to the point, any call your driver makes to DOS
- commits the sin of DOS re-entrancy, since DOS called your driver in the first
- place.
- To inform DOS where to locate the next driver in the chain, the initialization
- routine must return a break address in the request header that specifies the
- end of the driver image in memory. Here I simply label the ENDSEG segment
- (discussed earlier), a segment guaranteed to fall at the end of the driver
- image. init() passes the far address of the labeled segment to DOS as the
- break address. This quick and dirty method, however, means that init() remains
- in memory, where it becomes dead code once DOS has initialized your driver. To
- save memory, most drivers (those written in assembly language, that is) locate
- the break address above the init() code and any data that init() will use only
- once. DOS will then load the next driver over the unneeded parts.
- Although more difficult, this trick can be simulated in C. Since the code
- segment is located first, you can place all code and data in _TEXT, put init()
- in a separate module, and place init.obj last in the link order. You could
- then pass the (void far *) address of the start of init()'s code to DOS as the
- break address. The main obstacle is that most compilers will automatically
- place all data in the _DATA segment. In order to get your data into _TEXT, you
- must define all global data inside an assembly language module and make extern
- references to them in your C modules. Although this scheme works, it
- complicates making changes later. Unless you have particularly lengthy driver
- initialization code, attempting to drop init() is probably more trouble than
- it's worth. To repeat, you're programming a driver in C for C's relative ease
- of use, not primarily to save memory.
-
-
- Let The Programmer Beware
-
-
- When choosing a name for your device, which goes in the last field of the
- header block, choose a name you won't use for a file. When DOS opens a file,
- it first checks the driver chain for a device driver with the same name. At
- this level, DOS treats devices and files in the same manner. If your file and
- a device name conflict, DOS will open the device instead of your disk file and
- you will get some unexpected results. To illustrate, create a file called lpt1
- with your text editor. Try writing some text to it using the DOS TYPE or COPY
- commands. The data will go to the line printer instead of the disk file. You
- won't be able to delete the file from the DOS command line either.
- It's difficult to incorporate many of the standard C library functions in
- device driver code. You must be absolutely sure you know what they do, since
- some depend on initialized data in other modules or may take actions that in
- some way compromise your driver code. Neither should you use standard library
- functions that call malloc(), because the driver has no heap. You should write
- your own versions of most standard library functions, unless you use only
- simple, compact ones like strcpy().
- Most compilers put uninitialized global data in a special segment that the
- startup code initializes to zeros when a program executes. Many programmers
- write C code that relies on this behavior, but it's an assumption you can't
- make in a device driver written in C. Make sure all data is initialized in
- some way before you use it, the same as you would do when using automatic
- (stack-based) variables. Also, make sure you instruct the compiler to enforce
- byte-packing of structures. Borland enforces byte-packing by default, but I
- believe the Microsoft compiler uses word-packing unless instructed not to.
- Stack probing is a useful adjunct to program debugging, but it is difficult to
- implement a method in a driver that works with a variety of compilers. Though
- I have ignored stack probing in my driver code listings, you may find a way to
- implement it. If your compiler inserts stack probes by default, make sure you
- turn them off.
-
-
- State Machines
-
-
- State machines are useful in the control of any rule-based system, and their
- application to device control is a prime example. Briefly, you can apply state
- machines to any system you can define in terms of a finite number of states
- (or "islands") with a definite set of rules for traveling or navigating
- between them. The state machine uses initialized data in tables (a state
- table) to organize these states and rules into a structure that reflects the
- actual operation of the system.
- The state machine knows only the current state of the system. Given a command
- or event, the state machine refers to the state table to determine whether
- that event is valid for that state. The event determines the state to which
- the system will travel next. The state table also contains a set of procedures
- (functions) that tell the system how to effect the transition to the next
- state. When the machine reaches the next state, it waits until it receives
- another event or command, then repeats the cycle.
-
-
- The State Table In C
-
-
-
- To illustrate the basic concepts of device control using a state machine, I
- developed a simple example that controls a hypothetical tape backup unit. To
- build the state table, start with a list of the available device controller
- commands, as in the simplified list in Figure 1. These commands form the basis
- of a list of valid events, or user commands (see Listing 4, tape.h). You may
- also need to define other user commands, formed from combinations of the basic
- device commands.
- Next, define the various states in which your device will exist at any one
- time. For my tape backup unit, I defined states such as READY, PLAY, and
- RECORD (see Listing 4). Then, given a set of well-defined states, decide which
- events will be valid for each state. These valid events determine the subset
- of states to which you can move, given your current position. For instance, if
- the tape unit is currently in a READY state, meaning you just inserted a
- cassette, you can move directly to any other state. If you're currently in the
- REWIND state, however, you can only stop rewinding and return to a READY
- state.
- When you have defined all your states and the valid connections between them,
- you should end up with something like the table in Table 1, the State
- Transition Table. Along the way you may want to draw some simple diagrams.
- Such diagrams quickly become complex when the number of valid connections
- between states (represented by lines with arrowheads) averages more than two
- or three. You may also want to include a list of the functions needed to
- effect the transition from one state to the next, as I've done in Table 1.
- You can translate the State Transition Table almost directly into a single
- array of structures. Although easy to understand, such an array will mean some
- degree of data repetition. I have instead used a series of cascading or
- multi-level tables connected by pointers, resulting in an efficient use of
- memory as well as reduced coding effort. Each valid state in the system uses
- an array of S_TABs, the basic data element, to create an event table (Listing
- 4). The last member of an event table must be the macro END, which indicates
- the end of the table. An event table defines the valid events for each state,
- the next state to move to, as well as a pointer to the list of functions to
- effect the transition. All the event tables are connected at the top by an
- array of pointers (the state table). Note that the current state of the system
- serves as an index into this array. The order of states in the array should
- correspond to the order in which you initially defined the states, as in
- Listing 4.
- Listing 5 contains the state table. One of the most difficult tasks is finding
- descriptive names for all the tables, especially the lists of pointers to
- functions. At first this may seem overly complex, but working with initialized
- data in tables significantly reduces your coding effort. Without them, your
- code would be littered with logical branching statements.
-
-
- The IOCTL Interface
-
-
- DOS device drivers make available at least two command code routines to
- control the driver itself. Applications use these IOCTL functions to pass
- control information directly to the driver to control the driver's other I/O
- functions. An IOCTL call does not necessarily result in any input/output
- interaction with the physical device. The control information's format is
- unspecified and is known only to the driver and the application program. DOS
- takes no part except to provide a standard interface for using the IOCTL
- functions. An application wishing to pass control data to a driver uses DOS
- function 44h, sub-functions 2 and 3, which read and write I/O control data,
- respectively.
- DOS passes requests for functions 44h, subfunctions 2 and 3 directly to the
- device driver once you have obtained a device handle using the DOS open
- file/device function, 3Dh. Calls to the DOS IOCTL functions end up in command
- code routines 3 and 12 inside the device driver, ioctl_read() and
- ioctl_write(), respectively ( Listing 2). A developer can use these functions
- for any purpose since nothing is specified other than the calling protocol.
- As you can see in Listing 5, I have used the DOS IOCTL interface as a direct
- channel to control the hypothetical tape drive. The driver uses only command
- routines 0, 3, and 12. Most of the unused routines deal with more "normal"
- device I/O, such as servicing DOS read/write requests. A real tape unit must
- protect itself from such actions, which could easily corrupt data already
- stored on the tape. Without this protection, anyone could write to the tape
- unit using the DOS TYPE command with command-line redirection. However, since
- command routine 8 (device_write() in Listing 2) returns 0 on every call, DOS
- thinks it's writing to a real device, even though nothing is actually done.
- Note also that command routines 3 and 12 both call tape_io() in Listing 5. In
- the interest of uniformity, I combined these two functions, since the only
- real difference between them is semantic. You can therefore use either
- subfunction 2 or 3 of DOS 44h to make IOCTL calls to the tape unit.
-
-
- Putting It All Together
-
-
- Function tape_io() serves as the entry point into the state machine driver.
- tape_io() receives a far pointer to the command IOCTL string. The pointer is
- passed in the request header's transfer buffer (Listing 3). Since DOS does not
- specify the format of the command information, I defined it as a structure of
- type CMDARG (Listing 4). This structure serves as the state machine's
- primitive memory. It contains the current state of the system, the command for
- the tape unit to execute, and the return status of the command and/or the tape
- unit hardware.
- tape_io() first points to the correct event table, using the CMDARG argument
- as an index into the *s_table[] array. tape_io() then enters a loop,
- attempting to match a valid event for the current state with the command code
- passed in CMDARG. If the function can find no match, it returns immediately
- with an error code. Otherwise,tape_io() updates CMDARG's current state with
- the next valid state from the event table, then immediately executes the
- functions associated with the state transition. The array of function pointers
- doesn't arbitrarily limit the number of functions needed to effect the
- transition, nor does it require null padding to fill unused spaces in the
- array.
- Commands sent to the state machine driver, tape_io(), originate from a
- separate controller program that issues DOS IOCTL calls. The code in ctl.c
- (Listing 6) acts as the tape unit's user interface, something the device
- driver controller code need not be concerned with.
- To use the DOS IOCTL functions, you must first obtain a device handle using
- open () (Listing 6). If open () reports an error, the device driver wasn't
- installed. DOS next determines if the device driver is functioning correctly,
- using function _dos_dev_info(). Two possibilities exist here, one that the
- device name is actually a disk file, or that the "IOCTL" bit (0x4000) in the
- driver's header block was not set. In the latter case, DOS will not complete
- an IOCTL function call because it assumes the device driver doesn't have an
- IOCTL interface. The program uses functions _dos_ioctl_read() and
- _dos_ioctl_write() to actually communicate with the tape unit. As outlined
- earlier, using either function accomplishes the same result, since both end up
- in the state machine driver function, tape_io().
-
-
- Alternatives To IOCTL
-
-
- Several alternatives to using the DOS IOCTL functions exist. Since DOS serves
- only as an intermediary, you could dispense with the overhead of a DOS call
- entirely and set up the driver as a combination standard device driver and
- TSR, like the Expanded Memory Manager. When DOS initializes your driver
- (command code 0), you would set up a software interrupt to point to the device
- controller code. Application programs would access the driver through the
- interrupt, using much the same command interface as described earlier. You're
- then free to make DOS calls from inside the controller code, since DOS is no
- longer involved up front.
- Continuing along these lines, you could do away with the device driver
- altogether and put the entire controller code inside a TSR. Since the tape
- unit driver doesn't have to service normal I/O requests via DOS, as device
- drivers usually do, you're free to do this.
- However, using a TSR involves a varying amount of risk since DOS never fully
- supported them. Using a state machine in a device driver, on the other hand,
- gives the device driver full support of DOS. By using standard DOS function
- calls, you also avoid potential conflicts over the use of software interrupts.
-
-
- Rolling Your Own Driver
-
-
- I have presented the driver code in Listing 1 and Listing 2 as a sort of
- template, which you can use to get a head start past the coding basics. The
- template code allows you to concentrate on writing the actual device
- controller code in the language you probably know best. However, even with
- solid support behind you, you can still get stuck when testing and debugging
- your driver.
- Make sure you keep all device controller code in a separate module, as I did
- in Listing 5, tape.c. You can easily convert the module into a transient .EXE
- test bed, using compile-time #defines. Leave the test bed code in place,
- inactivated, when you produce the finished driver. When you make changes and
- retest, simply unpack the test bed code.
- Testing your code as a normal .EXE program means you can easily use a software
- debugger. Your test code should include a means to load a request header with
- appropriate data, exactly as DOS would, and make calls to the exec_command()
- routine. You can then examine the data and status codes returned in the
- request header when the call completes. Making actual calls to the strategy
- and interrupt routines (Listing 1), as DOS does, is probably unnecessary since
- this code is fully debugged.
- A driver still needs to be tested when linked into the DOS driver chain. To
- prepare for this, create a bootable floppy complete with a dummy config.sys
- that includes a command to load your test driver from the hard disk. Also
- include a dummy autoexec.bat that simply calls your normal autoexec.bat file
- on the hard disk.
- You'll probably find a software-only debugger difficult to use when testing a
- driver in place, although I have never tried it. You can set breakpoints
- within the body of the driver much as you normally would. However, most
- debuggers make DOS calls too. When they do so, they re-enter DOS, corrupting
- the DOS stack and leading directly to a system crash. Instead of using a
- debugger, you should rely as much as possible on testing the code thoroughly
- as an .EXE transient, as outlined above. To test the driver in place, you may
- find a "non-intrusive monitor" to be of some help (see reference [5]). You can
- then monitor the values of selected variables inside the driver during
- execution of a normal test program running in the foreground.
-
-
- Some Final Thoughts
-
-
- If you've tinkered with device drivers before and gotten stuck, this article
- may give you new impetus to try again. Even if you know your way around DOS
- drivers, it always helps to have a fresh slant on them, if only to affirm that
- your own way of doing it is better.
- This article has also shown that a state machine approach to writing device
- control code can provide considerable benefits. Its table-driven strategy
- forms a natural, built-in Application Program Interface (API) for device
- control from an application program. Using the API, anyone can build a desired
- user interface to the tape unit. You're also able to modify the user interface
- at any time without touching the device driver.
- This arrangement is very flexible, since you can locate the user interface
- code in any situation. You could place such code in a normal .EXE application
- that includes tape backups as an option. Another possibility would be a TSR
- that uses the tape driver to make background saves of specified files when the
- TSR detects that the files have been modified.
- References
- Duncan, Ray. Advanced MS-DOS Programming, 2nd Ed.. Microsoft Press, Redmond,
- WA.
- Fischer, Paul. "State Machines in C," The C Users Journal, December 1990, pp.
- 119-122.
- Johnson, Marcus. "Writing MS-DOS Device Drivers," The C Users Journal,
- December 1990, pp. 41-57.
- Lai, Robert. S. and The Waite Group. Writing MS-DOS Device Drivers.
- Addison-Wesley.
- Naleszkiewicz, John R. "A Non-Intrusive TSR Monitor," TECH Specialist, June
- 1991, pp. 32-40.
- Figure 1 TAPE Device Controller Functions
- 1. Query device status
-
- 2. Read data block
- 3. Write data block
- 4. Enable fast forward
- 5. Enable rewind
- 6. Stop
- 7. Initialize device
- 8. Eject
-
- Table 1 State Transition Table
- Current State Valid Commands Next State Functions
- --------------------------------------------------------------
- OFF CMD_POWER_ON ON initialize, status
- CMD_CHK_STATUS OFF status
- ON CMD_POWER_OFF OFF power off
- CMD_INSERT READY status
- CMD_CHK_STATUS ON status
- READY CMD_POWER_OFF OFF eject, power off
- CMD_EJECT ON eject
- CMD_RECORD RECORD record
- CMD_PLAY PLAY play
- CMD_FFORWARD FFORWARD fforward
- CMD_REWIND REWIND rewind
- CMD_CHK_STATUS READY status
- RECORD CMD_STOP READY stop
- CMD_EJECT ON stop, eject
- CMD_FFORWARD FFORWARD stop, fforward
- CMD_PLAY PLAY stop, play
- CMD_REWIND REWIND stop, rewind
- RECORD RECORD status
- PLAY CMD_STOP READY stop
- CMD_EJECT ON stop, eject
- CMD_FFORWARD FFORWARD stop, fforward
- CMD_REWIND REWIND stop, rewind
- CMD_RECORD RECORD stop, record
- CMD_CHK_STATUS PLAY status
- FFORWARD CMD_STOP READY stop
- CMD_EJECT ON stop, eject
- CMD_PLAY PLAY stop, play
- CMD_REWIND REWIND stop, rewind
- CMD_RECORD RECORD stop, record
- CMD_CHK_STATUS FFORWARD status
- REWIND CMD_STOP READY stop
- CMD_EJECT ON stop, eject
- CMD_PLAY PLAY stop, play
- CMD_RECORD RECORD stop, record
- CMD_FFORWARD FFORWARD stop, fforward
- CMD_CHK_STATUS REWIND status
-
- Listing 1 (start.asm) Device Driver Startup Module for C
- ; -----------------------------------------------------
- ; This module must appear first in the link order.
- ;
- ; Coded for TASM 2.0
- ;
- ; Source code Copyright (c) 1991 T.W. Nelson.
- ; May be used only with appropriate
- ; acknowledgement of copyright.
- ; -----------------------------------------------------
- ;
-
- ; define segment order first ....
- ;
-
- _TEXT segment byte public 'CODE'
- _TEXT ends
- -DATA segment word public 'DATA'
- -DATA ends
- -BSS segment word public 'BSS'
- -BSS ends
- ENDSEG segment word public 'ENDSEG'
- ENDSEG ends
-
- ; Establish TINY model segment group ....
- ;
- DGROUP group _TEXT,_DATA,_BSS,ENDSEG
- ASSUME cs:DGROUP,ds:DGROUP,es:NOTHING,ss:NOTHING
-
- STACKSIZ equ 256 ;stack size, words
-
- _TEXT segment
-
- org 0 ;all driver headers start at 0
-
- ; driver header data .......
- ;
-
- db 4 dup(Offh) ;points to ground (NULL)
- dw 8000h + 4000h ;driver attributes: char device
- ;...and ioctl read/write
- dw _dev_strategy ;offset to strategy routine
- dw _dev_interrupt ;offset to interrupt routine
- db 'TAPEXXXX' ;device name = 8 bytes
-
- rhdr_seg dw ? ;request header segment
- rhdr_off dw ? ;request header offset
- caller_ss dw ? ;for stack setup
- caller_sp dw ?
-
- ; Function appears in module 'exec.c' and must be
- ; defined as:
- ;
- ; void exec_command( REQHDR far *rhdr )
- ;
- extrn_exec_command:near
-
- ; First part of two-step driver call from DOS. This
- ; only saves a pointer to the request header used in
- ; the interrupt routine ......
- ;
- _dev_strategy proc far ;always far DOS call
- mov cs:rhdr_seg,es
- mov cs:rhdr_off,bx
- ret
- _dev_strategy endp
-
- ; Second (workhorse) part of 2-step driver call.
- ; Although called an 'interrupt' function, this is
- ; not a true interrupt handler .......
- ;
-
- _dev_interrupt proc far ;always far DOS call
-
- ;----- Save caller's CPU state and stack context ....
- push ax
- push bx
- push cx
- push dx
- push di
- push si
- push bp
- push ds
- push es
- pushf
- mov cs:caller_ss,ss
- mov cs:caller_sp,sp
-
- ;----- Setup segment addressability and driver
- ; stack frame ............
- mov ax,cs ;local data seg
- cli
- mov ds,ax
- mov es,ax
- mov ss,ax
- mov sp,offset cs:stack_top
- mov bp,sp ;set C stack frame
- sti
-
- ;----- Pass pointer to req hdr and execute command....
- mov ax,cs:rhdr_seg
- push ax
- mov ax,cs:rhdr_off
- push ax
- call _exec_command
- pop ax
- pop ax
-
- ;----- restore CPU state ..........
- cli
- mov ss,cs:caller_ss
- mov sp,cs:caller_sp
- sti
- popf
- pop es
- pop ds
- pop bp
- pop si
- pop di
- pop dx
- pop cx
- pop bx
- pop ax
- ret
- _dev_interrupt endp
-
- _TEXT ends
-
- ; ------------------------------------------------------
-
- _DATA segment
-
-
- dw STACKSIZ dup(?) ;driver's stack
- stack_top dw ?
-
- _DATA ends
-
- ; ------------------------------------------------------
-
- ENDSEG segment
-
- __TheEnd label byte ;location of cs:__TheEnd
- ;marks break address for DOS
- public __TheEnd
-
- ENDSEG ends
-
- END
-
- ; ------ End of File -----------------------------------
-
-
- Listing 2 (exec.c) Execute Driver Command
- /* ----------------------------------------------------
- * Author: T.W. Nelson
- * Compile options:
- * Version: 1.00
- * Date: 05-0ct-1991
- * Notes:
- *
- * Source code Copyright (c) 1991 T.W. Nelson.
- * May be used only with appropriate
- * acknowledgement of copyright.
- * -------------------------------------------------- */
-
- #include <dos.h>
- #include "driver.h"
-
- extern unsigned init(void), media_check(void),
- build_bpb(void), ioctl_read(void),
- device_read(void), nd_read(void),
- input_status(void), input_flush(void),
- device_write(void), verify_write(void),
- output_status(void), output_flush(void),
- ioctl_write(void), device_open(void),
- device_close(void), rem_media(void),
- output_busy(void), generic_ioctl(void),
- get_logdev(void), set_logdev(void),
- bad_command(void);
-
- static unsigned (*Dispatch[])(void) = {
- init, //0 = initialize driver
- media_check, //1
- build_bpb, //2 = build BIOS param block
- ioctl_read, //3 = read io control data
- device_read, //4
- nd_read, //5 = non-destructive read
- input_status, //6
- input_flush, //7 = flush input buffers
- device_write, //8
-
- verify_write, //9 = write with verify
- output_status, //10
- output_flush, //11 = flush output buffers
- ioctl_write, //12 = write io control data
- device_open, //13 (DOS 3+)
- device_close, //14 (DOS 3+)
- rem_media, //15 = removable media (3+)
- output_busy, //16 = output until busy (3+)
- bad_command, //17 = not used
- bad_command, //18 = not used
- generic_ioctl, //19 = generic io control (3.2+)
- bad_command, //20 = not used
- bad_command, //21 = not used
- bad_command, //22 = not used
- get_logdev, //23 = get logical device (3.2+)
- set_logdev, //24 = set logical device (3.2+)
- };
-
- #define NUM_CMDS (sizeof(Dispatch)/sizeof(void *))
-
- REQHDR far *Rh = (REQHDR far *) 0;
- unsigned int errno = 0; //for std library code
- extern unsigned tape_io(unsigned ofs, unsigned seg);
-
- static char signon[] =
- "Demo TAPE device driver, V1.00\r\n\n";
-
- void exec_command( REQHDR far *rhdr )
- {
- /* Call the command-code routine from the dispatch
- * table indexed by the command code. Access to
- * caller's request header is made available thru
- * global pointer 'Rh'. 'DONE' bit is always set
- * on exit.
- */
-
- Rh = rhdr; //assign to global copy
- Rh->status = 0; //clear status word
-
- Rh->status = ( (Rh->cmd >= NUM_CMDS) ?
- bad_command() :
- (*Dispatch[ Rh->cmd ])() );
- Rh->status = IM_DONE; //set 'done' bit
- }
-
- /*----------------------------------------------------
- * Command-code routines. Called indirectly by the
- * interrupt routine thru the dispatch table. All
- * routines should return the appropriate status code
- * to be assigned to the request header on return to
- * exec_command(). Return will be 0 if no error,
- * or (IS_ERROR + error_code) (see driver.h) if an
- * error was detected.
- *----------------------------------------------------*/
-
- unsigned init( void ) /* 0 */
- {
- /* Driver initialization function. Called once
- * by DOS on boot up when CONFIG.SYS is read.
-
- * Only DOS functions 01h-OCh and 30h can be called
- * here.
- */
-
- extern char _TheEnd; //marks end of driver
- void putstr( const char *str ); //below
-
- putstr( signon);
-
- /* Other initialization code goes here ......
- * If needed, a pointer to the command line after
- * 'DEVICE=' in CONFIG.SYS is available starting
- * at Rh->xfer_cnt (or, Rh + 18).
- */
-
- // Return break address to DOS .......
- Rh->xfer_seg = FP_SEG( (void far *) &_TheEnd );
- Rh->xfer_ofs = FP_OFF( (void far *) &_TheEnd );
-
- return 0;
- }
-
- unsigned media_check( void ) /* 1 */
- {
- /* Block devices only. Function determines
- * whether or not a floppy disk was changed,
- * allowing DOS to bypass re-reading the FAT.
- */
- return 0; }
-
- unsigned build_bpb(void) /* 2 */
- {
- /* Block devices only. Builds a table of disk
- * parameters when media_check() returns the
- * disk-change code.
- */
- return 0; }
-
- unsigned ioctl_read(void) /* 3 */
- {
- /* Character and block devices. Allows device
- * driver to pass data directly to application.
- * Called by DOS int21h/44h subfunction 2. The
- * IOCTL_RW bit (14) must be set in device block's
- * attribute word.
- */
- return tape_io(Rh->xfer_ofs, Rh->xfer_seg);
- }
-
- unsigned device_read(void) /* 4 */
- {
- /* Character and block devices. Read data from
- * the device into a specified buffer. Returns
- * #bytes or sectors transfered.
- */
- return 0; }
-
- unsigned nd_read(void) /* 5 */
- {
-
- /* Character devices only. Lets caller peek at
- * next byte in device's buffer w/o removing it.
- */
- return 0; }
-
- unsigned input_status(void) /* 6 */
- {
- /* Character devices only. Returns current input
- * status for device. Sets IM_BUSY bit if input
- * request cannot proceed immediately (characters
- * are waiting to be read from the input buffers).
- */
- return 0; }
-
- unsigned input_flush(void) /* 7 */
- {
- /* Character devices only. Function discards any
- * data pending in device's input buffers.
- */
- return 0; }
-
- unsigned device_write(void) /* 8 */
- {
- /* Character and block devices. Writes data from
- * the specified buffer to a device. Returns #
- * bytes or sectors written.
- */
- return 0; }
-
- unsigned verify_write(void) /* 9 */
- {
- /* Character and block devices. Same as
- * device_write(), but performs a read-after-
- * write verification.
- */
- return 0; }
-
- unsigned output_status(void) /* 10 */
- {
- /* Character devices only. Returns the current
- * output status for the device. Sets IM_BUSY bit
- * if write request cannot proceed immediately.
- */
- return 0; }
-
- unsigned output_flush(void) /* 11 */
- {
- /* Character devices only. Function discards any
- * data in output buffers and any pending output
- * requests.
- */
- return 0; }
-
- unsigned ioctl_write(void) /* 12 */
- {
- /* Character and block devices. Allows application
- * to pass control info directly to driver. Called
- * by DOS int21h/44h subfunction 3. The IOCTL_RW
- * bit (14) must be set in device block's attribute
-
- * word.
- */
- return tape_io(Rh->xfer_ofs, Rh->xfer_seg);
- }
-
- unsigned device_open(void) /* 13 */
- {
- /* Character and block devices, DOS 3+. Manages
- * reference count of #open files for block devices,
- * or device initialization for character devices.
- * Called only if bit 11 (OCR_MEDIA) is set in
- * attribute word in header block.
- */
- return 0; }
-
- unsigned device_close(void) /* 14 */
- {
- /* Character and block devices, DOS 3+. The
- * opposite of command-code 13, device_open().
- */
- return 0; }
-
- unsigned rem_media(void) /* 15 */
- { /* Block devices only. Sets IM_BUSY if media is
- * non-removable. Called only if bit 11
- * (OCR_MEDIA) is set in attribute word in header
- * block.
- */
- return 0; }
-
- unsigned output_busy(void) /* 16 */
- {
- /* Character devices only, DOS 3+. Transfers data
- * to a device from specified buffer until device
- * signals busy. Called only if attribute bit 13
- * (OUTPUT_BUSY) is set.
- */
- return 0; }
-
- unsigned generic_ioctl(void) /* 19 */
- {
- /* Character and block devices, DOS 3.2+. This
- * function is generally the same as command-codes
- * 3 and 12, with a different calling protocol. Bit
- * 6 (GEN_IOCTL) must be set in attribute word.
- */
- return 0; }
-
- unsigned get_logdev(void) /* 23 */
- {
- /* Block devices only, DOS 3.2+. Function returns
- * number of logical devices (drive letters)
- * assigned to a physical device. Bit 6
- * (GEN_IOCTL) must be set in attribute word.
- */
- return 0; }
-
- unsigned set_logdev(void) /* 24 */
- {
-
- /* Block devices only, DOS 3.2+. Function sets
- * number of logical devices assigned to a
- * physical device. Bit 6 (GEN_IOCTL) must be set
- * in attribute word.
- */
- return 0; }
-
-
- unsigned bad_command( void )
- {
- return IS_ERROR + INV_COMMAND;
- }
-
- void putstr( const char *str )
- {
- /* Utility function to output an asciiz-format
- * string to monitor using BIOS-level output....
- * Doesn't add an auto-CR; use '\r' in your
- * string. Coding putstr() in C adds 200-300 more
- * bytes (from int86()) to the TAPE.SYS image in
- * memory, relative to an asm version.
- */
-
- union REGS regs;
-
- while( *str) {
- regs.h.al = (char) *str++;
- regs.h.ah = 0x0e; //BIOS write TTY
- regs.x.bx = 0; //page 0
- int86( 0x10, ®s, ®s );
- }
- }
-
- /* ------ End of File --------------------*/
-
-
- Listing 3 (driver._H) MS-DOS Device Driver #defines and Definitions
- #ifndef_DRIVER_H
- #define_DRIVER_H
-
- /* Format of generic request header block. Depending
- * on the command code, data specific to the command is
- * carried in data[] buffer ......... */
- typedef struct request_block {
- unsigned char length; //length of REQHDR
- unsigned char unit; //unit # (block only)
- unsigned char cmd; //command code
- unsigned int status; //return status
- unsigned char reserved[8];
- unsigned char mtype; //media type
- unsigned int xfer_ofs; //xfer buffer offset
- unsigned int xfer_seg; // " " segment
- unsigned int xfer_cnt; //bytes to xfer
- unsigned char data[12]; //other command data
- } REQHDR;
-
- /* bit settings for upper byte of return
- * status word in REQHDR .... */
- #define IS_ERROR 0x8000
-
- #define IM-BUSY 0x0200
- #define IM_DONE 0x0100
-
- /* values returned in lower byte of REQHDR status
- * word if ERROR bit (bit 15) is set ..... */
- #define WRITE_PROTECT 0x0000
- #define INVALID_UNIT 0x0001
- #define NOT_READY 0x0002
- #define INV_COMMAND 0x0003
- #define CRC_ERROR 0x0004
- #define BAD_REQ_LENGTH 0x0005
- #define SEEK_ERROR 0x0006
- #define UNKNOWN_MEDIA 0x0007
- #define INV_SECTOR 0x0008
- #define OUT_OF_PAPER 0x0009
- #define WRITE_FAULT 0x000A
- #define READ_FAULT 0x000B
- #define GENERAL_FAILURE 0x000C
- #define INV_DISK_CHANGE 0x000F //DOS 3+ only
-
- /* Bit settings for attribute word ........ */
- #define IS_NUL 0x0004 //current NUL device
- #define IS_CLOCK 0x0008 //current CLOCK device
- #define GEN_IOCTL 0x0040 //generic ioctl: DOS 3.2+
- #define OCR_MEDIA 0x0800 //open-close-remove media
- #define IOCTL_RW 0x4000 //ioctl r/w supported (2+)
-
- /* Attribute bits for character devices only ...... */
- #define IS_STDIN 0x0001 //std input device
- #define IS_STDOUT 0x0002 //std output device
- #define USE_INT29H 0x0010 //CON device uses int 29h
- /define OUTPUT_BUSY 0x2000 //uses output until busy
- /define IS_CHAR 0x8000 //is character device
-
- /* Attribute bits for block devices only .... */
- #define BIG_SECTOR 0x0002 //32-bit sectors (DOS 4)
- #define USE_BPB 0x2000 //use BPB for media info
-
- #endif //__DRIVER_H
-
- /* ------ End of File ----------------------- */
-
-
- Listing 4 (tape.h) State Table Macros and Structure Definitions for TAPE
- Device
- /* Device states .... */
- enum device_states {
- OFF, //0
- ON, //1
- READY, //2
- RECORD, //3
- PLAY, //4
- FFORWARD, //5
- REWIND, //6
- MAX_STATE //7
- };
-
- /* User commands (events)..... */
- enum user_cmds {
- CMD_POWER_ON,
-
- CMD_POWER_OFF,
- CMD_INSERT,
- CMD_EJECT,
- CMD_RECORD,
- CMD_PLAY,
- CMD_FFORWARD,
- CMD_REWIND,
- CMD_CHK_STATUS,
- CMD_STOP
- };
-
- #define END -1
- #define OK 0
- #define NOTOK -1
-
- typedef struct command_arg {
- int c_state; //current state
- int cmd; //driver command
- int status; //device status
- } CMDARG;
-
- typedef struct state_table {
- int event;
- int n_state;
- int (**procs)(CMDARG far *arg); //-> to array of
- //....pointers to functions
- } S_TAB;
-
- /* ----- End of File ------------------------*/
-
-
- Listing 5 (tape.c) State Machine Constructs for TAPE Device
- /* -----------------------------------------------------
- * Author: T.W. Nelson
- * Compile options:
- * Version: 1.00
- * Date: 07-Oct-1991
- * Notes:
- *
- * Source code Copyright (c) 1991 T.W. Nelson.
- * May be used only with appropriate
- * acknowledgement of copyright.
- * -------------------------------------------------- */
-
- #include "driver.h"
- #include "tape.h"
-
- extern void putstr( const char *str );
-
- extern int initialize(), status(), eject(), record(),
- play(), fforward(), rewind(), stop(), cstate(),
- shutdown();
-
- //Arrays of function pointers to be executed
- //in the order listed, to effect transition from
- //the current state to next desired state.....
- //
- int (*f_init[])() = { initialize, cstate, 0 };
-
-
- int (*f_stat[])() = { status, cstate, 0 };
-
- int (*f_off[])() = { shutdown, cstate, 0 };
-
- int (*f_ejoff[])() = { eject, shutdown, cstate, 0 };
-
- int (*f_eject[])() = { eject, cstate, 0 };
-
- int (*f_rec[])() = { record, cstate, 0 };
-
- int (*f_play[])() = { play, cstate, 0 };
-
- int (*f_ffwd[])() = { fforward, cstate, 0 };
-
- int (*f_rwnd[])() = { rewind, cstate, 0 };
-
- int (*f_stop[])() = { stop, cstate, 0 };
-
- int (*f_steject[])() = { stop, eject, cstate, 0 };
-
- int (*f_strec[])() = { stop, record, cstate, 0 };
-
- int (*f_stplay[])() = { stop, play, cstate, 0 };
-
- int (*f_stffwd[])() = { stop, fforward, cstate, 0 };
-
- int (*f_strwnd[])() = { stop, rewind, cstate, 0 };
-
- //Event (Command) Tables describing valid events for a
- //state, the next state to transition to, as well as
- //the functions to effect the transition......
- //
- S TAB s_off[] = {
- /* command n_state funcs
- --------------------------------- */
- CMD_POWER_ON, ON, f_init,
- CMD_CHK_STATUS, OFF, f_stat,
- END, END, 0, };
-
- S_TAB s_on[] = {
- /* command n_state funcs
- ------------------------------------ */
- CMD_POWER_OFF, OFF , f_off,
- CMD_INSERT , READY, f_stat,
- CMD_CHK_STATUS, ON, f_stat,
- END , END , 0, };
-
- S_TAB s_ready[] = {
- /* command n_state funcs
- ------------------------------------ */
- CMD_POWER_OFF , OFF , f_ejoff,
- CMD_EJECT , ON , f_eject,
- CMD_RECORD , RECORD , f_rec,
- CMD_PLAY , PLAY , f_play,
- CMD_FFORWARD , FFORWARD, f_ffwd,
- CMD_REWIND , REWIND , f_rwnd,
- CMD_CHK_STATUS, READY , f_stat,
- END , END , 0, };
-
-
- S_TAB s_record[] = {
- /* command n_state funcs
- ------------------------------------ */
- CMD_STOP , READY , f_stop,
- CMD_EJECT , ON , f_steject,
- CMD_FFORWARD , FFORWARD, f_stffwd,
- CMD_PLAY , PLAY , f_stplay,
- CMD_REWIND , REWIND , f_strwnd,
- CMD_CHK_STATUS, RECORD , f_stat,
- END , END , 0, };
-
- S_TAB s_play[] = {
- /* command n_state funcs
- ------------------------------------ */
- CMD_STOP , READY , f_stop,
- CMD_EJECT , ON , f_steject,
- CMD_FFORWARD , FFORWARD, f_stffwd,
- CMD_REWIND , REWIND , f_strwnd,
- CMD_RECORD , RECORD , f_strec,
- CMD_CHK_STATUS, PLAY , f_stat,
- END , END , 0, };
-
- S_TAB s_fforward[] = {
- /* command n_state funcs
- ------------------------------------ */
- CMD_STOP , READY , f_stop,
- CMD_EJECT , ON , f_steject,
- CMD_PLAY , PLAY , f_stplay,
- CMD_REWIND , REWIND , f_strwnd,
- CMD_RECORD , RECORD , f_strec,
- CMD_CHK_STATUS, FFORWARD, f_stat,
- END , END , 0, };
-
- S_TAB s_rewind[] = {
- /* command n_state funcs
- ------------------------------------ */
- CMD_STOP , READY , f_stop,
- CMD_EJECT , ON , f_steject,
- CMD_PLAY , PLAY , f_stplay,
- CMD_RECORD , RECORD , f_strec,
- CMD_FFORWARD , FFORWARD, f_stffwd,
- CMD_CHK_STATUS, REWIND , f_stat,
- END , END , 0, };
-
- //The actual "state table" that connects all
- //the foregoing initialized data into one. enum
- //'device states' is an index into this array
- //of struct pointers. These MUST be in same order
- //as the enum in 'tape.h' ........
- //
- S_TAB *s_table[] = {
- s_off, s_on, s_ready, s_record, s_play,
- s_fforward, s_rewind,
- };
-
- unsigned tape_io( CMDARG far *arg )
- {
- /* State Machine Driver. Called by DOS thru driver
- * interrupt routine, command codes 3 & 12.
-
- */
-
- int i;
- S_TAB *pcmd; //-> event (cmd) table
- int (**pfun)(CMDARG far *arg); //-> function list
-
- //point to the correct event table .....
- if( arg->c_state >= MAX_STATE )
- return IS_ERROR-+ INV_COMMAND;
-
- pcmd = s_table[arg->c_state];
-
- //find the specified command in the event table...
- for( i = 0; pcmd[i].event != END; i++ )
- if( pcmd[i].event == arg->cmd )
- break;
-
- //check for invalid command .....
- if( pcmd[i].event == END ) {
- putstr("Invalid command specified\r\n");
- return IS_ERROR + INV_COMMAND;
- }
-
- //indicate next state for application .......
- arg->c_state - pcmd[i].n_state;
-
- //execute the function list ........
- pfun = pcmd[i].procs;
-
- for( i = 0;pfun[i]; i++)
- (*pfun[i])(arg);
-
- return 0;
- }
-
- int status(CMDARG far *arg)
- {
- arg->status = OK;
- return 0;
- }
-
- int initialize(CMDARG far *arg)
- {
- putstr("Device initialized\r\n");
- status(arg);
- return 0;
- }
-
- int eject(CMDARG far *arg)
- {
- putstr( "Cassette ejected\r\n");
- status(arg);
- return 0;
- }
-
- int record(CMDARG far *arg)
- {
- putstr( "Recording\r\n");
- status(arg);
-
- return 0;
- }
-
- int play(CMDARG far *arg)
- {
- putstr( "Playing\r\n");
- status(arg);
- return 0;
- }
-
- int fforward(CMDARG far *arg)
- {
- putstr( "Fast forwarding\r\n");
- status(arg);
- return 0;
- }
-
- int rewind(CMDARG far *arg)
- {
- putstr( "Rewinding\r\n");
- status(arg);
- return 0;
- }
-
- int stop(CMDARG far *arg)
- {
- putstr( "Stopping\r\n"};
- status(arg);
- return 0;
- }
-
- int shutdown(CMDARG far *arg)
- {
- putstr( "Powered down\r\n");
- status(arg);
- return 0;
- }
-
- int cstate(CMDARG far *arg)
- {
- /*Print current state, after transition ....*/
-
- static char *s_name[] = {
- "OFF", //0
- "ON", //1
- "READY", //2
- "RECORD", //3
- "PLAY", //4
- "FFORWARD", //5
- "REWIND", //6
- };
-
- putstr( "Current state is");
- putstr( s_name[arg->c_state] );
- putstr( "\r\n" );
- return 0;
-
- /* ----- End of File ------------------------ */
-
-
-
- Listing 6 (ctl.c) Test IOCTL Program for TAPE Device Driver
- /* ----------------------------------------------------
- * Author: T.W. Nelson
- * Compile options:
- * Version: 1.00
- * Date: 29-Sep-1991
- * Notes:
- * ------------------------------------------------- */
-
- #include <stdio.h>
- #include <io.h>
- #include <fcntl.h>
- #include <sys\stat.h>
- #include <dos.h>
- #include <bios.h>
- #include "tape.h"
-
- #define IOCTL_OK Ox4000 //ioctl check bit
- #define IM_A_DEVICE 0x0080 //device check bit
- #define IO_BUF_SIZE_sizeof(CMDARG)
-
- union REGS regs;
- struct SREGS sregs;
- char Dname[] = "TAPEXXXX";
- int ioHandle;
- CMDARG ioArg;
-
- char *Menu[] = {
- "",
- "Power ON .............0",
- "Power OFF ............1",
- "Insert Cassette ......2',
- "Eject Cassette .......3",
- "Record ...............4",
- "Play .................5",
- "Fast Forward .........6",
- "Rewind ...............7",
- "Check Status .........8",
- "Stop .................9",
- "Quit .................<Esc>",
- "",
- 0
- );
-
- extern int_doserrno;
-
- int _dos_dev_info( int handle,
- size_t *info )
- {
- /* Get information about specified handle assoc
- * with a file or device and return it in 'info'.
- * Returns 0 if successful, !0 if not. Error code
- * contained in global '_doserrno'.
- */
-
- regs.x.ax = 0x4400; //DOS IOCTL get device info
- regs.x.bx = handle;
- intdos(®s, ®s );
-
- *info = regs.x.dx; //assign device info word
-
- return regs.x.cflag; //CY signals error
- }
-
- int_dos ioctl_read( int handle,
- void *iobuf,
- size_t nbytes)
- {
- /* Read ioctl data from a character device driver
- * into 'iobuf'. Returns 0 if successful, !0 if not.
- * Error code contained in global '_doserrno'. This
- * call ends up as the device driver's cmd code 3,
- * ioctl_read.
- */
-
- regs.x.ax = 0x4402; //DOS read ioctl data
- regs.x.bx - handle;
- regs.x.cx = nbytes;
- sregs.ds = FP_SEG( iobuf );
- regs.x.dx = FP_OFF( iobuf );
- intdosx( ®s, ®s, &sregs );
-
- return regs.x.cflag; //CY signals error
- }
-
- int _dos_ioctl write( int handle,
- void *iobuf,
- size_t nbytes )
- {
- /* Write ioctl data to a character device driver
- * from 'iobuf'. Returns 0 if successful, !0 if not.
- * Error code contained in global '_doserrno'. This
- * call ends up as the device driver's cmd code 12,
- * ioctl_write.
- */
-
- regs.x.ax = 0x4403; //DOS write ioctl data
- regs.x.bx = handle;
- regs.x.cx = nbytes;
- sregs.ds - FP_SEG( iobuf );
- regs.x.dx = FP_OFF( iobuf );
- intdosx( ®s, ®s, &sregs );
-
- return regs.x.cflag; //CY signals error
- }
-
- void print_menu(void )
- {
- char **p;
-
- for( p = Menu; *p; p++ )
- printf( "%s\n", *p );
- }
-
- main(int argc, char **argv )
- {
- int cmd;
- size_t d_info;
-
-
- ioHandle = open(Dname, O_RDWR);
-
- if(ioHandle == -1) {
- printf("Unable to open TAPE device\n");
- return 1;
- }
-
- //Verify that TAPE is a device and that
- //it supports ioctl calls.....
-
- _dos_dev_info(ioHandle, &d_info);
-
- if((d_info & IM_A_DEVICE) == 0) {
- printf("Sorry,'%s' is a file\n", Dname);
- return 1;
- }
-
- if( (d_info & IOCTL_OK) == 0) {
- printf("Sorry, TAPE doesn't support IOCTL\n");
- return 1;
- }
-
- //Initialize the device at 'boot-up' ....
- ioArg.c_state = OFF;
- ioArg.cmd = CMD_POWER_ON;
- _dos_ioctl_write( ioHandle, (void*) &ioArg,
- IO_BUF_SIZE);
-
- //Execute user control loop .....
- while( 1 ) {
- print_menu();
- printf( "Enter selection: ");
- cmd = bioskey(0);
- cmd &= 0x00ff; //zap scan code
-
- if( cmd == 27 )
- break;
-
- printf("%c\n\n",cmd); //echo cmd
- ioArg.cmd = cmd - '0'; //to integer
- _dos_ioctl_read( ioHandle,
- (void *) &ioArg,
- IO_BUF_SIZE);
-
- }
-
- return 0;
- }
-
- /* ----- End of File -------------------------- */
-
- Listing 7 Makefile for TAPE.SYS Demo Device Driver
- MODEL = s
- LIBS = \lib\c$(MODEL)
- LINK = /m /s
- COMPILE = -w -c
- ASSEMBLE = /w+ /mx
-
-
- .c.obj:
- tcc $(COMPILE) -m$(MODEL) $*
-
- .asm.obj:
- tasm $(ASSEMBLE) $*
-
- all: tape.sys
-
- start.obj: start.asm
-
- exec.obj: exec.c driver.h
-
- tape.obj: tape.c driver.h tape.h
-
- tape.exe: start.obj exec.obj tape.obj
- tlink $(LINK) start exec tape,tape.exe,,$(LIBS)
-
- tape.sys: tape.exe
- exe2bin tape.exe tape.sys
- del tape.exe
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- The Header <signal.h>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
- ANSI C standards committee, X3J11, and convenor of the ISO C standards
- committee, WG14. His latest book is The Standard C Library, published by
- Prentice-Hall. You can reach him care of The C Users Journal or via Internet
- at pjp@plauger.uunet.uu.net.
-
-
-
-
- Introduction
-
-
- A signal is an extraordinary event that occurs during the execution of a
- program. Synchronous signals occur because of actions that your program takes.
- Division by zero is one example. Accessing storage improperly is another.
- Asynchronous signals occur because of actions outside your program. Someone
- striking an attention key is one example. A separate program (executing
- asynchronously) signaling yours is another.
- A signal that is not ignored by your program demands immediate handling. If
- you do not specify handling for a signal that occurs, it is treated as a fatal
- error. Your program terminates execution with unsuccessful status. In some
- implementations, the status indicates which signal occurred. In others, the
- Standard C library writes an error message to the standard error stream before
- it terminates execution.
- The header <signal.h> defines the code values for an open-ended set of
- signals. It also declares two functions:
- raise, which reports a synchronous signal
- signal, which lets you specify the handling of a signal
- You can handle a signal one of three ways:
- default handling is to terminate execution, as described above
- ignoring the signal effectively discards it
- handling the signal causes control to pass to a function that you designate
- In the last case, the function that you designate is called a signal handler.
- The Standard C library calls a signal handler when its corresponding signal is
- reported. Normal execution of the program is suspended. If the signal handler
- returns to its caller, execution of the program resumes at the point where it
- was suspended. Aside from the delay, and any changes made by the signal
- handler, the behavior of the program is unaffected.
-
-
- Synchronization
-
-
- This sounds like elegant machinery, but it is not. The occurrence of a signal
- introduces a second thread of control within a program. That raises all sorts
- of issues about synchronization and reliable operation. The C Standard
- promises little in either regard. C programs have been handling signals since
- the earliest days of the language. Nevertheless, a portable program can safely
- take very few actions within a signal handler.
- One problem is the Standard C library itself. If called with valid arguments,
- no library function should ever generate a synchronous signal. But an
- asynchronous signal can occur while the library is executing. The signal may
- suspend program execution part way through a print operation, for example.
- Should the signal handler print a message, an output stream can end up in a
- confused state. There is no way to determine from within a signal handler
- whether a library function is in an unsafe state.
- Another problem concerns data objects that you declare to have volatile types.
- That warns the translator that surprising agents can access the data object,
- so it is careful how it generates accesses to such a data object. In
- particular, it knows not to perform optimizations that move the accesses to
- volatile data objects beyond certain sequence points. A signal handler is, of
- course, a surprising agent. Thus, you should declare any data object you
- access within a signal handler to have a volatile type. That helps, provided
- the signal is synchronous and occurs between two sequence points where the
- data object is not accessed. For an asynchronous signal however, no amount of
- protection suffices. Signals are not confined to suspending program execution
- only at sequence points.
- The C Standard offers a partial solution to the problem of writing reliable
- signal handlers. The header <signal.h> defines the type sig_atomic_t. It is an
- integer type that the program accesses atomically. A signal should never
- suspend program execution part way through the access of a data object
- declared with this type. A signal handler can share with the rest of the
- program only data objects declared to have type volatile sig_atomic_t.
-
-
- Shortcomings Of Signals
-
-
- As a means of communicating information, signals leave much to be desired. The
- semantics spelled out for signals in the C Standard is based heavily on their
- behavior under the early UNIX operating system. That system had serious lapses
- in the way it managed signals:
- Multiple signals could get lost. The system did not queue signals, but
- remembered only the last one reported. If a second signal occurred before a
- handler processed the first, a signal could go unnoticed.
- A program could terminate even when it endeavors to process all signals. When
- control first passes to a signal handler, handling for that signal reverts to
- default behavior. The signal handler must call signal to reestablish itself as
- the handler for the signal. Should that signal occur between entry to the
- handler and the call to signal, the default handler gets control and
- terminates the program.
- No mechanism exists for specifically terminating the handling of a signal. In
- other operating systems, the program enters a special state. Processing of
- subsequent signals blocks until the signal handler reports completion. On such
- systems, other functions may have to assist in processing signals properly.
- These can include abort and exit, declared in <stdlib.h>, and longjmp,
- declared in <setjmp.h>.
- Moreover, signals arise from an odd assortment of causes on any computer. The
- ones named in the C Standard are a subset of those supported by UNIX. These in
- turn derive from the interrupts and traps defined for the PDP-11. Mapping the
- sources of signals for a given computer onto those defined for C is often
- arbitrary. Mapping the semantics of signal handling for a given operating
- systems can be even more creative.
- The C Standard had to weaken the already weak semantics of UNIX signals to
- accommodate an assortment of operating systems:
- A given signal may never occur unless you report it with raise.
- A given signal may be ignored unless you call signal to turn it on.
- There's not much left.
- Thus, no portable use for the functions declared in <signal.h> can be defined
- with complete safety. You could, in principle, specify a handler for a signal
- that only raise reports. It's hard to imagine a situation where that works
- better than instead using setjmp and longjmp, declared in <setjmp.h>. Besides,
- you cannot ensure that a given signal is never reported on an arbitrary
- implementation of C. Any time your program handles signals, accept the fact
- that you limit its portability.
-
-
- What The C Standard Says
-
-
-
-
-
- 7.7 Signal handling <signal.h>
-
-
- The header <signal.h> declares a type and two functions and defines several
- macros, for handling various signals (conditions that may be reported during
- program execution).
- The type defined is
- sig_atomic_t
- which is the integral type of an object that can be accessed as an atomic
- entity, even in the presence of asynchronous interrupts.
- The macros defined are
- SIG_DFL
- SIG_ERR
- SIG_IGN
- which expand to constant expressions with distinct values that have type
- compatible with the second argument to and the return value of the signal
- function, and whose value compares unequal to the address of any declarable
- function; and the following, each of which expands to a positive integral
- constant expression that is the signal number corresponding to the specified
- condition:
- SIGABRT -- abnormal termination, such as is initiated by the abort function
- SIGFPE -- an erroneous arithmetic operation, such as zero divide or an
- operation resulting in overflow
- SIGILL -- detection of an invalid function image, such as an illegal
- instruction
- SIGINT -- receipt of an interactive attention signal
- SIGSEGV -- an invalid access to storage
- SIGTERM -- a termination request sent to the program
- An implementation need not generate any of these signals, except as a result
- of explicit calls to the raise function. Additional signals and pointers to
- undeclarable functions, with macro definitions beginning, respectively, with
- the letters SIG and an uppercase letter or with SIG_ and an uppercase
- letter,108 may also be specified by the implementation. The complete set of
- signals, their semantics, and their default handling is
- implementation-defined; all signal numbers shall be positive.
-
-
- 7.7.1 Specify signal handling
-
-
-
-
- 7.7.1.1 The signal function
-
-
-
-
- Synopsis
-
-
- #include <signal.h>
- void (*signal(int sig, void
- (*func) (int))) (int);
-
-
- Description
-
-
- The signal function chooses one of three ways in which receipt of the signal
- number sig is to be subsequently handled. If the value of func is SIG_DFL,
- default handling for that signal will occur. If the value of func is SIG_IGN,
- the signal will be ignored. Otherwise, func shall point to a function to be
- called when that signal occurs. Such a function is called a signal handler.
- When a signal occurs, if func points to a function, first the equivalent of
- signal(sig, SIG_DFL); is executed or an implementation-defined blocking of the
- signal is performed. (If the value of sig is SIGILL, whether the reset to
- SIG_DFL occurs is implementation-defined.) Next the equivalent of (*func)
- (sig); is executed. The function func may terminate by executing a return
- statement or by calling the abort, exit, or longjmp function. If func executes
- a return statement and the value of sig was SIGFPE or any other
- implementation-defined value corresponding to a computational exception, the
- behavior is undefined. Otherwise, the program will resume execution at the
- point it was interrupted.
- If the signal occurs other than as the result of calling the abort or raise
- function, the behavior is undefined if the signal handler calls any function
- in the standard library other than the signal function itself (with a first
- argument of the signal number corresponding to the signal that caused the
- invocation of the handler) or refers to any object with static storage
- duration other than by assigning a value to a static storage duration variable
- of type volatile sig_atomic_t. Furthermore, if such a call to the signal
- function results in a SIG_ERR return, the value of errno is indeterminate.109
- At program startup, the equivalent of
- signal (sig, SIG_IGN);
- may be executed for some signals selected in an implementation-defined manner;
- the equivalent of
- signal (sig, SIG_DFL);
- is executed for all other signals defined by the implementation.
- The implementation shall behave as if no library function calls the signal
- function.
-
-
- Returns
-
-
-
- If the request can be honored, the signal function returns the value of func
- for the most recent call to signal for the specified signal sig. Otherwise, a
- value of SIG_ERR is returned and a positive value is stored in errno.
- Forward references: the abort function (7.10.4.1), the exit function
- (7.10.4.3).
-
-
- 7.7.2 Send signal
-
-
-
-
- 7.7.2.1 The raise function
-
-
-
-
- Synopsis
-
-
- #include <signal .h>
- int raise(int sig);
-
-
- Description
-
-
- The raise function sends the signal sig to the executing program.
-
-
- Returns
-
-
- The raise function returns zero if successful, nonzero if unsuccessful.
- Footnotes:
- 108. See "future library directions" (7.13.5). The names of the signal numbers
- reflect the following terms (respectively): abort, floating-point exception,
- illegal instruction, interrupt, segmentation violation, and termination.
- 109. If any signal is generated by an asynchronous signal handler, the
- behavior is undefined.
-
-
- Using <signal.h>
-
-
- Signal handling is essentially nonportable. Use the functions declared in
- <signal.h> only when you must specify the handling of signals for a known set
- of operating systems. Don't try too hard to generalize the code.
- If default handling for a signal is acceptable, then by all means choose that
- option. Adding your own signal handler decreases portability and raises the
- odds that the program will mishandle the signal. If you must provide a handler
- for a signal, categorize it as follows:
- a handler for a signal that must not return, such as SIGFPE reporting an
- arithmetic exception or SIGABRT reporting a fatal error
- a handler for a signal that must return, such as SIGINT reporting an attention
- interrupt that may have interrupted a library operation
- As a rule, the second category contains asynchronous signals not intended to
- cause immediate program termination. Rarely will you find a signal that does
- not fit clearly in one of these categories.
- A signal handler that must not return ends in a call to abort, exit, or
- longjmp. Do not, of course, end a handler for SIGABRT with a call to abort.
- The handler should not reestablish itself by calling signal. Leave that to
- some other agency, if the program does not terminate. If the signal is
- asynchronous, be wary of performing any input or output. You may have
- interrupted the library part way through such an operation.
- A signal handler that must return ends in a return statement. If it is to
- reestablish itself, it should do so immediately on entry. If the signal is
- asynchronous, store a nonzero value in a volatile data object of type
- sig_atomic_t. Do nothing else that has side effects visible to the executing
- program, such as input or output and accessing other data objects.
- A sample asynchronous signal handler might look like:
- #include <signal.h>
-
- static sig_atomic_t intflag = 0;
-
- static void field_int(int sig)
- { /* handle SIGINT */
- signal (SIGINT, &field_int);
- intflag = 1;
- return;
- }
- The program calls signal(SIGINT, &field_int) to establish the handler. From
- time to time, it can then check for the occurrence of asynchronous interactive
- attention interrupts by executing code such as:
- if (intflag)
-
- { /* act on interrupt */
- intflag = 0;
- .....
- }
- Note that two small windows exist where these signals can go astray:
- Within field_int before the call to signal, an occurrence of SIGINT can
- terminate the program.
- Between the testing and clearing of intflag, an occurrence of SIGINT can be
- lost.
- Those are inherent limitations of signals.
-
-
- Standard Signals
-
-
- Here is a brief characterization of the signals defined for all
- implementations of Standard C. Note that a given implementation may well
- define more. Display the contents of <signal.h> for other defined macro names
- that begin with SIG. These should expand to (small) positive integers that
- represent additional signals.
- SIGABRT -- This signal occurs when the program is terminating unsuccessfully,
- as by an explicit call to abort, declared in <stdlib.h>. Do not ignore this
- signal. If you provide a handler, do as little as possible. End the handler
- with a return statement or a call to exit, declared in <stdlib.h>.
- SIGFPE -- The name originally meant "floating-point exception." The C Standard
- generalizes this signal to cover any arithmetic exception such as overflow,
- underflow, or zero divide. Implementations vary considerably on what
- exceptions they report, if any. Rarely does an implementation report integer
- overflow. Ignoring this signal may be rash. A handler must not return.
- SIGINT -- This is the conventional way of reporting an asynchronous
- interactive attention signal. Most systems provide some keystroke combination
- that you can type to generate such a signal. Examples are ctl-C, DEL, and
- ATTN. It offers a convenient way to terminate a tiresome loop early. But be
- aware that an asynchronous signal can catch the program part way through an
- operation that should be atomic. If the handler does not return control, the
- program may subsequently misbehave. You can safely ignore this signal.
- SIGSEGV -- The name originally meant "segmentation violation," because the
- PDP-11 managed memory as a set of segments. The C Standard generalizes this
- signal to cover any exception raised by an invalid storage access. The program
- has attempted to access storage outside any of the functions or data objects
- defined by C, as with an ill-formed function designator or lvalue. Or the
- program has attempted to store a value in a data object with a const type. In
- any event, the program cannot safely continue execution. Do not ignore this
- signal or return from its handler.
- SIGTERM -- This signal is traditionally sent from the operating system or from
- another program executing asynchronously with yours. Treat it as a polite but
- firm request to terminate execution. It is an asynchronous signal, so it may
- occur at an inopportune point in your program. You may want to defer it, using
- the techniques described above. You can ignore this signal safely, although it
- may be bad manners to do so.
-
-
- Implementing <signal.h>
-
-
- Listing 1 shows the file signal.h. The header <signal.h> I present here is
- minimal. A UNIX system, for example, defines dozens of signals. Many systems
- endeavor to look as much as possible like UNIX in this regard. They too define
- all these signals even if they do not generate many of them. Notwithstanding
- this concerted group behavior, the choice of signals and their codes both vary
- considerably. I have endeavored here to choose codes that are most widely
- used.
- As usual, I make use of the internal header <yvals.h> to provide parameters
- that can vary among systems. The code for SIGABRT is one. The highest valid
- signal code is another. Some functions in this implementation use the macro
- _NSIG to determine the lowest positive number that is not a valid signal code.
- Thus, the header <yvals.h> defines two macros of interest here. For a typical
- UNIX system, the definitions are:
- #define _SIGABRT 6
- #define _SIGMAX 32
- The header <signal.h> makes an additional concession to widespread UNIX
- practice. It defines the macros SIG_ERR and SIG_IGN in a moderately ugly way.
- The values -1 and 1 could conceivably be valid function addresses in some
- implementation. Admittedly, that is only rarely possible. Where it is
- possible, the linker can be jiggered to avoid the possibility. Still, other
- values would be more gracious. (The addresses of signal and raise, for
- example, are not likely to specify useful signal handlers.) But the values
- chosen here are the ones used widely in UNIX implementations. They are also
- widely imitated under other operating systems. I chose these for compatibility
- with existing machinery.
- That compatibility is often necessary. Almost invariably, the functions signal
- and raise must be tailored for each operating system. UNIX is the extreme
- case. In that environment, the system service signal does the whole job. If
- you have access to a C-callable function of that name, just use it. Let other
- functions call it directly. If the system service has a private name, such as
- _Signal, you can write signal as:
- /* signal function - UNIX version */
- #include <signal.h>
-
- _Sigfun *_Signal(int, _Sigfun *)
-
- _Sigfun *(signal)(int sig,
- _Sigfun *fun)
- { /* call the system service */
- return (_Signal(sig, fun));
- }
- This is an obvious candidate for a masking macro in <signal.h>.
- The function raise is only slightly more difficult. It uses the system service
- kill to send a signal to itself. ("Kill" is a misnomer stemming from its
- earliest use for sending only the signal SIGKILL.) To identify itself, raise
- also needs the system service getpid. Assuming suitable secret names for these
- two system services, such as _Kill and _Getpid, you can write raise as:
- /* raise function - UNIX version */
- #include <signal.h>
-
- int _Getpid (void);
- int _Raise(int, int);
-
- int (raise)(int sig}
- { /* raise a signal */
- return (_Kill(_Getpid(), sig));
- }
- Here is another obvious candidate for a masking macro.
- I have also written more generic versions of signal and raise, but I lack the
- space to present them here. They manage a table of pointers to signal handlers
- for an environment that does less of this work for you. Of course, you still
- have to add system-specific code to handle any hardware interrupts. As I
- pointed out earlier, there's no such thing as portable signals.
- This article is excerpted from P.J. Plauger, The Standard C Library,
- (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
-
- Listing 1
- /* signal.h standard header */
- #ifndef _SIGNAL
-
- #define _SIGNAL
- #ifndef _YVALS
- #include<yvals.h>
- #endif
- /* type definitions */
- typedef int sig_atomic_t;
- typedef void _Sigfun(int);
- /* signal codes */
- #define SIGABRT _SIGABRT
- #define SIGINT 2
- #define SIGILL 4
- #define SIGFPE 8
- #define SIGSEGV 11
- #define SIGTERM 15
- #define _NSIG _SIGMAX /* one more than last code */
- /* signal return values */
- #define SIG_DFL (_Sigfun *)0
- #define SIG_ERR (_Sigfun *)-1
- #define SIG_IGN (_Sigfun *)1
- /* declarations */
- int raise(int);
- _Sigfun *signal(int, _Sigfun *);
- #endif
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Doctor C's Pointers(R)
-
-
- Data Structures, Part 10
-
-
-
-
- Rex Jaechke
-
-
- Rex Jaeschke is an independent computer consultant, author, and seminar
- leader. He participates in both ANSI and ISO C Standards meeting and is the
- editor of The Journal of C Translation, a quarterly publication aimed at
- implementors of C language translation tools. Readers are encouraged to submit
- column topics and suggestions to Rex at 2051 Swans Neck Way, Reston, VA 22091
- or via UUCP at rex@aussie.com.
-
-
-
-
- Stacks, Continued
-
-
- Last month I talked about evaluating expressions. While a stack can be useful
- for this task, it does require that the expression be rearranged. For example,
- in programming languages we write arithmetic expressions using infix notation.
- That is, binary operators go between their operands as in (a + (b * c)).
- It would be very advantageous to be able to rewrite this expression in the
- order in which the operations are to be evaluated. You can do this by taking
- the precedence into account. This method, called postfix notation, completely
- removes the need for grouping parentheses. The previous expression written in
- postfix notation becomes a b c *+.
- Now the multiplication applies to the two operands preceding it and the
- addition applies to that result and the operand preceding that, namely a.
- Expressions written in postfix notation lend themselves directly to being
- evaluated using a stack. For example, an H-P calculator requires you to enter
- expressions in postfix notation. This is often referred also as Reverse Polish
- Notation (RPN), since it is opposite to Polish (or prefix) notation. The trick
- then is to convert an infix expression to a postfix expression. Listing 1,
- Listing 2, and Listing 3 show much of the solution.
- The main program prompts for and reads in an expression in infix notation,
- converts to the corresponding postfix notation, and prints the resulting
- string. It ignores white space in the input string. Figure 1 shows inputs and
- their results.
- The real work is done by the function intopost shown in Listing 2. The stack
- manipulation functions are shown in Listing 3.
- The conditional compilation code using TRACE is useful both in debugging and
- in demonstrating what is actually being pushed and popped along the way.
- Figure 2 shows an example.
- This program goes a long way toward handling infix expressions but it clearly
- has some limitations. First, it handles only variable names, and then only
- those one character long. In reality it would need to handle constants as well
- and longer variable names. It would also have to maintain some kind of symbol
- table for each name encountered, along with type information since it can have
- mixed-mode arithmetic involving any arithmetic types of variables and
- constants. Also, the conversion function can only handle addition,
- subtraction, multiplication, and division. It is easy to add other operators
- but you would need to handle the precedence issue in a more general way. In
- intopost, precedence is hard-wired whereas some kind of table lookup would be
- more efficient as well as elegant.
- Next month we'll look at stacks that handle different object types and stacks
- that share storage.
- Figure 1
- Enter infix: a + b
- postfix: ab+
- Enter infix: (((((a + b)))))
- postfix: ab+
- Enter infix: c - d * e + f
- postfix: cde*-f+
- Enter infix: c - d * (e + f)
- postfix: cdef+*-
- Enter infix: (c - d * e) + f
- postfix: cde*-f+
- Enter infix: c - (d * (e + f))
- postfix: cdef+*-
- Enter infix: (c - d) * e + f
- postfix: cd-e*f+
- Enter infix: (c - d) * (e + f)
- postfix: cd-ef+*
- Enter infix: (((a - b) / (c + d)) * e)
- postfix: ab-cd+/e*
-
- Figure 2
- Enter infix: c - d * (e + f)
- pushing: (
- *infix: c
- *infix:
- *infix: -
- popping: (
- pushing: (
- pushing: -
- *infix:
-
- *infix: d
- *infix:
- *infix: *
- popping: -
- pushing: -
- pushing: *
- *infix:
- *infix: (
- pushing: (
- *infix: e
- *infix:
- *infix: +
- popping: (
- pushing: (
- pushing: +
- *infix:
- *infix: f
- *infix: )
- popping: +
- popping: (
- popping: *
- popping: -
- popping: (
- postfix: cdef+*-
-
- Listing 1
- /* convert infix notation to postfix notation */
-
- #include <stdio.h>
-
- main()
- {
- char infix_str[200];
- char postfix_str[200];
- void intopost(const char *infix, char *postfix);
-
- while (1) {
- printf("Enter infix: ");
- if (gets(infix_str) == NULL)
- break;
-
- intopost(infix_str, postfix_str);
- printf(" postfix: %s\n", postfix_str);
- }
-
- return 0;
- }
-
- /* End of File */
-
-
- Listing 2
- #include <ctype.h>
-
- void push(int);
- int pop(void);
-
- /*------------------------------------------------------------------
-
-
- FUNCTION: intopost - converts infix to postfix
-
- A. Push a ( on the stack. This sentinel allows us to detect when we have
- flushed out the stack on completion in step I.
-
- --- Perform steps B through H for each character in the infix string ---
-
- B. Ignore white space.
-
- C. Pass alphabetic characters through to postfix list.
-
- D. Push any ( on the stack. These sentinels allows us to detect when we
- have flushed out the stack when handling ) and operators.
-
- E. Have a ) so pop off the stack and put into postfix list until a ( is
- popped. Discard that (.
-
- F. Have a * or /. Pop off any operators of equal or higher precedence
- and put them into postfix list. If a ( or lower precedence operator (such
- as + or -) is popped, put it back and stop looking. Push new * or /.
-
- G. Have a + or -. Pop off any operators of equal or higher precedence
- (that includes all of them) and put them into postfix list. If a ( is
- popped, put it back and stop looking. Push new + or -.
-
- H. Report unknown character on input.
-
- ------
-
- I. Have processed all input characters. Now flush stack until we find
- the matching ( put there in step A.
-
- J. Terminate the postfix list.
-
- --------------------------------------------------------------------*/
-
- void intopost(const char *infix, char *postfix)
- {
- int st;
-
- /*A*/ push (' (');
- while (*infix != '\0') {
-
- #ifdef TRACE
- printf("*infix: %c\n", *infix);
- #endif
-
- /*B*/ if (isspace(*infix)) {
- ;
- }
- /*C*/ else if (isalpha(*infix)) {
- * postfix++ = *infix;
- }
- /*D*/ else if (*infix == '(') {
- push('(');
- }
- /*E*/ else if (*infix == ')') {
- while ((st = pop()) != '(')
- * postfix++ = st;
-
- }
- /*F*/ else if (*infix == '*' *infix == '/') {
- while (1) {
- if ((st = pop()) == '(' st == '+'
- st == '-') {
- push(st);
- break;
- }
- *postfix++ = st;
- }
- push(*infix);
- }
- /*G*/ else if (*infix == '+' *infix == '-') {
- while (1) {
- if ((st = pop()) == '(') {
- push(st);
- break;
- }
- *postfix++ = st;
- }
- push(*infix);
- }
- /*H*/ else {
- printf("Unknown input character %c\n", *infix);
- }
-
- ++infix;
- continue;
- }
-
- /*I*/ while ((st = pop()) != '(')
- *postfix++ = st;
-
- /*J*/ *postfix = '\0';
-
- /* End of File */
-
-
- Listing 3
- #include <stdio.h>
-
- #define STACK_SIZE 30
-
- static int stack[STACK_SIZE]
-
- static size_t stack_ptr = 0;
-
- void push(int value)
- {
-
- #ifdef TRACE
- printf("pushing: %c\n", value);
- #endif
-
- if (stack_ptr == STACK_SIZE)
- printf("Stack is full\n");
- else
- stack[stack_ptr++] = value;
- }
-
-
- int pop(void)
- {
- if (stack_ptr == 0) {
- printf("Stack is empty\n");
- return 0;
- }
-
- #ifdef TRACE
- printf("popping: %c\n", stack[stack_ptr - 1]);
- #endif
-
- return stack[--stack_ptr];
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Using Code Generators For Creating C Code
-
-
-
-
- Ken Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language
- courses for corporations. He is the author of C Language for Programmers and
- All On C, and was a member on the ANSI C committee. He also does custom C
- programming for communications, graphics, image databases, and hypertext. His
- address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax
- questions for Ken to (919) 489-5239. Ken also receives email at kpugh@dukemvs.
- ac.duke.edu (Internet).
-
-
- Q
- I've read many of your books and articles. They are very enlightening, thank
- you. I'm posting this for a friend who is a business manager. He's been given
- the task of writing C programs, but he doesn't know C that well. He's heard
- that a code generator might help. What is it? Is it worth getting? Where can
- he get one if it is worth while? Thanks for your time in reading this note, I
- know you're a busy individual.
- Steve Kohut
- New York
- A
- Code generators ease the creation of screens and reports. They go a step
- beyond demo programs, such as Dan Bricklin's, in not just demonstrating a user
- interface, but also in turning it into C code. That may complete the input
- portion of your program, but you still have to write the process portion. For
- example, the placement and colors for name and address fields in a mailing
- list program can be created by the generator. But any custom functions (such
- as comparing the ZIP code to the state for agreement) and the disk operations
- for storing and retrieving the names must be written by you.
- There are several available from the companies that sell programming tools
- (such as the ones advertising here in The C Users Journal). The only one I
- have used for the C language are C-scape's Look and Feel from the Oakland
- Group (now Liant). It was a version from 1988, so the product may have
- additional features by now.
- You should look at the model that the screen package uses -- the numbers and
- types of functions. If you can understand the model, then modifying the source
- that is created by the package will be simple. C-scape has a fairly simple
- model. It is easy to determine from the code where a particular field is being
- input.
- On a similar note at the recent C-forum, I had the opportunity to see a demo
- of Liana, by Base Technology (1543 Pine Street, Boulder, Colorado 80302; (303)
- 440-4558). This is a C-like language interpreter with an object-oriented
- approach to MS Windows. You can not only create windows, but perform many
- standard operations (such as text editing, printing, DDE). It is not a code
- generator in the standard sense of the term. But it is such an easy-to-use
- interface to Windows that it yields the same benefits for the C programmer
- trying to create a Window program.
- By the way, there are other C code generators that are used for special
- purposes. If you are doing any work with language processing, I heartily
- recommend lex and yacc. If any reader knows of other generators for any
- purpose, please let me know and I'll pass them along.
- Q
- I've been working in C just a short while and only now have had to use
- pointers. The following mini program (Listing 1) is a slight adaptation of an
- example program copied from C Primer Plus by Mitchell Waite et al. It yields
- different results depending on the memory model that is used during the
- compilation, as shown in Figure 1 and Figure 2. Perhaps it's only a printf bug
- (or feature) but it's got me puzzled.
- As I understand it, the three models that don't work use either far or huge
- memory pointers, whereas the three that do work use near pointers. With that
- in mind I attempted to specify the use of near pointers in either or both of
- the int declaration lines (i.e. int near *ptr) while the compiler was
- defaulting to one of the three larger models. The results were the same, that
- is, it worked incorrectly.
- Is this a documented problem or am I doing something stupid or am I misusing
- the Turbo C compiler or what? Any help would be appreciated.
- Tom Frank
- Austin, Texas
- A
- With the %u format specifier, you are asking printf to print an unsigned
- integer. The size of a large memory model pointer (or near pointer) is two
- bytes, or the size of an integer. When you compiled it with the small model,
- the size of the pointers being passed to printf agreed with that designated by
- the format specifier.
- The size of a large memory model pointer (or far pointer) is four bytes, or
- the size of a long integer. When you used the large memory model, the size of
- the pointers was twice that specified by the format. Each half of the pointer
- values that were passed was treated as an integer by printf.
- On a PC, the bytes of an integer or pointer are in reverse order. So the
- values that were printed out represented the two least significant bytes
- followed by the two most significant bytes. The value of ptr was really equal
- to
- 6207 * 65536 + 4056
- You can see the same phenomena if you print longs on a PC using the integer
- format, as shown in Listing 2.
- You can avoid the memory model problem by using the new ANSI format specifier
- %p. This prints out the value of the pointer using the size based on the
- memory model specified at compile time.
- Q
- What books would you recommend as the best for intermediate to advanced
- programming of 2-D and 3-D animation and saving and loading PCX and GIF
- picture files in Microsoft C v6? If none of the books covers v6 is there
- anything for QuickC v2?
- Could you also recommend a book on BIOS, DOS, and hardware addresses for
- programmers?
- Would I be better off using C++ for graphics programming?
- Bruce Balstad
- Hoffman Estates, IL
- A
- I have to admit I have simplified my life when it comes to graphics. I wrote
- my own PCX loader originally using the PCX file documentation from Z-Soft.
- Later I bought Essential Graphics (from Essential Software, 76 So. Orange, So.
- Orange NJ 07079 (201) 762-6965). It handles most all the options in PCX files.
- To handle all other graphics, I simply convert them to PCX using Hijaak.
- Essential Graphics has lots of drawing functions. I am not wild about the
- names they have used, but they work. They are a little slow for animation.
- In regards to DOS, pick up a copy of MS-DOS Programmer's Reference book. It's
- complete (except for a few undocumented calls). For the BIOS, the IBM
- Technical Reference manual is available (at a bit of a price). The earlier
- versions included the assembly source code listings. I haven't bought one
- recently.
- Perhaps our reader's have some suggestions for graphic books.
- Q
- I am currently working on a business application that involves a large amount
- of reading and writing to inventory style data files but due to the fact that
- most of my knowledge comes from books and not experience, I am running into a
- problem that is setting me behind schedule. The problem is that I just can't
- seem to find the correct codes that will allow the user to delete an item from
- the inventory file. I have tried writing the hex value of the delete key to
- the selected bytes of the file, but instead of deleting the chosen
- character(s) the program displays the ASCII representative of the delete key
- to the selected bytes.
- Is there any simple solution to this problem that will allow me to accomplish
- this task or do you know of any books that would help me to find out what I am
- doing wrong?
- Your help would be greatly appreciated. I hope to hear from you soon.
- Michael Messuri
- Moline, IL
- A
- This is a tough one, since you didn't specify the database that you are using.
- On many database systems, a record is marked as deleted if the first byte is a
- special character (e.g. OXFF or OX7F). When the data file is read sequentially
- (without an index), any records with this byte are usually ignored by the
- database. If the data file is read using an index, then when the record is
- accessed, the database may return the record as a valid one. It would be up to
- your code to check for deleted records.
- If the data file is re-indexed after a deletion is made, then the deleted
- record will not be indexed. If the data file is compacted, then the record
- will be physically deleted.
-
- Q
- I have a program problem that is driving me crazy. I figure the answer should
- be quite easy for a C programming veteran like you. In a BASIC program (please
- don't give up on me yet) I use the following routines (Listing 3) to save or
- load the Hercules graphics screen to/from disk.
- I would like to do the same thing in C. My routine (Listing 4) does save a
- 32767 byte file however, it is not the Hercules screen. What am I doing wrong?
- I feel very stupid asking you about this. This one has me bewildered. Your
- help is appreciated. Thanks.
- James A. Gant
- Oklahoma City, OK
- A
- Don't worry about feeling stupid. That happens to me sometimes when I stare at
- a block of code for a few hours and realize that "for lack of a semicolon, a
- program was lost."
- Your code appears on the surface to be fine. You must compile it with the
- large memory model. If you do not, then the library functions will use the
- small memory model. And the header files that will be included are those for
- the small model.
- If the compiler you are using has prototypes, then the following will occur.
- Inside <stdio.h> is the following statement. (To simplify, I've substituted
- int for size_t).
- int fwrite(void *address, int size,
- int count, FILE *file_pointer);
- Inside your program, as you have written it is the statement
- fwrite(hga_buff, 1, 32767, fp);
- Since a prototype is in scope, and the parameters are assignment compatible,
- then the parameters will undergo a silent conversion. The only one that does
- not match is the first one. So it will be as if you had written
- fwrite ( (void near *) hga_buffer, 1, 32767, fp);
- Note that the void * address of the prototype will be interpreted as a near
- pointer, since the file is compiled in the small model. The data that is
- written out would start at location 0 (the lower order two bytes of the
- address).
- Now you should get a warning message about "suspicious pointer conversion"
- since you are converting a far pointer to a near pointer. But the warning is
- not required by ANSI and if you have warnings turned off, you won't see it.
- If the compiler does not have prototypes, the conversion will not take place,
- but the writing to the file should have failed.
-
-
- main () Placement
-
-
- I'm really behind on my reading, so I'll assume your incomplete answer in the
- July issue of The C Users Journal, pages 106 and 107, has already been pointed
- out. But just in case... The question was, why are functions normally
- main ()
- {}
- func1 ()
- {}
- func2 ()
- {}
- func3 ()
- {}
- but sometimes
- func1 ()
- {}
- func2 ()
- {}
- func3 ()
- {}
- main ()
- {}
- In days of old, before prototyping in ANSI C made function declaration common,
- there was a problem with pointers to functions. The compiler had to know of
- the function before it would allow a pointer to it. This could be handled by
- main()
- {
- int func1 (), func2();
- float func3() ;
- . . .
- Code
- . . .
-
- }
- But it is more often handled, such as in CU, by coding the functions to be
- pointed to by function pointers before the code that will set pointers to said
- functions.
- There also was the quirky thing in many compilers that any variables listed
- outside a function were considered global from that point on down. So one
- could have functions that were not supposed to be able to access global
- variables that other functions in this file were enabled to do so. (Other
- files had access to all non-static globals in this file.)
- I'm assuming that Lyle O. Haga was looking at old code. I haven't seen these
- techniques in common use since C/UNIX stopped being mostly a PDP/11
- phenomenon.
- Doug Parsons
- Boston, MA
- Thanks for your comments. Actually that wasn't a quirky thing in the compiler.
- In K&R, page 206, the rule for external identifiers was "from the definition
- to the end of the source file in which they appear." That simplified the
- compiler since any variable name that was found in a function had to have been
- declared prior to its use.
- The ANSI rules state that the external definition has file scope. This means
- that it can be placed anywhere in the source file.
-
- In reality, most people place all external definitions at the head of the
- source file. Relying on the scoping rules of K&R as you described makes the
- maintenance a bit of a nightmare. (KP)
-
-
- DLLs for DOS
-
-
- In the September 1991 issue of CUJ, David Qualls asks about support for
- "dynamic linking" in DOS. Although it currently supports only UNIX, and not
- DOS, he might want to take a look at dld by W. Wilson Ho. dld is (quoting its
- documentation) "a library package of C functions that performs dynamic link
- editing."
- Programs that use dld can add compiled object code to or remove such code from
- a process anytime during its execution. Loading modules, searching libraries,
- resolving external references, and allocating storage for global and static
- data structures are all performed at runtime. dld is now available for VAX,
- Sun3, SPARCstation, Sequent Symmetry, and Atari ST. dld is distributed under
- the terms of the GNU copyright agreement (and can be obtained from most GNU
- archive sites), so source code is freely available.
- It might be possible to implement the code under DOS, but doing so would
- doubtless require a thorough understanding of the structure of DOS .OBJ, .EXE,
- and .LIB files. Perhaps some keen programmer somewhere would like to
- volunteer?
- Eric Zurcher
- Canberra Australia
- Any volunteers? (KP)
-
-
- Memory Resident Utilities
-
-
- In response to Moses Mwarari Maina's question in the September, 1991 issue, I
- know of two different sources for writing TSRs. The first is Turbo C
- Memory-Resident Utilities, Screen I/O and Programming Techniques, by Al
- Stevens from MIS Press. It has two chapters with about 80 pages on the
- subject. The second is the TesSeRact TM Ram-Resident Development System, from
- Innovative Data Concepts, Inc., 122 North York Road, Suite 5, Hatboro, PA
- 19040. Their order phone number is 1-800-926-4551. I have only played with
- these and have not had time to use them myself.
- Geoffrey Probert
- Broomfield, CO
- This is in response to the letter from Moses Mwarari Maina of Nairobi, Kenya
- which was published in the September 1991 issue of CUJ. Mr. Maina might
- benefit from the book Turbo C: Memory-Resident Utilities, Screen I/O, and
- Programming Techniques, written by Al Stevens and published by MIS Press in
- 1987. MIS was located here in Portland; I just tracked them down. They are now
- a division of Henry Holt Company, 4375 West 1980 South, Salt Lake City, UT
- 84104. Their WATS number is 800-408-5233. They still publish this book.
- Beginning with Chapter 4, chapter titles are: General- purpose Functions,
- Screen Windows, The Windows Function Library, Context-sensitive Help Windows,
- Data Entry Windows, the Windows Text Editor (aside: Al also did a series, in
- DDJ I believe, on TWP: Tiny Word Processor), Windows Menus, Memory-resident
- Programs, and Building Turbo C Memory-resident Programs.
- Another source I can recommend from personal experience is IDC (Innovative
- Data Concepts) at 122 North York Road, Suite 5, Hatboro, PA 19040. They
- developed the shareware TesSeRact TSR library and purchased Mike Smedley's
- superb CXL windowing/menu/data entry/general purpose C function shareware
- library (it's now TCXL version 5.52). I hope this helps Mr. Maina. Kudos to
- you and the rest of the CUJ team for a valuable publication
- Richard B. Shepard
- Thanks to you both for your sharing of the information. (KP)
- Figure 1 Output if Compiled as Compact, Large or Huge
- &ptr = 4062, ptr = 6207, *ptr = 4056
- *ptr = 100, &ptr = 4062, ptr = 6207
- ptr = 4056, *ptr = 6207, &ptr = 100
-
- urn[0] = 100, &urn[0] = 4056
- &urn[0] = 4056, urn[0] = 6207
-
- Figure 2 Output if Compiled as Tiny, Small or Medium
- &ptr = 65494, ptr = 65488, *ptr = 100
- *ptr = 100, &ptr = 65494, ptr = 65488
- ptr = 65488, *ptr = 100, &ptr = 65494
-
- urn[0] = 100, &urn[0] = 65488
- &urn[0] = 65488, urn[0] = 100
-
- Listing 1
- main()
- {
- int urn[3] = {100,200,300};
- int *ptr;
-
- ptr = urn;
- printf("&ptr = %u, ptr = %u, *ptr = %u\n", &ptr, ptr, *ptr);
- printf("*ptr = %u, &ptr = %u, ptr = %u\n", *ptr, &ptr, ptr);
- printf("ptr = %u, *ptr = %u, &ptr = %u\n\n", ptr, *ptr, &ptr);
-
- printf("urn[0] = %u, &urn[0] = %u\n", urn[0], &urn[0]);
- printf ("&urn[0] = %u, urn[0] = %u\n\n", &urn[0], urn[0]);
- getch();
- }
-
- /* End of File */
-
-
-
- Listing 2
- unsigned long la, lb, lc;
- printf("Longs are %lu %lu %lu", la, lb, lc);
- printf("Bad way to print longs %u %u %u", la, lb, lc);
-
- /* End of File */
-
-
- Listing 3
- PLOAD:
- DEF SEG = &HB000
- BLOAD N$,0
- DEF SEG
- RETURN
- PSAVE:
- DEF SEG = &HB000
- BSAVE N$,0,32767
- DEF SEG
- RETURN
-
-
- Listing 4
- #include <stdlib.h>
- #include <stdio.h>
- main()
- {
- FILE *fp;
- char far *hga_buff;
- unsigned bytes_written;
- hga_buff = (char far *) 0xB0000000L;
- if ( fp = fopen("screen.pic", "wb") == NULL )
- {
- puts("Error opening file."};
- exit(1);
- }
- bytes_written = fwrite(hga_buff,1,32767,fp);
- printf("%u bytes written to file.\n",bytes_written);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stepping Up To C++
-
-
- Operator Overloading
-
-
-
-
- Part 2
-
-
-
-
- Dan Saks
-
-
- Dan Saks is the owner of Saks & Associates, which offers consulting and
- training in C and C++. He is secretary of the ANSI C++ standards committee,
- and also contributing editor for Windows/DOS Developer's Journal. Dan recently
- finished his first books, C++ Programming Guidelines, written with Thomas
- Plum. You can write to him at 393 Leander Dr., Springfield, OH 45504, or
- dsaks@wittenberg. edu (Internet), or call (513)324-3601.
-
-
- In my last column, I introduced operator overloading in C++ (see "Operator
- Overloading, Part 1," January 1992). Using operator overloading, you can
- define new meanings for operator symbols applied to objects of user-defined
- class types. Operator overloading enables the implementation of user-defined
- types that are nearly indistinguishable from built-in types.
- Last time, I used a simple class for rational numbers (fractions) to
- demonstrate overloading of binary operators. In this article, I'll enhance the
- rational class to make it more like a built-in type by adding mixed-mode
- operations (operations combining rationals with other built-in types) and
- unary operators.
-
-
- Reviewing The Basics
-
-
- Listing 1 shows the declaration for class rational as it was when I left off
- in the first part of this series. Each rational number stores its numerator
- and denominator in a pair of signed long integers. The class declares eight
- overloaded binary operators: +, -, *, and /, and their corresponding
- assignment operators +=, -=, *=, and /=. Using this class you can declare
- objects of type rational, and perform rational arithmetic using the overloaded
- operators. For example, if r1, r2, and r3 are rationals, then
- r1 += r2;
- adds r2 to r1 using rational ::operator +=. The statement compiles as if you
- had written
- r1.operator+=(r2);
- Similarly,
- r1 = r2 + r3;
- adds r2 and r3 using rational::operator+ and then copies the result to r1
- using rational::operator=. This statement compiles as if you had written
- r1.operator=(r2.operator+(r3));
- Note that Listing 1 doesn't provide a declaration for rational::operator=. The
- compiler generates a default version of operator= if you don't write one
- explicitly. The default version uses memberwise assignment. That is, it copies
- each member of the right-hand operand to the corresponding member of the
- left-hand operand using that member's operator=.
- Listing 2 presents definitions for the rational member functions. I
- implemented each arithmetic operator (e.g. +) in terms of its corresponding
- assignment operator (e.g. +=). Note that the first line in the body of each
- arithmetic operator is the declaration
- rational result(*this);
- which uses the rational copy constructor
- rational::
- rational (const rational &);
- to initialize the local variable result with a copy of *this. (Recall that
- this is the address of the object for which the member function was called.) I
- did not declare the copy constructor explicitly; I let the compiler generate
- it.
- The member function rational::put (FILE *) prints a rational number in the
- format (num/denom.) rational::simplify is a private member function that
- reduces a rational number to its simplest form. (My method for reducing
- fractions is admittedly too simple for an industrial-strength class, but the
- fine points of fractional arithmetic are secondary to my presentation of
- overloaded operators. Zeidler[1] presents a better algorithm for reducing
- fractions.)
- simplify uses a general-purpose library function gcd that computes the
- greatest common divisor of its arguments. I presented a version of gcd in the
- first part of this series, but since then I've discovered problems with the
- implementation. Listing 3 contains a new version of gcd based on an algorithm
- also provided by Zeidler[1]. I compiled gcd separately and placed the object
- code in a linkable library called mylib. simplify accesses gcd's declaration
- by including mylib.h.
-
-
- Constructors As Conversions
-
-
- C++, like C, provides standard conversions among the arithmetic types. These
- rules simplify writing arithmetic expressions. For example, when you mix ints
- and longs in the same expression, the compiler automatically promotes the ints
- to longs, and does all the computations in long arithmetic. Similarly, you can
- initialize a long with an int expression, such as
- long n = 0;
- The compiler automatically converts the integer literal 0 to a long value. You
- need not write
- long n = 0L;
- The compiler also performs this conversion when you pass an int to a function
- that expects a long argument.
- Unfortunately, the rational class defined in Listing 1 does not provide
- automatic conversions to or from other arithmetic types. For example, you
- cannot write an expression like
- rational r1, r2;
- ...
- r1 = r2 + 1;
-
- You must write it as
- r1 = r2 + rational(1, 1);
- The problem is that rational::operator+ requires two rational operands, and
- the literal 1 is an int, not a rational.
- You could solve this problem by extending the class to include a member
- function rational::operator+(long n), as shown in Listing 4. The return
- statement in that function uses the constructor call rational(n, 1) to convert
- long n to its equivalent rational value, n/1. Adding this member function to
- the class lets you write expressions like
- r1 = r2+ 1L;
- It even lets you write
- r1 = r1 + 1;
- because the compiler promotes 1 to long before calling
- rational::operator+(long).
- The problem with this approach is that you must also add
- rational operator+=(long);
- rational operator-(long);
- rational operator-=(long);
- and so on, for every operator defined by the class. A class written this way
- gets very big very fast, and contains lots of duplicated code.
- If you also add operator=(long) to the class, you can write
- rational r;
- ...
- r = 3;
- which assigns 3/1 to r. Unfortunately, even with this additional operator, you
- still can't write declarations like
- rational r = 3;
- Remember, this is not an assignment; it's an initialization that uses a
- constructor called with one argument, as if you had written either
- rational r = rational(3);
- or
- rational r (3);
- To overcome this problem, you must add a rational constructor that accepts a
- single argument of type long, defined as
- rational (long n):
- num(n), denom(1) {}
- It turns out that this one constructor eliminates the need for all those extra
- operator functions that accept a single long argument. A class constructor
- with one argument acts as a rule for converting the argument type to the class
- type. For example, adding the above constructor to the class definition in
- Listing 1 lets you write
- rational r;
- ...
- r = 3;
- In translating the assignment, the compiler finds only one assignment operator
- that it could possibly use, namely
- rational &rational::
- operator=(const rational &);
- as generated by the compiler. The compiler promotes 3 to long and passes the
- result to the rational(long) constructor to create a rational object for use
- as the right-hand operand in the assignment. If the constructor is written as
- an inline function, a good optimizing compiler will "compile away" any
- temporary objects, and produce code equivalent to
- r.num = 3;
- r.denom = 1;
- The compiler applies the rational(long) constructor whenever it needs to
- covert the right-hand operand of a rational operator. An expression like
- r /= 2;
- compiles as if you had written
- r.operator/=(rational(2));
- and converts the 2 to the rational value 2/1 before passing it to operator/=.
- Similarly,
- r1 = r2 * 2;
- compiles as if you had written
- t1 = r2.operator+ (rational (2));
- rl.operator = (t1);
- where t1 is a temporary rational object added for readability.
- The implicit conversion from integral types to rational lets you write code
- that looks even more as if rationals were built in. However, it also lets you
- inadvertently write less efficient code. For example, you can rewrite
- r1 = r2 * rational(2,3) + r3;
- as
- r1 = r2 * 2/3 + r3;
- Despite the spacing, C++'s precedence rules interpret the latter expression as
- r1 = ((r2 * 2) / 3) + r3;
- which compiles as
- t1 = r2.operator*(rational(2));
- t2 = t1.operator/(rational(3));
- r1 = t2.operator+(r3);
- (Again, t1 and t2 are temporary rational objects added only for readability.)
- Whereas the expression rational(2,3) explicitly creates a single object whose
- value is 2/3, the expression 2/3 gets regrouped into two separate rational
- objects and introduces a separate call to rational::operator/.
- Note that you cannot create a single rational object by simply adding
- parentheses
- r1 = r2 * (2/3) + r3;
-
- In this case, the compiler sees that 2 and 3 are integer literals, so it
- performs 2/3 using integer division. The result is an integer-valued zero,
- which is converted to rational when used as the right-hand operand of
- rational::operator*.
- The compiler doesn't know that you intended 2/3 to be a rational constant. It
- doesn't promote the operand types if they are already the same type. This
- situation is no different than if r1, r2, and r3 had been float or double
- variables. 2/3 grouped as such always yields zero.
-
-
- Non-Member Operator Functions
-
-
- When you overload operators using members functions, the compiler can apply
- user-defined conversions to the right-hand argument, but not to the left. It
- follows that the left operand must be a class object. Thus you can write
- expressions like
- r1 = r2 * 2;
- but not like
- r1 = 2 * r2;
- because 2 is not a class object. The compiler will not take the liberty of
- commuting the operands to produce
- r1 = r2 * 2;
- because some operations, like -- and /, are not commutative.
- If you want the compiler to apply user-defined conversion to the left operand
- as well as the right operand of an overloaded operator, then implement that
- operator as a non-member function. For example, you move the member function
- rational rational::
- operator+(rational r);
- outside the class declaration, and rewrite its declaration as the non-member
- function
- rational operator+
- (rational r1, rational r2);
- Notice that although the member function appears to have only one formal
- argument, the non-member function has two. But a member function really has an
- extra (hidden) argument -- the address of the object addressed by this. When
- you rewrite the operator as a non-member function, you must add a second (or
- is it a first?) explicit argument.
- Changing this operator from a member to a non-member function doesn't affect
- any of the calls to operator+ using infix notation. The fact is, when the
- compiler sees r1 + r2, where either r1 or r2 is class type, it tries compiling
- the expression as either r1.operator+(r2) or operator+(r1, r2), and uses
- whichever one it finds.
- If you define both forms of operator+ accepting identical argument types, then
- when you write r1 + r2, the compiler produces a diagnostic that says the
- expression is an ambiguous reference. If you define both forms of operator+
- accepting sufficiently different argument types, the compiler selects the best
- match according to an elaborate set of argument matching rules. Ellis and
- Stroustrup[2] and Stroustrup[3] describe these rules in detail. I will explain
- them in a future column.
- Listing 5 shows a new version of the header rational.h that declares class
- rational with the arithmetic operators +, -, *, and / implemented as
- non-member functions. The body of each function is a one-liner that uses the
- corresponding assignment operator to do the computation. For example, the body
- of operator+ is simply
- return r1 += r2;
- The operator function doesn't need a local rational object to hold the result;
- it uses its local copy of the left-hand operand for temporary storage. Since
- the left operand of operator+ is passed by value, changing the value of the
- formal parameter has no effect on the actual argument passed to operator+. The
- arithmetic operators are so short, it's appropriate to declare them as inline
- functions in the header.
- When the operators were member functions, they had access to the private
- members of rational objects. When you rewrite them as non-member functions
- they lose their access rights, but the loss poses no problem because these
- functions never exercised those rights. For example, all the real work of
- operator+ is done by calling rational::operator+=, which still has all its
- private access rights. operator+= manipulates the private num and denom
- members of the rational objects, and returns a reference to a complete
- rational object.
-
-
- Using const Reference Arguments
-
-
- The size of a rational object is the sum of the sizes of its data members,
- namely, 2 * sizeof(long). This is rarely smaller, and often two or four times
- the size of a pointer. Therefore, you may want to pass the arguments to these
- operators by const reference rather than by value. (The underlying
- implementation of a reference is a pointer, so passing by reference is often
- cheaper than passing a large object by value.) You can certainly do this with
- the member functions, e.g.
- rational &operator+=(const rational &);
- without changing anything else in the function definition or in any function
- call. You can also do it with the second argument of the non-member functions,
- e.g.,
- inline rational operator+
- (rational r1, const rational &r2);
- However, as implemented in Listing 5, you can't change the first argument, r1,
- to a const reference because the function alters r1's value. If you insist on
- changing r1 to a const reference, then you must use a local rational variable
- to hold the result, as shown in Listing 6, but I doubt you gain anything by
- writing the function this way.
-
-
- Unary Operators
-
-
- Built-in arithmetic types support a number of unary operators. Rationals
- should too. Listing 7 shows the class declaration in rational.h extended to
- include six unary operators: +, -, prefix ++ and -- --, and postfix ++ and --
- --.
- In general, you overload unary operators using either a member function with
- no arguments or a non-member function with one argument. (The exceptions are
- postfix ++ and -- --, explained later.) For example, as a member function, you
- declare rational unary + as
- rational rational::operator+();
- As a non-member function, declare it as either
- rational operator+(rational);
- or
- rational operator+(const rational &);
- In Listing 7, I used the member function notation.
- The implementation of unary + is trivial, so I simply defined it inside the
- class definition. Hence, the function is inline by default. Unary + simply
- returns the value of its operand.
- I try to make overloaded operators for user-defined types act as much as
- possible as they do for predefined types. Unary + applied to predefined types
- returns an rvalue, not an lvalue. The return value of a function is an rvalue
- unless it returns a reference. Therefore, I made the return value of
- rational's unary + a rational, rather than a rational &.
- The implementation of unary - is a little more difficult. The function
- definition appears in Listing 8 along with the other non-inline unary operator
- functions. The return value is simply the value of the operand with its
- numerator negated. However, you must be careful to leave the operand
- unchanged. If the body of the function were simply
- num = -num;
- return *this;
- it would negate the operand itself, and you would get surprising behavior. For
- example, if negation changed its operand, then the second statement in
- r1 = rational (1, 1); // r1 = 1/1
- r2 = -r1; // r2 = -1/1
-
- would also change r1 to -1/1. To avoid this odd behavior, rational unary -
- computes the negation in a local variable.
- Early versions of C++ (those compatible with AT&T cfront up to release 2.0)
- did not distinguish between prefix and postfix applications of overloaded ++
- and -- --. That is, for a class like rational you could write a single
- function operator++ (either as a member or non-member) and invoke that
- function as either ++r or r++. There was no way to get ++r and r++ to behave
- differently as they do for primitive types.
- Newer C++ compilers let you overload the prefix and postfix operators
- separately. You declare the prefix operator++ much as you do any other unary
- operator using a member function declared as
- rational rational::operator++();
- or a non-member function declared as
- rational operator++(rational);
- You declare the postfix operator++ with an additional argument of type int, as
- a member function
- rational rational::operator++(int);
- or as a non-member function
- rational operator++(rational, int);
- For a rational r, the expression ++r compiles as the call
- r.operator++()
- and the expression r++ compiles as
- r.operator++(0)
- You can use an explicit function call to the postfix operator to pass a
- non-zero value as the int argument, but I haven't yet seen a reason to do
- this. In practice, the int argument is a dummy argument that distinguishes the
- postfix from the prefix operator.
- The implementation of the prefix operators are very simple. For predefined
- types, ++r is defined to be r+=1, and that's exactly what the overloaded
- prefix rational ++ does. The postfix rational++ must do a little more work to
- set aside a copy of the prior value of the operand while it increments the
- operand.
- For predefined types, the operand of prefix and postfix ++ and -- -- must be a
- modifiable lvalue, but the return type is not an lvalue. This means, for
- example, that ++2 and K++ (where K is a const variable) are invalid. The
- overloaded rational operators should try to preserve this behavior.
- Unfortunately, as a member function, operator++ and operator -- -- will accept
- operands other than modifiable lvalues. For example, the member function
- accepts a call like ++rational(1, 2). However, you should be able to prevent
- such expressions by defining operator++ as the non-member function
- inline
- rational operator++(rational &r)
- {
- return r += 1;
- }
- That is, the rational argument is passed as a non-const reference. Since a
- rational & argument can only be bound to a modifiable rational object (an
- lvalue), this function definition should not accept a call like ++rational(1,
- 2). Therefore, I recommend overloading ++ and -- -- this way.
- I say that this should be able to prevent non-modifiable lvalue operands
- because that is my understanding of the rules for references defined by the
- Annotated C++ Reference Manual (the ARM)[2], and by the C++ draft standard.
- However, as I explained in an earlier column on references (see "Reference
- Types", CUJ, September 1991) some current compilers still bind non-const
- references to a constant expression by binding the reference to a temporary
- initialized by the value of the expression. Since I haven't had the
- opportunity to use a compiler that agrees with my interpretation, there's room
- for some debate about my recommendation.
- References
- [1] Zeidler, Steve, "Doing Fractions in C++", The C Users Journal, Vol. 9, No.
- 11, Nov. 1991.
- [2] Ellis, Margaret A. and Bjarne Stroustrup, The Annotated C++ Reference
- Manual. Addison-Wesley, Reading, MA, 1990.
- [3] Stroustrup, Bjarne, The C++ Programming Language, 2nd. ed. Addison-Wesley,
- Reading, MA, 1991.
-
- Listing 1 (rational.h)
- #include <stdio.h>
-
- class rational
- {
- public:
- rational (){ }
- rational(long n, long d) : num(n), denom(d) { }
- rational operator+(rational);
- rational operator-(rational);
- rational operator*(rational);
- rational operator/(rational);
- rational &operator+=(rational);
- rational &operator-=(rational);
- rational &operator*=(rational);
- rational &operator/=(rational);
- void put(FILE *);
- private:
- long num, denom;
- void simplify();
- };
-
- /* End of File */
-
-
- Listing 2 (rational.cpp)
- #include "mylib.h"
- #include "rat1.h"
-
- rational &rational::operator+=(rational r)
-
- {
- num = num * r.denom + r.num * denom;
- denom *= r.denom;
- simplify();
- return *this;
- }
-
- rational &rational::operator-=(rational r)
- {
- num = num * r.denom - r.num * denom;
- denom *= r.denom;
- simplify();
- return *this;
- }
-
- rational &rational::operator*=(rational r)
- {
- num *= r.num;
- denom *= r.denom;
- simplify();
- return *this;
- }
-
- rational &rational::operator/=(rational r)
- {
- num *= r.denom;
- denom *= r.num;
- simplify();
- return *this;
- }
-
- rational rational::operator+(rational r)
- {
- rational result(*this);
- return result += r;
- }
-
- rational rational::operator-(rational r)
- {
- rational result(*this);
- return result -= r;
- }
-
- rational rational::operator*(rational r)
- {
- rational result(*this);
- return result *= r;
- }
-
- rational rational::operator/(rational r)
- {
- rational result(*this);
- return result /= r;
- }
-
- void rational::put(FILE *f)
- {
- fprintf(f,"(%ld/%ld)", num, denom);
- }
-
-
- void rational::simplify()
- {
- long x = gcd(num, denom);
- num /= x;
- denom /= x;
- }
-
- // End of File
-
-
- Listing 3 (gcd.cpp)
- #include <stdlib.h>
- #include "mylib.h"
-
- long gcd(long x, long y)
- {
- ldiv_t r;
- x= labs(x);
- if ((y = labs (y)) == 0)
- return x;
- do
- {
- r = ldiv(x, y);
- x = y;
- }
- while ((y = r.rem) !=0);
- return x;
- }
-
- // End of File
-
-
- Listing 4
- rational::operator+(long n)
- {
- rational result(*this);
- return result += rational(n, 1);
- }
-
- // End of File
-
-
- Listing 5 (rational.h)
- #include <stdio.h>
-
- class rational
- {
- public:
- rational() { }
- rational(long n) : num(n), denom(1) { }
- rational(long n, long d) : num(n), denom(d) { }
- rational &operator+=(rational);
- rational &operator-=(rational);
- rational &operator*=(rational);
- rational &operator/=(rational);
- void put(FILE *);
- private:
- long num, denom;
-
- void simplify();
- };
-
- inline rational
- operator+(rational r1, rational r2)
- {
- return r1 += r2;
- }
-
- inline rational
- operator-(rational r1, rational r2)
- {
- return r1 -= r2;
- }
-
- inline rational
- operator*(rational r1, rational r2)
- {
- return r1 *= r2;
- }
-
- inline rational
- operator/(rational r1, rational r2)
- {
- return r1 /= r2;
- }
-
- /* End of File */
-
-
- Listing 6
- inline rational
- operator+(const rational &r1, const rational &r2)
- {
- rational result(r1);
- return result += r2;
- }
-
- // End of File
-
-
- Listing 7 (rational.h)
- #include <stdio.h>
-
- class rational
- {
- public:
- rational() { }
- rational(long n) : num(n), denom(1) { }
- rational(long n, long d) : num(n), denom(d) { }
- rational &operator+=(rational r);
- rational &operator-=(rational r);
- rational &operator*=(rational r);
- rational &operator/=(rational r);
- rational operator+() { return *this; }
- rational operator-();
- rational operator++() { return *this += 1; }
- rational operator--() { return *this -= 1; }
- rational operator++(int); // postfix ++
-
- rational operator--(int); // postfix --
- void put(FILE *);
- private:
- long num, denom;
- void simplify();
- };
-
- // ... the rest of rational.h as in Listing 5
-
- /* End of File */
-
-
- Listing 8 (rational.cpp)
- #include "mylib.h"
- #include "rational.h"
-
- rational rational::operator-()
- {
- rational result(*this);
- result.num = -result.num;
- return result;
- }
-
- rational rational::operator++(int)
- {
- rational result(*this);
- *this += 1;
- return result;
- }
-
- rational rational::operator--(int)
- {
- rational result(*this);
- *this -= 1;
- return result;
- }
-
- // ... the rest of rational.cpp as in Listing 2
-
- // End of File
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On the Networks
-
-
- Is Everything Miscellaneous?
-
-
-
-
- Sydney S. Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, author, and
- president of Datacomp Systems, Inc., a consulting and contract programming
- firm specializing in databases, data presentation and windowing, transaction
- processing, networking, testing and test suites, and device management for
- UNIX and MS-DOS. He can be contacted care of Datacomp Systems, Inc., 3837
- Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail on the
- Internet/Usenet mailbox syd@DSI.COM (dsinc!syd for those who cannot do
- Internet addressing).
-
-
- It appears that everyone has decided that they want the early appearance of
- their postings in comp.sources.misc over the testing of them in the other
- groups. During the past two months I counted the postings I saved, so I could
- mention them in this column. There were 0.5MB in comp.sources.games, 1.2MB in
- comp.sources.unix, 1.3MB in comp.sources.reviewed, and a whopping 16MB in
- comp.sources.misc. Now, this is only what I saved, which for all of the above
- except the misc group, is everything. In misc, I do not keep anything that is
- not written in C, that is totally specific to a single machine, or is
- shareware that requires a registration fee.
- There is plenty to talk about, but it all falls into the misc group it seems.
-
-
- Rich?, Part N
-
-
- Only one posting showed up in comp.sources.unix this time. Chris Lewis
- <psroff-request@ferret.ocunix.on.ca> posted a major package called psroff in
- comp.sources.unix this time. It will be useful for those UNIX systems stuck
- with the older C/A/T based troff. The program troff accepts text input and
- outputs phototypesetter commands. Originally, it was designed to drive a
- particular typesetter in use at Bell Laboratories. Bell used this C/A/T
- typesetter to set most of its documents in the late 1970s and early 1980s.
- Now, with laser printers based on Postscript or HP/PCL, output from troff
- could potentially become unusable. However, psroff can convert the original
- C/A/T troff output into Postscript or HP/PCL for use in Postscript printers or
- the HP LaserJet/DeskJet family of printers. It upgrades the old troff, with
- few limitations, to be equivalent to the more modern device independent troff
- (ditroff). Not only does it handle the old troff, but it can also handle
- converting ditroff output into Postscript or HP LaserJet/DeskJet output.
- psroff v3.0, , was posted on September 26, 1991 as Volume 24, Issues 96-114
- with patches 1 through 4 as Issues 115-118.
-
-
- Hold Those Presses!
-
-
- NEWS FLASH: This column is mostly written, and I was just checking the thing
- over when a plea crossed my desk. Paul Vixie, the postmaster at Dec Western
- Research Laboratories posted a request to news.admin stating that he was
- getting many copies of the postings to comp.sources.unix as its primary
- moderator and would people check their active files as some had it marked as
- unmoderated. Well, I don't remember seeing any change in moderator announced
- (although there have been a lot of "Dump the currently not producing
- moderator" sentiment brewing for a while). So this was a surprise. Perhaps,
- shortly, some postings will resume in this group.
-
-
- Reviews Also Almost Empty
-
-
- New postings to comp.sources.reviewed this time consisted of patches to Chip
- Salzenberg's Deliver program, reported on in the December 1991 issue, plus
- three new programs.
- Deliver is a system to handle incoming electronic mail. It can forward the
- mail based on content, store it in a set of folders, reply with requested
- information to the sender, or anything that can be described as a shell
- script. Patches 7 and 8 to version 2.1 were posted as Volume 1 Issue 15 and
- Volume 1 Issue 40 respectively.
- New postings consisted of mawk, an interpreter for the awk language. awk is a
- utility language provided with UNIX systems named after the first initial of
- each of its authors' last names. The awk language is useful for text
- conversions, general report writing, and almost any task that needs to take
- text in and convert/process it into different text on the way out. This
- interpreter is based on the version of awk as defined in the 1988 book, also
- known as the "New awk." mawk is faster than either the new or old awks and has
- been ported to many flavors of UNIX as well as MS-DOS. mawk was contributed by
- Mike Brennan <brennan@boeing.com> for Volume 1, Issues 16-30.
- The SuperServer program, posted as Volume 1, Issues 1-3 was update with a new
- release. SuperServer allows any program to appear as a TCP/IP service. It does
- so without requiring any special programming. Even shell scripts can be made
- into services and it aids in the debugging of network servers. Note:
- SuperServer does require the BSD Socket calls, including the select system
- call. New features include removing the need for a separate supersrv program.
- The first server started now acts as the master server. In addition, the
- master and its clients now can use UNIX domain sockets instead of IP domain
- sockets. This can save CPU time by not requiring network wide byte ordering to
- be used (optional). Version 1.5 was contributed by Steven Grimm
- <Steven.Grimm@eng.sun.com> for Volume 1 Issues 31-34.
- Lastly, is Ajay Shah's <ajayshah%monty@rand.org> ols, a linear regression
- tool. ols does much of what a normal statistics package does, only by way of
- linear regressions. While the package provides a very useful set of tools to
- fit data, it is designed for those that already know and understand linear
- regression theory. It was posted in Volume 1 as Issues 35-39.
-
-
- misc Overflows My Disk
-
-
- So much was posted to comp.sources.misc, I had to clean out some of the
- directory prior to the end of the two months just to have room. With so many
- new packages posted, I can only go into selections from the over seventy
- different packages that I saved to comment on.
- In December, I reported on crack v3.2a. It turns out that v3.2a was an
- impostor version posted as v3.2b by someone else. To correct this, an updated
- version, 3.3c was posted as Volume 23, Issues 1-5. But, hold, on, Alec David
- Muffett <aem@aber.ac.uk> re-thought through the problem, and rewrote it
- entirely. crack v4.0a was posted as Volume 25, Issues 5-9. crack is a program
- used by system administrators under UNIX to check that users did not pick
- easily compromised passwords. This new version supports network wide load
- leveling, a programmable dictionary generator, better handling of GECOS-based
- passwords, faster fcrypt algorithm, a simpler user interface and better
- portability.
- unproto, mentioned in December's previews from alt.sources has been released
- by Wietse Venema <wietse@wzv.win.tue.nl> for Volume 23, Issues 12 and 13. It
- allows K&R C compilers to compile ANSI C programs by converting the ANSI
- constructs back to K&R constructs after the preprocessor stage. It can handle
- function headings, function pointer type declarations and casts, function type
- declarations and combinations of those items.
- Another posting previewed in December provided changes required to port GCC
- 1.40, GAS 1.38.1, and GDB 3.5 from the GNU project to SCO XENIX. Steve Blezard
- <Steve.Bleazard@robobar.co.uk> contributed these as Volume 23, Issue 28. This
- port works on SCO XENIX 386 and produces files in the native Microsoft/Intel
- OMF format. The GNU binutils are not used, so the port is compatible with the
- standard library files.
- One of the backup tools used for UNIX is cpio (cp in/out, cp is the UNIX copy
- command). cpio has its problems in its original form, including lack of
- multiple volume support (the backup must fit on one tape) and no recovery of
- tape read errors. One error and the entire archive is lost. afio, a freely
- distributable version of cpio, was contributed as v2.2 by Jeff Buhrt
- <prslnk!buhrt> for Volume 23, Issues 33 and 34. It supports floppy disks as
- well as tapes, and allows for compression/uncompression on the fly,
- verification of floppies and restart at any volume break. It can also somewhat
- gracefully handle input data corruption.
- One of the larger postings was an updated release of the Extended Portable
- Bitmap Toolkit (PBMPLUS). In the June 1990 issue I extensively reviewed this
- toolset. It is used to convert various image formats to and from a portable
- format. It also includes many tools for manipulating the images once they are
- in this portable format. The package include four parts: PBM for bitmaps (1
- bit/pixel), PGM for graymaps (grayscale images), PPM for pixmaps (full color
- images), and PNM for context independent manipulations of any of the three
- internal formats. The tools for manipulation include smoothing, scaling,
- inversion, rotation, color map compression, and combining multiple images.
- This is the 27sep91 distribution and was contributed by Jef Poskanzer as
- Volume 23, Issues 36-59 with patch05oct91 issued as Volume 23, Issue 60 and
- patch30oct91 as Volume 25, Issue 33. The patches were mostly bug fixes.
- The follow-on operating system product from the original authors of UNIX is
- Plan 9. It includes a command interpreter that is very heavily based on the C
- programming language. This shell is called rc. Byron Rakitzis
- <byron@archone.tamu.edu> has written his own implementation of this shell and
- contributed version 1.2 for Volume 23, Issues 61-66. It is reasonably small
- and fast, especially when compared to the current do-everything shells in use.
- It is useful for both interactive terminals/windows and command scripts.
- On the other hand, an update to one of those do-everything shells was also
- posted this time. Paul Falstad <pfalstad@phoeniz.princeton.edu> contributed
- v2.1 of his zsh shell for Volume 24, Issues 1-19. zsh has most of the features
- of tcsh, ksh, and bash plus a few more. It is mostly a ksh syntax style shell,
- but it does allow most of the csh syntax. It includes many bells and whistles,
- and provides a new document An Introduction to the Z Shell precompiled into
- Post-Script.
- And lastly, for those that use the Korn shell most everywhere, but don't have
- it on all their systems, Simon J. Gerraty has submitted PDksh, a public domain
- work-alike to the Korn shell (ksh). While it is not 100% compatible with ksh,
- Simon uses it daily on his Sun systems and does not notice the difference
- between it and ksh88.PDksh is Volume 25, Issues 47-55.
- In October I also reported on the generic unzip utility posted by the Info-ZIP
- Workgroup. This month the companion zip utility was contributed for Volume 23,
- Issues 88-96. Info-ZIP <Info-ZIP@valeria.cs.ucla.edu> develops a complete
- archive library system that can handle creating and unpacking compressed file
- archives. These archives are compatible with the PKware ZIP archives.
- Elber Gershon <gershon%gr@cs.utah.edu> has released v3.0 of his Gnuplot
- package for Volume 24, Issues 23-48. This package provides a command-line
- driver interactive plotting utility for UNIX, MS-DOS, and VMS platforms. It
- supports many different types of terminals, plotters and printers and is
- easily extensible to include new devices. Enhancements since v2.0 include
- surface plots, errorbar plots, a rewrite of the Post-Script driver that now
- also supports color PostScript and many bug fixes.
- For those running mail servers or mailing lists, Stephen R. van den Berg
- <berg@messua.informatik.rwth-aachen.de> has updated his procmail program to
- v2.31. Posted as Volume 25, Issues 1-4, procmail can be used to sort incoming
- mail, preprocess incoming mail, or implement mailing lists or servers.
- procmail is small, relatively easy to install, and very configurable.
- Also from the preview section of a prior column, Alan Saunders <tharr!alan>
- has cleaned up and contributed his QBATCH queued batch processing software for
- UNIX for Volume 25, Issues 20-25. UNIX does support background processing, and
- it has the atrun command to allow jobs to be run at later times. However there
- is no queueing mechanism to prevent them all from running at once. QBATCH
- provides this queueing mechanism to prevent any swamping and to increase
- overall throughput. It is a cleanup and rewrite from the earlier preview and
- includes new documentation. Patch 1 was issued as Volume 25, Issues 58 and 59
- to fix some typo-graphicals and bugs plus a few small enhancements.
- If you have an alphanumeric pager (beeper) and wanted to allow your computer
- to send it messages, it's easy enough. All you need to do is talk the IXO
- protocol. Tom Limoncelli <tal@warren.mentorg.com> submitted ixobeeper for
- Volume 25, Issue 43. It enables UNIX systems to send messages to pagers. It
- supports command line or stdin for its messages and can tee the input into
- stdout for a filter effect.
-
- The Tool Command Language, TCL v6.1, was submitted for John Ousterhout by Karl
- Lehenbauer <karl@neosoft.com> for Volume 25, Issues 69-101. This large posting
- is the complete source and documentation for TCL, an embeddable tool command
- language created by John Ousterhout. The distribution includes the original
- paper from the USENIX Winter 1990 conference that described v3.0 of TCL as
- well as current documentation and a test suite. Extensions to TCL, tclx v6.1a
- were also posted at the same time as Volume 26, Issues 1-23. In addition,
- these extensions also provide online help to the original TCL.
- Lastly, beav, the binary file editor from Peter Reilley <pvr@wang.com> was
- released at vl.32. Posted as Volume 26, Issues 37-45, beav supports editing
- binary files interactively. It can not only search and change bytes in place,
- it can also insert and delete bytes, of course changing the length of the
- file. beav is very portable and runs under UNIX, DOS, and AmigaDOS.
- Of course, there were also patches issued in the group. The USENET freely
- distributable spreadsheet, sc was updated to v6.19 by patch 3, submitted by
- Jeff Buhrt as Volume 23, Issue 35. This fixed some null pointer problems and
- added some minor features.
- David Skoll <dfs@doe.carleton.ca> has issued patch 3 for his remind package as
- Volume 22, Issue 102. remind can notify you of events, appointments, or other
- things you feel you should be reminded about via mail or screen notices. Patch
- 4 adds no features, it just tidies up the code and makefile.
- The rayshade construction group <rayshade-request@cs.princeton.edu> has
- released a patch to their large graphics package reported on in the October
- 1991 issue. Patch 1, listed as a "HIGH" priority patch, was posted as Volume
- 23, Issue 75. It fixes many problems and adds transform, window, crop,
- spotlights, and transparency support. It also speeds up several of the
- processes.
- Larry Wall <lwall@netlabs.com> finally issued the long awaited "Patch 11" to
- Perl v4.0. Many times in the past I have sung the praises of Perl. This patch
- (which runs over many parts, but is really one patch) fixes many of the
- outstanding bugs in v4 and also adds a few features. This patch was posted in
- two parts, patches 11 to 18 are the main patch, and patch 19 is a cleanup of
- problems caused by patches 11-18. Posted as Volume 25, Issues 60-68. New
- features include dbz support, calling back to Perl from C routines that were
- called from Perl (not fully calling Perl from C yet...) and several new
- library routines.
-
-
- Games Still Quiet
-
-
- Only four postings in comp.sources.games, so here is each of them.
- George Sicherman <gls@windmill.att.com> contributed conn4 for Volume 12, Issue
- 100. It plays games of Connect Four with you using only a CRT that supports
- cursor movement.
- Metroid in Volume 12, Issue 99 from Ralph Betza <ssiny!gnhmon> allows for
- cracking Nintendo passwords for the game Metroid. These passwords allow access
- to some of the undocumented features of the game.
- Don Dodson <ata@sag.cc.purdue.edu> contributed a version of Othello that only
- uses curses and termcap for Volume 12, Issue 98. This is a small compact
- version of the game as the complete posting is only 817 lines.
- Lastly a patch to Tim Stoehr's rogue clone to allow it to be used with VMS was
- posted by Mike Zraly <mzraly@ldbvax.dnet.lotus.com> as patch 3 to the rogue
- package in Volume 12, Issue 97. It adds all the programs and files necessary
- to port it from UNIX to Vax VMS.
-
-
- Previews from alt.sources
-
-
- As ususal, alt.sources is being used for testing out and checking things that
- will later be posted to the moderated groups. This is as it should be. So here
- are the current previews, along with their date of posting.
- With computer generated faxes becoming more popular, Klaus Schallhorn
- <cnix!klaus> posted a program to convert HP LaserJet output generated from
- almost any program into a PBM file scaled for use as a low or high resolution
- fax image. He includes integration of the package into faxpax. Posted on
- August 29, 1991 in four parts it also includes the bitmaps needed for the HP
- built-in fonts.
- A game to help improve typing skills, loosely based on the Space Invaders type
- games was posted by Larry Moss on October 3, 1991 in one part. It uses falling
- letters where the proper key must be pressed to defend against the letter. He
- added some new features including a bonus round and has made it more portable
- to various systems than the last version.
- Jan-Piet Mens <jpm@logix.de> submitted editbuf, a routine to be used with the
- curses package to allow the user to edit an alphanumeric string. It supports
- using the function keys/cursor keys to move around the current field and edit
- the string. This subroutine for use with curses(3X) was posted on October 17,
- 1991 in one part.
- Dan Bernstein <brnstnd@kramden.acf.nyu.edu> posted his client/server tool set.
- It is a set of three client-server suites that support different protocols all
- with the same well define interface. It allows writing programs that work the
- same way independent of the underlying protocol and will aid in portability of
- the programs that use it. It is UCSPI compliant (UNIX Client/Server Program
- Interface) which will help with future portability issues as well.
- clientservv0.80 was posted on October 21, 1991.
- Those running BBS systems under UNIX may want to look for UNIX-Chat v2.10, a
- multiuser chat utility, designed for UNIX System V. It does not require
- sockets, but instead works with standard UNIX IPC calls. It supports multiple
- conferences at one, and no limit to the number of users in any of the
- conferences. It can use any terminal type, including "dumb" terminals. It was
- posted in seven parts on November 16, 1991 by Marc Laukien. The first part is
- the introduction, the program is the next three parts, followed by three
- patches.
- Now that there is a reason to look for a UNIX BBS, Ken MacLeod
- <unidel@bitsko.slc.ut.us> posted the Unidel UNIX BBS system. It supports
- USENET news, UNIX mail, looks like a Citadel room-based BBS system, secure
- shell access, external editors, file-transfer, chat/talk, CB programs, and it
- is freely distributable. It was posted on November 17, 1991 in seven parts.
- On a totally different note, Lance Norskog <thinman@netcom.com> has posted
- SoundKit, a set of tools to manipulate sound samples in various different
- formats. This package does for sound files what the PBMPLUS package did for
- graphic files. It allows for conversion between many common formats, and for
- simple manipulations on the sound clips. It was posted on November 19, 1991 in
- four parts.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Illustrated C
-
-
- A Portable Menu Compiler, Part 2: The cmenu Translator
-
-
-
-
- Leor Zolman
-
-
- Leor Zolman has been involved with microcomputer programming for 15 years. He
- is the author of BDS C, the first C compiler targeted exclusively for personal
- computers. Leor's first book, Illustrated C, is now available from R&D
- Publications, Inc. Leor and his family live in Lawrence, KS.
-
-
- This is the second installment in a series of columns describing the CMENU
- menu compiler system. Last time I introduced the CMENU specification
- language's syntax, and described the data structures used by the cmenu
- translator (pre-compiler). This month I'll present the entire procedural
- section of the cmenu program.
-
-
- Doing Files
-
-
- cmenu's main function passes the name of each file to be processed, in
- sequence, to the dofile ( ) function.
- dofile( ) (beginning at line 40 of Listing 1) processes a cmenu specification
- file from start to finish. The first few lines chop off the .mnu extension in
- case the user included it in the filename. Two copies are then made of the
- base filename. One (src_name) gets the source file extension appended onto it,
- and the other (obj_name) gets the object file extension.
- If opening the source file succeeds, lines 62-65 initialize some global modes
- and counters. The file is entirely processed within the token loop described
- earlier.
- After an EOF is encountered and the main loop terminates, the file is closed
- and several checks for possible error conditions are performed. In line 85 we
- make sure that at least one menu was defined, or there wouldn't be much point
- in writing an output file.
- Lines 88-93 check if the last menu terminated correctly. If the value of
- global in_menu is TRUE, then there was an endmenu keyword missing. At this
- point, we also call the itemcheck( ) function to see if the last item in the
- last menu was similarly incomplete. (An item isn't complete until both text
- and action clauses for that item have been processed.)
- The last piece of error checking is performed in lines 95-101, a test for
- unresolved forward menu references. The MInfo array is scanned for entries
- having a Processed flag still set to FALSE. Menu entries are created as soon
- as they are first referenced (unlike item entries, for the reasons given
- above). It is, therefore, possible that a menu entry referenced in an lmenu
- statement was never defined or the identifier was misspelled somewhere. Either
- way we get an unresolved menu reference.
- Finally, if there weren't any fatal errors encountered, the object file for
- the current menu is written to disk and dofile( ) returns.
-
-
- Utilities, Utilities
-
-
- The remaining functions in cmenu1.c provide support for creating entries in
- the menu and item info tables, searching for those entries by label name,
- checking for item completeness, writing the output file, string matching, and
- reporting errors and warnings.
- Menu info table management is performed by create_menu( ) and find_menu( ).
- Because MENU structures are stored directly, there is no need to worry about
- memory allocation when creating a new MINFO entry. create_menu( ) defines a
- local MINFO structure, initializes it appropriately, and returns it by value
- to the calling routine. To find a given MINFO entry, find_menu () iterates
- through the MInfo array, comparing the name of each registered entry with the
- name string supplied. When a match occurs, a pointer to the structure having
- the matching name is returned.
- The item info table is managed by create_item( ) and find_item( ). find_item(
- ) works like find_menu( ). Because IINFO structures are stored by reference
- (to reduce memory requirements), create_item( ) must allocate a memory block
- for the new IINFO structure and return a pointer to that block. Provided there
- wasn't any problem with obtaining the memory, both the IINFO structure and its
- member INFO structure get initialized with all the default values appropriate
- to an unprocessed item entry.
- The itemcheck( ) function is called from a few places in cmenu to make sure
- both required portions of an item definition, the text and an action clause,
- have been specified.
- When a menu specification file has been completely processed and no fatal
- errors occurred, the write_file( ) function is called to create the output
- file on disk. After creating the output filename and opening the file for
- writing (in binary mode), the first thing write_file( ) actually writes is the
- value of the global menu count, n_menus. Each menu is then written to the file
- in a format consisting of the MENU structure first, then each associated ITEM
- structure. There is no need to write an explicit item count for each menu
- because that information is already part of the MENU structure, and rmenu can
- obtain that value dynamically when the .mnc file is loading for execution. To
- insure portability across machines with different storage requirements for
- various variable types, all reads and writes involving the .mnc file are
- performed using the sizeof operator to determine how many bytes to transfer.
- Even writing a simple integer value, such as the menu count (line 273), should
- employ sizeof rather than a constant byte count value such as 2 or 4.
- After each item is written to disk, its memory block is freed (line 299). If
- there are many menu definitions in a single menu specification file, then many
- blocks of item memory will end up having been allocated and freed
- repetitively. Since, however, cmenu is only run occasionally, the extra
- overhead this repetition entails isn't enough to have a noticeable effect on
- performance.
- In rmenu, realtime efficiency is far more critical because many processes may
- be active simultaneously. (I often see between ten and fifteen rmenu processes
- running concurrently at the office.) Later we'll see how rmenu employs a
- slightly more complex allocation strategy to avoid the memory thrashing
- problem.
-
-
- Error Reporting
-
-
- The next set of functions in cmenu1.c are warning( ), error( ), and fatalerr(
- ) (lines 307-394). These represent variations on a single theme: display a
- diagnostic message on the standard error device (usually the user's terminal).
- All three functions take a variable number of arguments, allowing format
- conversions like printf( ) to be supported. Since pre-ANSI and post-ANSI C
- differ in their provisions for functions that accept a variable number of
- arguments, I found it necessary to include two different versions of the
- starting sequence in each of the three error reporting functions.
- ANSI C function headers allow the specification of ellipses (...) to indicate
- a variable number of arguments in a function header or prototype. The macros
- va_list and va_start, used in the processing of the variable length argument
- list, are taken from the <stdarg.h> standard header file (see line 16).
- Pre-ANSI C, on the other hand, cannot handle ellipses in function definitions.
- Before the existence of ellipses and <stdarg.h>, functions taking a variable
- number of arguments were processed with the macros defined in <stdarg.h>'s
- predecessor, <varargs.h>. <varargs.h> contains alternate versions of the
- macros va_list and va_start, plus an additional macro named va_alist that is
- used in function headers as the placeholder for optional arguments.
- The usages of <varargs.h> and <stdarg.h> differ only up to (and including) the
- appearance of the va_start macro. After that, the methods are equivalent, so
- the conditional portions of the three error reporting functions end at that
- point.
- Each diagnostic function calls fprintf( ) to send the name and current line
- number of the source file involved to the standard error stream. Then they
- call vfprintf( ) to send the specific error information suppled in the call to
- the standard error stream. The only difference between the three functions is
- in the way they affect the global error flags. warning( ) sets no flags.
- error( ) sets only err_flag. fatalerr( ) sets both err_flag and fatal. Setting
- the fatal flag causes processing of the current source file to be terminated
- immediately upon return to the main processing loop, while err_flag is sampled
- only after the current source file has been completely processed (without
- fatal errors) to determine if an object file should be written.
-
-
- String Matching
-
-
- The matchkey( ) function searches the keyword table to see if a keyword exists
- matching the given string, and returns the token value for that keyword if a
- match is found.
- The strstr( ) function tests if one given string is a subset of a second given
- string. strstr( ) is supplied in case your library does not already include
- it. Compilation of strstr( ) is conditionally controlled by the NEEDSTR
- symbolic constant defined in the makefile.
-
-
-
- Parsing The Input Stream
-
-
- The two major components of cmenu remain to be discussed: token parsing, and
- token processing. Because tokens must be recognized before they can be
- processed, I'll begin with a description of the token parsing process. I'll
- then tackle token processing to tie everything else together.
- Tokens are the basic syntactic objects manipulated by cmenu. Each token is
- represented by an integer value, sometimes in conjunction with the value of an
- auxiliary variable. If the token represents a keyword or special condition,
- then the integer value alone is sufficient to qualify it. If the token
- represents a string or a numeric value, then the token value T_STRING or
- T_VALUE in conjunction with the text in global array tparam (for strings), or
- the integer value of the global vparam (for values) is needed to fully qualify
- the token. You'll find definitions for tparam and vparam in ccmenu.h (see CUJ,
- January, 1992). The token values are defined in lines 41-99.
- The token parsing code resides in cmenu3.c (Listing 3). There are three
- functions in this source file, two of which are called from other parts of the
- program: gettok( ) to get the next token, and ungettok( ) to "unget" a token.
- The last function, getword( ), is called only by gettok( ).
-
-
- getword( ), The Workhorse
-
-
- getword( ) does all the grunt work in the token parsing process. It recognizes
- and skips over comments and whitespace, keeps track of the current line
- number, recognizes both quoted and unquoted text strings, and maps unquoted
- text strings into lowercase. getword( ) returns a pointer to a statically
- allocated text string containing the text of the next token from the input
- file without making any attempt to distinguish a keyword string from straight
- text. (That job is handled by gettok( ).) Starting in line 126 (of Listing 3),
- getword( ) checks for all the special cases just mentioned. Newlines cause the
- lineno global to be incremented, while other whitespace is ignored (including
- commas and semicolons). The only non-alphabetic keyword, the colon, requires a
- special case. This simplifies the code to recognize other tokens (lines
- 146-147).
- Comments are handled by getword( ) in lines 149-162: all characters after the
- leading # are ignored until a newline is encountered (or EOF, should the
- trailing newline be missing). The line count is then incremented.
- Lines 164-200 process quoted strings. First, the quoted_text flag is set to
- TRUE so gettok( ) will know a string has been found. Then the string is
- collected into the tok array, with appropriate action being taken when certain
- special characters are encountered. Because multiline strings are not
- supported, a newline in a string is treated as an error, as is an EOF
- condition. If a double quote is encountered, the string is terminated with a
- zero byte and a pointer to the string is returned to gettok( ).
- Finally, an unquoted string (probably a keyword or an identifier, but possibly
- a short action string or a pathname) is processed in lines 202-211. Any of the
- usual separators or a colon terminate such a string. While it is being
- collected up into the tok array, the string is converted to lowercase. As soon
- as an illegal character is found, it is "ungotten" and a pointer to tok is
- returned.
-
-
- Back To gettok( ) And ungettok( )
-
-
- At the top of cmenu3.c (lines 14-91), several items of static data are defined
- to support the unget feature for tokens.This is a necessary feature for a
- simple language processor like cmenu, because in some cases the code must scan
- past the end of a clause to determine that the clause has come to an end. The
- unget feature allows any token processing function to give back the extra
- token. Then, next time the gettok( ) function is called to get a token from
- the input stream, the ungotten token will be returned again. Only a single
- level of ungetting is needed to process the CMENU syntax.
- When gettok( ) is called it first checks for an active ungotten token. If
- found, all the saved static token detail variables are copied into th eir
- global counterparts and gettok( ) returns the saved token value.
- If there wasn't an active ungotten token, it is time to get a new one. The
- global detail variables tparam and vparam are cleared, and getword( ) is
- called to fetch the next syntactic object from the input stream. Lines 85-98
- handle the easy kinds of tokens: EOF, quoted string, colon, or simple reserved
- word. (In the case of quoted text, the text needs to be copied into tparam.)
- Lines 100-104 process integer values by setting vparam if a leading digit is
- detected and returning T_VALUE. If we reach line 105, the token is treated as
- an unquoted string: the text is copied into tparam, and T_STRING is returned.
- The ungettok( ) function (lines 29-49) first checks to make sure there isn't
- already a token pushed back. (There shouldn't ever be if the program is
- working correctly; this test was put here only to catch possible development
- bugs.) Then all the token detail values are saved in the static variables.
-
-
- Token Processing
-
-
- The cmenu code discussed up to this point all plays a supporting role to the
- actual token processing functions in cmenu2.c. The call to each token
- processing function is placed indirectly through the function pointers stored
- in the keywords table. The dispatching takes place in the main processing loop
- in cmenu1.c.
- As each token processing function receives control, it can count on the
- availability of certain information through global variables. We've already
- seen how most of these global variables are managed. There's one more, named
- token, that contains the most recently scanned token value (responsible for
- arrival at a particular function in cmenu2). This variable is assigned in the
- main processing loop, immediately before the dispatch is performed. We need to
- know this value, because some token processing functions can handle more than
- one token, and they examine the value of token to determine which token they
- have been called to process.
-
-
- The Menu Clause
-
-
- To start at the beginning, the do_menu( ) function (Listing 2, line 20)
- handles the start of a menu definition. The first thing that happens is a
- check to see if the previous menu in the program was properly terminated with
- endmenu. If it wasn't, then do_endmenu ( ) is called and a warning is issued.
- If it was just a missing endmenu statement, the compilation will still
- (begrudgingly) succeed.
- do_menu ( ) then checks for the existence of a menu label by calling gettok( )
- and seeing which token turns up next. If it is not a string, then no label was
- given. This is only tolerated at the start of the file, because the omission
- of a label in subsequent menus would prevent that menu from ever being
- accessible (via the lmenu action). If the first menu is missing a label, a
- dummy label is stuffed into tparam.
- If the label is too long it is truncated (lines 38-43), and then a check is
- made to see if the name appeared previously. If not, then we have the simple
- case of a new definition, and lines 47-49 create an entry for it in the MInfo
- array.
- If the name has been used before (i.e., there's already an entry in MInfo for
- it), then there are two possible explanations. Either it was used in a forward
- lmenu reference, which is legitimate, or the label has already been used in
- the definition of a previous menu, constituting an error (line 55). We can
- tell which case it is by examining the Processed flag associated with that
- MInfo entry.
- When we get to line 57, MIp points to the MInfo entry for the new menu. We now
- need to modify a group of elements of the Menu structure that is itself an
- element of the MInfo entry. So, we assign the address of the Menu structure to
- the pointer Mp, and then initialize everything in the Menu structure through
- that pointer. Mp remains a valid pointer to that Menu structure after return
- from do_menu ( ); several other functions will take advantage of it.
- After all the attendant mode and structure initialization, we check for a
- trailing colon in lines 68-69 and, if found, ignore it.
-
-
- Menu Options
-
-
- The next six functions (lines 75-257) deal with all the possible menu option
- statements that, if present, must appear before the initial item clause.
- do_title( ) and do_path( ) are very similar, each verifying the existence of
- its required text string parameter, making sure the option hasn't appeared
- before, and stuffing the string into the appropriate element of the current
- Menu structure. (Mp comes in handy here.)
- do_path( ) performs all the steps above, plus one more: it deletes any
- trailing path delimiter character (slash or backslash) found at the end of the
- path text. This prevents two consecutive path delimiter characters from
- appearing in a path string when incremental paths are glued together.
- The do_align( ) function was originally intended to support alternative ways
- of aligning the item text on the screen. I later changed my mind about the
- usefulness that option and never taught rmenu how to recognize it. The cmenu
- code for processing the option remains, if someone thinks of a reason for
- rmenu to use it.
- do_spacing( ) and do_columns( ) are similar to do_title( ) and do_path( ), but
- they take an integer instead of a text string for their parameter. I don't
- think they require any further annotation.
- The do_escape( ) function handles both the escape and noescape options,
- checking for the usual error conditions and then setting the escape flag in
- the Menu structure to either YES or NO. If this option does not appear, the
- default value for the escape flag ends up being DEFAULT, which has a very
- different meaning from both YES and NO.
- The do_endmenu( ) function pulls clean-up duty after a menu definition. If no
- items were found, that merits an error message. The forward reference table is
- then checked to make sure all references have been resolved. If an unresolved
- reference is found, then a little shuffle-play is performed involving the
- global line number variable, lineno. This forces the error message function to
- report the line number where the unresolved reference was made, instead of the
- current line number.
- Finally, global modes are reset in lines 284-287 with values indicating a "not
- in menu" status. The Processed flag is set to show that the menu has now been
- defined, and the number of items found is stashed away in the Menu structure
- (squeezing some final mileage out of old venerable Mp).
-
-
- The Item Clause
-
-
-
- There is a lot of parallelism between the set of functions that process item
- related statements and the set we just saw for handling menu related ones. I
- suppose one could even describe the relationship between menus and items as
- fractal, because the structure somewhat repeats itself. There is an item
- clause with an optional identifier, followed by a set of item options, some of
- which are required and some are optional. In the case of item definitions,
- however, there is no requirement that the optional clauses precede the
- required ones (the way menu options must precede all menu items).
- There is no explicit keyword to mark the end of an item definition. The first
- thing, therefore, the do_item( ) function (lines 291-357) must do is check
- that the last item, if any, defined in the current menu included its required
- clauses. The call to itemcheck( ) in line 303 does this.
- Next, we test if a label is attached to the item definition. If not, a dummy
- name is constructed; if so, the identifier name used is checked for legality
- in lines 311-326.
- Lines 328-329 make sure that the label name has not been used before in the
- current menu. Even if a forward reference has been made to this label, it will
- not show up in a call to find_item( ) because the forward reference
- information has all been segregated into the fwd_refs table (to be examined
- shortly).
- Now we can create an entry for the new item, and place a pointer to it in the
- Items array. The create_item( ) function (Listing 1, lines 160-192) allocates
- memory for the new item info structure, initializes its members, and returns a
- pointer to the structure. If there is a problem creating the array,
- create_item( ) returns NULL and the process is aborted.
- Having survived to line 335, the global mode in_item is set and a handy ITEM
- pointer, Ip, is set to point to the ITEM structure element of the IINFO
- structure just created. Ip may then be used throughout the remainder of this
- item's processing.
- Lines 338-340 resolve any possible forward references to the new item. The
- fwd_refs table is scanned for an entry whose name matches the new item's. If
- one is found, the lmenunum element of the item where the reference was made is
- set (indirectly, through the refp pointer) to reflect the new item's index
- value. This overwrites the previous item's lmenunum value of UNDEF_FWD,
- thereby resolving the forward reference.
- The last part of the do_item() function checks to see if the item text is
- present as part of the item clause. To prevent ambiguity, I've required a
- colon to precede any item text included directly in an item clause. Without
- this restriction, the parser couldn't know if text found after the item
- keyword was meant to be a label or an item text string. At this point in
- do_item() (line 344), anything other than a colon terminates the item clause
- processing.
- Even if a colon is spotted, there may not be any item text given. Lines
- 350-351 check for the item text and register it via do_text2( ) if present.
-
-
- Item Options
-
-
- The next six functions handle the various item options and the required action
- clause. By now the pattern of these token processing functions should be
- familiar. I'll skip the routine details and point out the highlights.
- The do_opts( ) function handles a whole slew of trivial binary options by
- setting Item flags to values triggered directly by the option tokens. There
- are three functional categories processed by do_opts( ): prompting,
- pre-clearing and post-clearing. Only one option from each of those three
- categories is permitted to appear within any one item definition. The flag
- associated with each category is initialized to the value DEFAULT. It keeps
- that value until the appropriate statement forces the value to either YES or
- NO.
- The do_nextitem( ) function processes the nextitem clause, so the user may
- alter the menu system's flow of control. One of four variations must be used.
- The last is to supply a label for the next item to be highlighted. If the next
- token after nextitem is a string, then find_item( ) is called to see if the
- item has been defined. If it has, its index is inserted into the nextitem
- element of the Item structure and no forward referencing is involved. If
- find_item( ) couldn't find it, lines 432-437 install the item reference into
- the forward reference table, for resolution when the item definition with the
- given label finally appears. If you missed it, see "The Item Clause" section
- above for related details.
- If the screen text for the menu item was not specified in the item clause, it
- must appear in a subsequent text clause. The do_text( ) and do_text2( )
- functions process this option. After do_text( ) has checked the basics,
- do_text2( ) tests whether the string is too long to store in the available
- space. If it's too long, a message is printed and no attempt is made to copy
- the string into the ITEM structure.
- After copying a reasonable length string in through the Ip pointer, the flag
- named widest (in the MENU structure) is updated to reflect the length of the
- longest item text seen so far. This value will be used by rmenu when it comes
- time to decide how all the menu items will be arranged on the screen.
-
-
- Springing Into Action
-
-
- There must be exactly one action associated with each menu item, from a choice
- of four possibilities. The do_action( ) function is set to process all four
- actions, each using a different token sequence.
- The exit action may be specified either with or without the action keyword
- preceding it. Both forms are valid.
- The basic action clause is specified by the action keyword and a text string.
- The string represents an operating system command (or sequence of commands
- separated by semicolons). All do_action( ) does is copy the string (through
- Ip) into the action element of the current ITEM structure (line 527). The
- emenu action, which calls an external menu, is processed in the same way,
- except that the acttyp element of the ITEM structure is set to identify the
- action string as an external menu name rather than a command to be passed to
- the system's command interpreter.
- The fourth type of action is the lmenu clause, specifying a local menu to run.
- If no menu by the given name is found in the file, a menu structure is created
- and added to the MInfo array, and the index number of the new menu entry is
- plugged into lmenunum element of the item being processed.
- There is nothing new to say about the do_help( ) function, so I won't.
- The final function in the module, do_err( ), gets called whenever a keyword is
- encountered unexpectedly (for example when the first keyword is seen without a
- leading nextitem). In the keywords table array, any keyword that does not
- represent the beginning of a legitimate option sequence is assigned do_err( )
- as its processing function. By calling the fatalerr( ) function to print its
- error message, do_err( ) forces compilation to be terminated immediately after
- the error message is printed, allowing the user to fix the syntax without
- having to deal with additional spurious error messages.
-
-
- Intermission: dmenu
-
-
- This concludes my illustration of the cmenu program. Before delving into rmenu
- in the next installment, I'd like to introduce a little diagnostic utility
- named dmenu. I wrote this short program after eliminating the simple syntax
- errors from my initial stab at the cmenu code, but before rmenu was written.
- At that point, I needed a quick, easy way to examine cmenu's output files for
- debugging purposes. dmenu reads an .mnc file and disassembles it into human
- readable form, providing a complete picture of a compilation job performed by
- cmenu. With the help of dmenu, I was able to debug cmenu quickly, without even
- needing to have menu available. The code for dmenu.c is shown in Listing 4.
-
- Listing 1
- 1: /*************************************************************
- 2: * Program: CMENU Menu Compiler
- 3: * Module: cmenu1.c
- 4: * Menu Compiler:
- 5: * Main and Utility Functions
- 6: * Written by: Leor Zolman, 7/91
- 7: *************************************************************/
- 8:
- g: #define MASTER
- 10: #include "cmenu.h"
- 11: #include "ccmenu.h"
- 12:
- 13: #include <string.h>
- 14:
- 15: #if__STDC______LINEEND____
- 16: # include <stdarg.h>
- 17: #else
- 18: include <varargs.h>
- 19: #endif
-
- 20:
- 21: int main(argc,argv)
- 22: int argc;
- 23: char **argv;
- 24: {
- 25: register i;
- 26:
- 27: printf('CMENU Menu Compiler v%s\n", VERSION);
- 28: if (argc < 2)
- 29: {
- 30: puts("usage: cmenu <menu-source-file(s)>\n");
- 31: return 0;
- 32: }
- 33:
- 34: for (i = 1; i <argc; i++)
- 35: if (dofile(argv[i]) == ERROR) /* process source files */
- 36: return 1;
- 37: return 0;
- 38: }
- 39:
- 40: /************************************************************
- 41: * dofile():
- 42: * Process a single .mnu source file
- 43: ************************************************************/
- 44:
- 45: int dofile(name)
- 46: char *name;
- 47: {
- 48: register i;
- 49: char *cp;
- 50:
- 51: if ((cp = strstr(name, ".mnu"))
- 52: (cp = strstr(name, ".MNU")))
- 53: *cp = '\0';
- 54:
- 55: strcpy(src_name, name);
- 56: strcat(src_name, ".mnu");
- 57: strcpy(obj_name, name);
- 58:
- 59: if ((fp = fopen(src_name, "r")) == NULL)
- 60: return fprintf(stderr, "Can't open %s\n", src_name);
- 61:
- 62: n_menus = 0;
- 63 lineno = 1;
- 64: in_menu = FALSE;
- 65: fatal = FALSE;
- 66:
- 67: /* Main processing loop. Read a token and process it,
- 68: * until end of file is reached:
- 69: */
- 70:
- 71: while ((token = gettok(fp)) != T_EOF)
- 72: {
- 73: if (!in_menu && token != T_MENU)
- 74: {
- 75: error("Each menu must begin with the Menu keyword");
- 76: break;
- 77: }
- 78: if ((*keywords[token].t_func)() == ERROR)
-
- 79: if (fatal) /* If fatal error, exit loop */
- 80: break;
- 81: }
- 82:
- 83: fclose(fp);
- 84:
- 85: if (!n_menus)
- 86: return error("No menus defined");
- 87:
- 88: if (in_menu)
- 89: {
- 90: if (n_items)
- 91: itemcheck();
- 92: error("Menu definition missing \"Endmenu\" statement");
- 93: }
- 94:
- 95: for (i = 0; i < n_menus; i++) /* check for undefined */
- 96: if (!MInfo[i].Processed) /* "lmenu" references */
- 97: {
- 98: printf("Local Menu \"%s\" is undefined.\n",
- 99: MInfo[i] .Name);
- 100: err_flag = TRUE;
- 101: }
- 102:
- 103: if (err_flag)
- 104: return ERROR;
- 105:
- 106: if (write_file() == ERROR)
- 107: return ERROR;
- 108: return OK;
- 109: }
- 110:
- 111:
- 112: /***********************************************************
- 113: * create_menu():
- 114: * Construct a new menu information structure and
- 115: * return it (by value).
- 116: * Set fatal to TRUE if can't create.
- 117: ***********************************************************/
- 118:
- 119: MINFO create_menu(name)
- 120: char *name;
- 121: {
- 122: MINFO mi;
- 123:
- 124: if (n_menus == MAX_MENUS)
- 125: fatalerr("Maximum # of menus (%d) exceeded", MAX_MENUS);
- 126: else
- 127: {
- 128: strcpy(mi_Name, name);
- 129: mi.Processed = FALSE;
- 130: }
- 131: return mi;
- 132: }
- 133:
- 134:
- 135: /***********************************************************
- 136: * find_menu():
- 137: * Search the Menu Info table for a named local menu.
-
- 138: * If found:
- 139: * Return a pointer to the entry if found, and set
- 140: * global variable menu_num to the menu's index
- 141: * else:
- 142: * return NULL
- 143: ***********************************************************/
- 144:
- 145: MINFO *find_menu(name)
- 146: char *name;
- 147: {
- 148: int i;
- 149:
- 150: for (i = 0; i < n_menus; i++)
- 151: if (!strcmp(MInfo[i].Name, name))
- 152: {
- 153: menu_num = i;
- 154: return &MInfo[i];
- 155: }
- 156: return NULL;
- 157: }
- 158:
- 159:
- 160: /***********************************************************
- 161: * create_item(): Allocate space for Item Info structure,
- 162: * Initialize it and return a pointer to the structure
- 163: * Return NULL if there was a creation error.
- 164: ***********************************************************/
- 165:
- 166: IINFO *create_item(name)
- 167: char *name;
- 168: {
- 169: IINFO *IIp;
- 170: ITEM *Ip;
- 171:
- 172: if (n_items == MAX_ITEMS)
- 173: {
- 174: fatalerr("Max. # of items (%d) exceeded", MAX_ITEMS);
- 175: return NULL;
- 176: }
- 177:
- 178: if ((IIp =(IINFO*) malloc(sizeof(IINFO))) == NULL)
- 179: {
- 180: fatalerr("Out of memory");
- 181: NULL;
- 182: }
- 183:
- 184: strcpy(IIp->Name, name);
- 185: Ip = &IIp->Item;
- 186: Ip->acttyp = ACT_NONE;
- 187: Ip->pre_clear = Ip->post_clear = Ip->prompt = DEFAULT;
- 188: Ip->nextcode = DEFAULT;
- 189: Ip->nextitem = Ip->lmenunum = 0;
- 190: *Ip->text = *Ip->path = *Ip->action = *Ip->help = '\0';
- 191: return IIp;
- 192: }
- 193:
- 194:
- 195: /***********************************************************
- 196: * find_item():
-
- 197: * Search the Item Info table for a named item in the
- 198: * currently active menu definition.
- 199: * If item name found:
- 200: * Set item_num to the index value of the Item,
- 201: * Return a pointer to the entry
- 202: * else:
- 203: * return NULL
- 204: ***********************************************************/
- 205:
- 206: IINFO *find_item(name)
- 207: char *name;
- 208: {
- 209: int i;
- 210:
- 211: for (i = 0; i < n_items; i++)
- 212: if (!strcmp(MIp->Items[i]->Name, name))
- 213: {
- 214: item_num = i;
- 215: return MIp->Items[i];
- 216: }
- 217: return NULL;
- 218: }
- 219:
- 220:
- 221: /***********************************************************
- 222: * itemcheck():
- 223: * Check the currently active item to make sure
- 224: * both a Text and an Action clause have been
- 225: * explicitly given.
- 226: ***********************************************************/
- 227:
- 228: Void itemcheck()
- 229: {
- 230: if (!*Ip->text)
- 231: error("No TEXT clause found for current item");
- 232: if (Ip->acttyp == ACT_NONE)
- 233: error("No ACTION clause found for current item");
- 234: }
- 235:
- 236:
- 237: /***********************************************************
- 238: * write_file():
- 239: * Write menu object file to disk, ready for
- 240: * execution via menu.
- 241: * Menu object file format:
- 242: * --------------------------------
- 243: * <count> (integer count of # of menus in file)
- 244: * MENU 1 (MENU structure for 1st Menu)
- 245: * ITEM 1
- 246: * ITEM 2
- 247: * ...
- 248: * ITEM n_items
- 249: * MENU 2 (MENU structure for 2nd Menu)
- 250: * ...
- 251: * .
- 252: * .
- 253: * .
- 254: * MENU <count> (MENU structure for final Menu)
- 255: * ...
-
- 256: * --------------------------------
- 257: *
- 258: ***********************************************************/
- 259:
- 260: int write_file()
- 261: {
- 262: int i,j;
- 263:
- 264: strcat(obj_name, ".mnc");
- 265:
- 266: if ((fp = fopen(obj_name, "wb")) == NULL)
- 267: {
- 268: fprintf(stderr,
- 269: "Cannot open %s for writing.\n", obj_name);
- 270: return ERROR;
- 271: }
- 272:
- 273: if (fwrite((Void *)&n_menus, sizeof n_menus, 1, fp) != 1)
- 274: {
- 275: fprintf(stderr,
- 276: "Error writing menu count to %s\n", obj_name);
- 277: return ERROR;
- 278: }
- 279:
- 280: for (i = 0; i < n_menus; i++)
- 281: {
- 282: Mp = &MInfo[i].Menu;
- 283: if (fwrite((Void *) Mp, sizeof (MENU), 1, fp) != 1)
- 284: {
- 285: fprintf(stderr,
- 286: "Error writing to %s\n", obj_name);
- 287: return ERROR;
- 288: }
- 289:
- 290: for (j = 0; j < Mp->nitems; j++)
- 291: {
- 292: if (fwrite((Void *) &MInfo[i].Items[j]->Item,
- 293: sizeof (ITEM), 1, fp) != 1)
- 294: {
- 295: fprintf(stderr,
- 296: "Error writing to %s\n", obj_name);
- 297: return ERROR;
- 298: }
- 299: free(MInfo[i].Items[j]);
- 300: }
- 301: }
- 302: printf("Menu object file %s written.\n", obj_name);
- 303: return OK;
- 304: }
- 305:
- 306:
- 307: /***********************************************************
- 308: * warning():
- 309: * Display a warning message, preceded by source
- 310: * file name and line number, supporting format
- 311: * conversions.
- 312: ***********************************************************/
- 313:
- 314: #if_STDC_ /* ANSI variable-#-of-args method: */
-
- 315: int warning(char *fmt, ...)
- 316: {
- 317: va_list arglist;
- 318: va_start(arglist, fmt);
- 319:
- 320: #else /* old "varargs" method: */
- 321: int warning(fmt, va_alist)
- 322: char *fmt;
- 323: va_dcl
- 324: {
- 325: va_list arglist;
- 326: va_start(arglist);
- 327: #endif
- 328: /* the rest is the same, ANSI or varargs: */
- 329:
- 330: fprintf(stderr, "%s (%d): ", src_name, lineno);
- 331: vfprintf(stderr, fmt, arglist);
- 332: va_end(arglist);
- 333: fprintf(stderr, "\n");
- 334: return OK;
- 335: }
- 336:
- 337:
- 338: /***********************************************************
- 339: * error():
- 340: * Display an error message, preceded by source
- 341: * file name and line number, supporting format
- 342: * conversions.
- 343: ***********************************************************/
- 344:
- 345: #if__STDC__ /* ANSI variable-#-of-args method: */
- 346: int error(char *fmt, ...)
- 347: {
- 348: va_list arglist;
- 349: va_start(arglist, fmt);
- 350:
- 351: #else /* old "varargs" method: */
- 352: int error(fmt, va_alist)
- 353: char *fmt;
- 354: va_dcl
- 355: {
- 356: va_list arglist;
- 357: va_start(arglist);
- 358: #endif
- 359:
- 360: /* the rest is the same, ANSI or varargs: */
- 361:
- 362: fprintf(stderr, "%s (%d): ", src_name, lineno);
- 363: vfprintf(stderr, fmt, arglist);
- 364: va_end(arglist);
- 365: fprintf(stderr, "\n");
- 366: err_flag = TRUE;
- 367: return ERROR;
- 368: }
- 369:
- 370:
- 371: /***********************************************************
- 372: * fatalerr():
- 373: * Like error, except global flag "fatal" is set.
-
- 374: ***********************************************************/
- 375:
- 376: #if __STDC__ /* start function the ANSI way... */
- 377: int fatalerr(char *fmt, ... /)
- 378: {
- 379: va_list arglist;
- 380: va_start(arglist, fmt);
- 381: #else /* or the old "varargs" way... */
- 382: int fatalerr(fmt, va_alist)
- 383: char *fmt;
- 384: va_dcl
- 385: {
- 386: va_list arglist;
- 387: va_start(arglist);
- 388: #endif
- 389:
- 390: error(fmt, arglist); /* the rest is same, ANSI or varargs */
- 391: va_end(arglist);
- 392: fatal = TRUE;
- 393: return ERROR;
- 394: }
- 395:
- 396:
- 397: /***********************************************************
- 398: * matchkey():
- 399: * Test if given string is a reserved word. Return the token
- 400: * value of the matching token if so; else return NULL.
- 401: ***********************************************************/
- 402:
- 403: int matchkey(str)
- 404: char *str;
- 405: {
- 406: char str2[MAX_CMD], *cp;
- 407: int i;
- 408:
- 409: strcpy(str2, str);
- 410: for (cp = str2; *cp; cp++)
- 411: *cp = tolower(*cp);
- 412:
- 413: for (i = 0; i < (int) N_KEYWORDS; i++)
- 414: if (!strcmp(keywords[i].keyword, str2))
- 415: return i;
- 416:
- 417: return NULL;
- 418: }
- 419:
- 420:
- 421: #ifdef NEEDSTR
- 422: /*
- 423: * Search for first occurence of substring s2 in s1:
- 424: * (provided for Xenix only; this function is in
- 425: * most modern standard distribution libraries)
- 426: */
- 427:
- 428: char *strstr(s1, s2)
- 429: char *s1, *s2;
- 430: {
- 431: int i, j, nposs;
- 432: int len1 = strlen(s1);
-
- 433:
- 434: int len2 = strlen(s2);
- 435: char *pl;
- 436:
- 437:
- 438: if (len1 < len2)
- 439: return NULL;
- 440:
- 441: nposs = len1 - len2;
- 442:
- 443: for (i = 0; i <= nposs; i++)
- 444: {
- 445: for (j = 0, p1 = &s1[i]; j < len2; j++)
- 446: if (*pl++ != s2[j])
- 447: break;
- 448: if (j == len2)
- 449: return &s1[i];
- 450: }
- 451: return NULL;
- 452: }
- 453: #endif
- /* End of File */
-
-
- Listing 2
- 1: /*************************************************************
- 2: * Program: CMENU Menu Compiler
- 3: * Module: cmenu2.c
- 4: * Menu Compiler:
- 5: * Menu/Item Token Processing Functions
- 6: * Written by: Leor Zolman, 7/91
- 7: *************************************************************/
- 8:
- 9: #include "cmenu.h"
- 10: #include "ccmenu.h"
- 11:
- 12: #include <ctype.h>
- 13:
- 14:
- 15: /************************************************************
- 16: * do_menu():
- 17: * Process the MENU keyword
- 18: *************************************************************/
- 19:
- 20: int do_menu()
- 21: {
- 22: int tok;
- 23:
- 24: if (in_menu) /* Are we currently processing a menu? */
- 25: { /* yes. */
- 26: warning("Endmenu missing from previous menu");
- 27: do_endmenu(); /* give them the benefit of the doubt */
- 28: }
- 29:
- 30: if ((tok = gettok()) != T_STRING)
- 31: {
- 32: if (n_menus)
- 33: error("Menu name missing; menu unreachable");
- 34: sprintf(tparam, "menu%d", n_menus + 1); /* force a name */
-
- 35: ungettok(tok);
- 36: }
- 37:
- 38: if (strlen(tparam) > MAX_NAME)
- 39: {
- 40: error("The name '%s' is too long (%d chars max)",
- 41: tparam, MAX_NAME);
- 42: tparam[MAX_NAME] = '\0'; /* truncate name */
- 43: }
- 44:
- 45: if ((MIp = find_menu(tparam)) == NULL) /* menu exist? */
- 46: {
- 47: MInfo[n_menus] = create_menu(tparam); /* no. */
- 48: if (fatal)
- 49: return ERROR; /* creation error */
- 50: else
- 51: MIp = &MInfo[n_menus++]; /* OK, bump count */
- 52: }
- 53: else
- 54: if (MIp -> Processed) /* redefinition? */
- 55: return fatalerr("Duplicate Menu definition"); /* yes. */
- 56:
- 57: Mp = &MIp -> Menu;
- 58: *Mp->title = *Mp->path = '\0';
- 59: Mp->nitems = Mp->widest = 0;
- 60: Mp->spacing = Mp->columns = Mp->escape = DEFAULT;
- 61: Mp->align = DEFAULT;
- 62:
- 63: in_item = FALSE; /* state variables describing the */
- 64: in_menu = TRUE; /* current menu being processed */
- 65: n_items = 0;
- 66: n_refs = 0; /* no forward item references yet */
- 67:
- 68: if ((tok = gettok()) != T_COLON) /* optional colon */
- 69: ungettok(tok);
- 70:
- 71: return OK;
- 72: }
- 73:
- 74:
- 75: /************************************************************
- 76: * do_title():
- 77: * Process the TITLE clause for a menu.
- 78: ************************************************************/
- 79:
- 80: int do_title()
- 81: {
- 82: int tok;
- 83:
- 84: if ((tok = gettok()) != T_STRING)
- 85: {
- 86: error("Title text missing");
- 87: ungettok(tok);
- 88: }
- 89:
- 90: if (!in_item) /* Before all items? */
- 91: { /* yes. */
- 92: if (*Mp->title)
- 93: return error("A Menu Title has already been specified");
-
- 94: strcpy(Mp->title, tparam);
- 95: }
- 96: else
- 97: return error("The Menu Title must precede all Menu Items.");
- 98:
- 99: return OK;
- 100: }
- 101:
- 102:
- 103: /***********************************************************
- 104: * do_path():
- 105: * Process the PATH option.
- 106: * Note that the PATH option may apply to an entire
- 107: * menu or just to a single menu item (determined
- 108: * by context.)
- 109: ***********************************************************/
- 110:
- 111: int do_path()
- 112: {
- 113: int tok;
- 114: char *cp;
- 115:
- 116: if ((tok = gettok()) != T_STRING)
- 117: {
- 118: error("Path text missing");
- 119: ungettok(tok);
- 120: }
- 121:
- 122: if (tparam[strlen(tparam)-1]=='/' tparam[strlen(tparam)-1]='\\')
- 123: tparam[strlen(tparam) - 1] = '\0'; /* delete traling slash */
- 124:
- 125: if (!in_item) /* Referring to the menu? */
- 126: { /* yes. */
- 127: if (*Mp->path)
- 128: return error("A Menu Path has already been specified");
- 129: strcpy(Mp->path, tparam);
- 130: }
- 131: else
- 132: { /* Must be for the item. */
- 133: if (*Ip->path)
- 134: return error("An Item Path has already been specified");
- 135: strcpy(Ip->path, tparam);
- 136: }
- 137: return OK;
- 138: }
- 139:
- 140:
- 141: /***********************************************************
- 142: * do_align():
- 143: * Process text alignment option.
- 144: * Note: this option is a no-op. I decided there wasn't
- 145: * any real need for any other than left-justified item
- 146: * alignment. But, in case anyone thinks of a use for it,
- 147: * I'm leaving in the ability to process the option.
- 148: ***********************************************************/
- 149:
- 150: int do_align()
- 151: {
- 152: int tok;
-
- 153:
- 154: if (in_item)
- 155: return error("The Align clause must precede all Menu Items.");
- 156:
- 157: if (Mp->align)
- 158: return error("Align option already specified for this menu");
- 159:
- 160: switch (tok = gettok())
- 161: {
- 162: case T_LEFT:
- 163: Mp->align = 'L';
- 164: break;
- 165:
- 166: case T_RIGHT:
- 167: Mp->align = 'R';
- 168: break;
- 169:
- 170: case T_CENTER:
- 171: Mp->align = 'C';
- 172: break;
- 173:
- 174: default:
- 175: ungettok(tok);
- 176: return error("Align missing valid modifier");
- 177: }
- 178: return OK;
- 179: }
- 180:
- 181:
- 182: /***********************************************************
- 183: * do_spacing():
- 184: * Process the SPACING option (applies
- 185: * to menus only.)
- 186: ***********************************************************/
- 187:
- 188: int do_spacing()
- 189: {
- 190: int tok;
- 191:
- 192: if ((tok = gettok()) != T_VALUE)
- 193: {
- 194: error("Spacing value missing");
- 195: ungettok(tok);
- 196: }
- 197:
- 198: if (in_item)
- 199: return error("Spacing option must precede all menu items");
- 200:
- 201: if (Mp->spacing)
- 202: return error("Spacing option already specified");
- 203:
- 204: /* only single and double spacing supported */
- 205: if (vparam != 1 && vparam != 2)
- 206: return error("Spacing value must be either 1 or 2");
- 207:
- 208: Mp->spacing = vparam;
- 209: return OK;
- 210: }
- 211:
-
- 212:
- 213: /***********************************************************
- 214: * do_columns():
- 215: * Process the COLUMNS option
- 216: ***********************************************************/
- 217:
- 218: int do_columns()
- 219: {
- 220: int tok;
- 221:
- 222: if ((tok = gettok()) != T_VALUE)
- 223: {
- 224: error("Colunms value missing");
- 225: ungettok(tok);
- 226: }
- 227:
- 228: if (in_item)
- 229: return error("Columns option must precede all menu items");
- 230:
- 231: if (Mp->columns)
- 232: return error("Columns option already specified");
- 233:
- 234: if (vparam < 1 vparam > 6) /* 6 seems a reasonable max. */
- 235: return error("Columns value must be between 1 and 6");
- 236: Mp->columns = vparam;
- 237: return OK;
- 238: }
- 239:
- 240:
- 241: /***********************************************************
- 242: * do_escape():
- 243: * Process "escape" and "noescape" menu options
- 244: ***********************************************************/
- 245:
- 246: int do_escape()
- 247: {
- 248: if (in_item)
- 249: return error("\"%s\" must appear before all menu items",
- 250: keywords[token] .keyword);
- 251:
- 252: if (Mp->escape)
- 253: return error("Escape option already specified");
- 254: Mp->escape = (token == T_ESCAPE) ? YES : NO;
- 255:
- 256: return OK;
- 257: }
- 258:
- 259:
- 260: /***********************************************************
- 261: * do_endmenu():
- 262: * Process ENDMENU keyword
- 263: ***********************************************************/
- 264:
- 265: int do_endmenu()
- 266: {
- 267: int i;
- 268:
- 269: if (!n_items)
- 270: error("No menu items specified for this menu");
-
- 271:
- 272: for (i = 0; i < n_refs; i++) /* check for unresolved */
- 273: { /* forward item references */
- 274: if (*fwd_refs[i].refp == UNDEF_FWD)
- 275: {
- 276: int save_lineno = lineno;
- 277: lineno = fwd_refs[i] .lineno;
- 278: error("Unresolved reference to Item \"%s\"",
- 279: fwd_refs[i].iname);
- 280: lineno = save_lineno;
- 281: }
- 282: }
- 283:
- 284: in_menu = in_item = FALSE; /* done with current menu */
- 285: MIp -> Processed = TRUE; /* it is now processed */
- 286: Mp -> nitems = n_items;
- 287: return OK;
- 288: }
- 289:
- 290:
- 291 /************************************************************
- 292: * do_item():
- 293: * Process the ITEM clause. Create a new ite
- 294: * and fill in any existing forward references to it.
- 295: ************************************************************/
- 296:
- 297: int do_item()
- 298: {
- 299: int tok, i;
- 300: char *cp, c;
- 301:
- 302: if (n_items)
- 303: itemcheck(); /* check for previous item's completion */
- 304:
- 305: if ((tok = gettok()) != T_STRING) /* label specified? */
- 306: { /* If not, stuff unique */
- 307: sprintf(tparam,"dummy!%d", n_items); /* dummy name in */
- 308: ungettok(tok);
- 309: }
- 310: else
- 311: {
- 312: if (strlen(tparam) > MAX_NAME)
- 313: {
- 314: error("Item name \"%s\" too long. Max %d chars.",
- 315: tparam, MAX_NAME);
- 316: tparam[MAX_NAME] = '\0';
- 317: }
- 318: else for (cp = tparam; c = *cp; cp++)
- 319: if (!(isalpha(c) isdigit(c) c == '_'))
- 320: {
- 321: error("Invalid char in identifier name: \"%s\"",
- 322: tparam);
- 323: *cp = '\0';
- 324: break;
- 325: }
- 326: }
- 327:
- 328: if ((IIp = find_item(tparam)) != NULL) /" Item name found? */
- 329: return error("Item name previously used.");
-
- 330:
- 331: if ((MIp->Items[n_items] =IIp = create_item(tparam))
- 332: == NULL)
- 333: return ERROR;
- 334:
- 335: in_item = TRUE;
- 336: Ip = &IIp->Item;
- 337:
- 338: for (i = 0; i < n_refs; i++) /* check for fwd refs */
- 339: if (!strcmp(fwd_refs[i].iname, tparam))
- 340: *fwd_refs[i].refp = n_items; /* fill in with item # */
- 341:
- 342: n_items++; /* bump item count */
- 343:
- 344: if ((token = gettok()) != T_COLON) /* optional colon? */
- 345: {
- 346: ungettok(token); /* if not, all done */
- 347: return OK;
- 348: }
- 349:
- 350: if ((token = gettok()) == T_STRING) /* short-form text? */
- 351: return do_text2(); /* if so, go process */
- 352: else
- 353: {
- 354: ungettok(token); /* else all done */
- 355: return OK;
- 356: }
- 357: }
- 358:
- 359:
- 360: /***********************************************************
- 361: * do_opts():
- 362: * Process simple "binary" options for prompt,
- 363: * pre- and post-clear specifications.
- 364: * Note: upon entry, global "token" contains the
- 365: * value of the token to be processed.
- 366: ***********************************************************/
- 367:
- 368: int do_opts()
- 369: {
- 370: if (!in_item)
- 371: return error("\"%s\" only valid within an item",
- 372: keywords[token].keyword);
- 373:
- 374: switch(token)
- 375: {
- 376: case T_PROMPT: case T_PAUSE:
- 377: case T_NOPROMPT: case T_NOPAUSE:
- 378: if (Ip->prompt != DEFAULT)
- 379: return error("Prompt option already specified");
- 380: Ip->prompt= (token==T_PROMPT token==T_PAUSE)? YES :NO;
- 381: break;
- 382:
- 383: case T_POSTCLEAR: /* these are actually no-ops, */
- 384: case T_NOPOSTCLEAR: /* but again, I've left them in */
- 385: if (Ip->post_clear != DEFAULT)
- 386: return error("Postclear option already specified");
- 387: Ip->post_clear = (token == T_POSTCLEAR) ? YES : NO;
- 388: break;
-
- 389:
- 390: case T_PRECLEAR:
- 391: case T_NOPRECLEAR:
- 392: if (Ip->pre_clear != DEFAULT)
- 393: return error("Preclear option already specified");
- 394: Ip->pre_clear = (token == T_PRECLEAR) ? YES : NO;
- 395: break;
- 396: }
- 397: return OK;
- 398: }
- 399:
- 400:
- 401: /***********************************************************
- 402: * do_nextitem():
- 403: * Process NEXTIEM option.
- 404: ***********************************************************/
- 405:
- 406: int do_nextitem()
- 407: {
- 408: int tok;
- 409:
- 410: if (Ip->nextcode != DEFAULT)
- 411: error("Nextitem option already specified");
- 412:
- 413: switch (tok = gettok())
- 414: {
- 415: case T_FIRST:
- 416: Ip->nextcode = NXT_FIRST;
- 417: break;
- 418:
- 419: case T_LAST:
- 420: Ip->nextcode = NXT_LAST;
- 421: break;
- 422:
- 423: case T_NEXT:
- 424: Ip->nextcode = NXT_NEXT;
- 425: break;
- 426:
- 427: case T_STRING:
- 428: Ip->nextcode = NXT_DIRECT;
- 429: if (find_item(tparam))
- 430: Ip->nextitem = item_num;
- 431: else
- 432: { /* record forward item reference */
- 433: strcpy(fwd_refs[n_refs] .iname, tparam);
- 434: fwd_refs[n_refs].refp = &Ip->nextitem;
- 435: fwd_refs[n_refs++].lineno = lineno;
- 436: Ip->nextitem = UNDEF_FWD;
- 437: }
- 438: break;
- 439:
- 440: default:
- 441: ungettok(tok);
- 442: return error("Bad Nextitem specification");
- 443: }
- 444: return OK;
- 445: }
- 446:
- 447:
-
- 448: /***********************************************************
- 449: * do_text():
- 450: * Process Text parameter
- 451: ***********************************************************/
- 452:
- 453: int do_text()
- 454: {
- 455: int tok;
- 456:
- 457: if (!in_item)
- 458: return error("Text clause must be within an item");
- 459: if (*Ip->text)
- 460: return error("Text clause already specified for this item");
- 461:
- 462: if ((tok = gettok()) != T_STRING)
- 463: {
- 464: ungettok(tok);
- 465: return error("Text clause specified without the text.");
- 466: }
- 467:
- 468: return do_text2();
- 469: }
- 470:
- 471:
- 472: /***********************************************************
- 473: * do_text():
- 474: * Continued TEXT clause processing, shared between
- 475: * do_text() and do_item().
- 476: ***********************************************************/
- 477:
- 478: int do_text2()
- 479: {
- 480: if (strlen(tparam) > MAX_TXTWID)
- 481: {
- 482: *Ip->text = 'x'; /* to avoid "missing text" error */
- 483: return error("Text is too long; maximum %d chars",
- 484: MAX_TXTWID);
- 485: }
- 486: else
- 487: strcpy(Ip->text, tparam);
- 488:
- 489: if (strlen(tparam) > Mp -> widest)
- 490: Mp -> widest = strlen(tparam);
- 491:
- 492: return OK;
- 493: }
- 494:
- 495:
- 496: /***********************************************************
- 497: * do_action():
- 498: * Process standard action, Exit, Lmenu or Emenu clause
- 499: ***********************************************************/
- 500:
- 501: int do_action()
- 502: {
- 503: int tok;
- 504: int old_acttyp = Ip->acttyp;
- 505:
- 506: if (!in_item)
-
- 507: return error("%s clause only valid within an item",
- 508: keywords[token].keyword);
- 509:
- 510: if (token == T_EXIT (tok = gettok()) == T_EXIT)
- 511: Ip->acttyp = ACT_EXIT;
- 512: else
- 513: if (tok !=T_STRING)
- 514: {
- 515: ungettok(tok);
- 516: error("Incomplete %s specification",
- 517: keywords[token] .keyword);
- 518: }
- 519: else
- 520: if (strlen(tparam) > MAX_CHD)
- 521: error("%s parameter too long (max %d chars)",
- 522: keywords[token] .keyword, MAX_CMD);
- 523: else
- 524: switch(token)
- 525: {
- 526: case T_ACTION:
- 527: strcpy(Ip->action, tparam);
- 528: Ip->acttyp = ACT_CMND;
- 529: break;
- 530:
- 531: case T_L_MENU:
- 532: if (find_menu(tparam) != NULL) /* named menu defined? */
- 533: Ip->lmenunum = menu_num; /* yes, */
- 534: else
- 535: { /* no. create entry */
- 536: MInfo[n_menus] = create_menu(tparam);
- 537: if (fatal)
- 538: return ERROR; /* creation error */
- 539: else
- 540: Ip->lmenunum = n_menus++; /* Ok; assign. */
- 541: }
- 542:
- 543: Ip->acttyp = ACT_LMENU;
- 544: break;
- 545:
- 546: case T_EMENU:
- 547: strcpy(Ip->action, tparam);
- 548: Ip->acttyp = ACT_EMENU;
- 549: break;
- 550: }
- 551:
- 552: if (old_acttyp)
- 553: return error("Only one Action clause allowed per item");
- 554:
- 555: return OK;
- 556: }
- 557:
- 558:
- 559: /***********************************************************
- 560: * do_help():
- 561: * Process help clause.
- 562: ***********************************************************/
- 563:
- 564: int do_help()
- 565: {
-
- 566: int tok;
- 567:
- 568: if (!in_item)
- 569: return error("Help clause only valid within an item");
- 570:
- 571: if ((tok = gettok()) != T_STRING)
- 572: {
- 573: ungettok(tok);
- 574: return error("No Help text specified");
- 575: }
- 576:
- 577: if (strlen(tparam) > MAX_HELP)
- 578: return error("Help text too long (max %d chars)",
- 579: MAX_HELP);
- 580:
- 581: if (*Ip->help)
- 582: return error("Only one help line allowed per item");
- 583:
- 584: strcpy(Ip->help, tparam);
- 585: return OK;
- 586: }
- 587:
- 588:
- 589: /***********************************************************
- 590: * do_err():
- 591: * Diagnose hopelessly bad syntax (i.e., encountering a
- 592: * totally unexpected keyword)
- 593: ***********************************************************/
- 594:
- 595: int do_err()
- 596: {
- 597: return fatalerr("Unrecoverable Syntax error.");
- 598: }
- 599:
- /* End of File */
-
-
- Listing 3
- 1: /*************************************************************
- 2: * Program: CMENU Menu Compiler
- 3: * Module: cmenu3.c
- 4: * Menu Compiler:
- 5: * Token Processing Functions
- 6: * Written by: Leor Zolman, 7/91
- 7: *************************************************************/
- 8:
- 9: #include "cmenu.h"
- 10: #include "cmenu.h"
- 11:
- 12: #include <ctype.h>
- 13:
- 14: static int unget_flag = FALSE;
- 15: static int unget_token;
- 16: static char unget_tparam[MAX_CMD];
- 17: static int unget_vparam;
- 18:
- 19: static int quoted_text;
- 20:
- 21: #if_STDC_____LINEEND____
-
- 22: char *getword(void);
- 23: int matchkey(char *);
- 24: #else
- 25: char *getword();
- 26: int matchkey();
- 27: #endif
- 28:
- 29: /************************************************************
- 30: * ungettok():
- 31: * Push a token back into the input stream, to
- 32: * be returned by the following call to gettok().
- 33: * Only one level of push-back is supported; any attempt to
- 34: * push back a token when there is already one pushed back
- 35: * draws an "internal error" diagnostic.
- 36: ************************************************************/
- 37:
- 38: Void ungettok(tok)
- 39: int tok;
- 40: {
- 41: if (unget_flag) /* can't "unget" more than 1 item! */
- 42: fatalerr("internal error: ungettok() overflow");
- 43:
- 44: unget_flag = TRUE;
- 45: unget_token = tok;
- 46: unget_vparam = vparam;
- 47: strcpy(unget_tparam, tparam);
- 48: return;
- 49: }
- 50:
- 51:
- 52: /************************************************************
- 53: * gettok():
- 54: * Read a token from the source input stream.
- 55: * If the token is a reserved word, the appropriate token
- 56: * value is returned.
- 57: * If the token is a string, the global "tparam" is set
- 58: * to the text of the string. White space within the
- 59: * string is only recognized if double quote ("...")
- 60: * characters are used to delimit the string.
- 61: * T_STRING is returned.
- 62: * If the token is a numeric value, the global "vparam"
- 63: * is set to the integer value specified, and
- 64 * T_VALUE is returned.
- 65 * Returns T_EOF on end-of-file.
- 66: ************************************************************/
- 67:
- 68: int gettok()
- 69: {
- 70: register c;
- 71: char nexttok[MAX_CMD];
- 72: char *wordp;
- 73:
- 74: if (unget_flag) /* was a token "pushed back"? */
- 75: { /* yes. set the pushed-back values and */
- 76: vparam = unget_vparam; /* attributes */
- 77: strcpy(tparam, unget_tparam); /* clear the */
- 78: unget_flag = FALSE; /* flag */
- 79: return unget_token; /* return pushed token */
- 80: }
-
- 81:
- 82: *tparam = '\0'; /* clear parameter */
- 83: vparam = 0; /* value registers */
- 84:
- 85: if (!*(wordp = getword())) /* get a token. */
- 86: return token = T_EOF; /* End of file */
- 87:
- 88: if (quoted_text) /* string enclosed */
- 89: { /* in quotes? */
- 90: strcpy(tparam, wordp);
- 91: return T_STRING;
- 92: }
- 93:
- 94: if (!strcmp(wordp, ":")) /* colon is special */
- 95: return T_COLON; /* (non-alphabetic)*/
- 96:
- 97: if (c = matchkey(wordp)) /* reserved word? */
- 98: return c; /* yes, just return */
- 99:
- 100: if (isdigit(*wordp)) /* handle numeric value */
- 101: {
- 102: vparam = atoi(wordp);
- 103: return T_VALUE;
- 104: }
- 105: else
- 106: {
- 107: strcpy(tparam, wordp);
- 108: return T_STRING;
- 109: }
- 110: }
- 111:
- 112:
- 113: /***********************************************************
- 114: * getword():
- 115: * Read the next syntactic object from the input stream,
- 116: * and return a pointer to it.
- 117: * Return pointer to a null string on EOF.
- 118: * If object is a quoted string, drop the quotes and
- 119: * set the quoted_text flag (preserve whitespace).
- 120: * Otherwise strip all whitespace, commas and comments,
- 121: * return pointer to next word/number.
- 122: * Track current line number by incrementing lineno
- 123: * on each newline encountered.
- 124: ***********************************************************/
- 125:
- 126: char *getword()
- 127: {
- 128: static char tok[MAX_CMD];
- 129: char quote_char;
- 130: register c,i;
- 131:
- 132: quoted_text = FALSE;
- 133: *tok = '\0';
- 134:
- 135: while ((c = getc(fp)) != EOF)
- 136: {
- 137: if (c == '\n') /* bump line number if */
- 138: lineno++; /* newline encountered */
- 139:
-
- 140: if (isspace(c)) /* skip all whitespace */
- 141: continue;
- 142:
- 143: if (c == ',' c == ';') /*legal separators: ,; */
- 144: continue;
- 145:
- 146: if (c == ':') /* special case: colon */
- 147: return ":";
- 148:
- 149: if (c == '#') /* process comment */
- 150: { /* wait for newline or EOF */
- 151: while(c = getc(fp))
- 152: {
- 153: if (c == EOF)
- 154: break;
- 155: if (c == '\n')
- 156: {
- 157: lineno++;
- 158: break;
- 159: }
- 160: }
- 161: continue; /* then get next token */
- 162: }
- 163:
- 164: if (c == '"' c == '\" ) /* quoted string? */
- 165: {
- 166: quoted_text = TRUE;
- 167: quote_char = c;
- 168: for (i = 0; ;i++)
- 169: {
- 170: switch (c = getc(fp))
- 171: {
- 172: case '\n':
- 173: fatalerr("Unterminated string");
- 174: return NULL;
- 175:
- 176: case EOF:
- 177: fatalerr("Unterminated string (line %d)",
- 178: lineno);
- 179: return NULL;
- 180:
- 181: case ' " ':
- 182: case '\":
- 183: if (c == quote_char)
- 184: {
- 185: tok[i] = '\0';
- 186: return tok;
- 187: }
- 188:
- 189: default:
- 190: if (i == MAX_CMD)
- 191: {
- 192: tok[i - 1] = '\0';
- 193: fatalerr("String too long (max %d chars)",
- 194: MAX_CMD);
- 195: return NULL;
- 196: }
- 197: tok[i] = c;
- 198: }
-
- 199: }
- 200: }
- 201:
- 202: tok[0] = c;
- 203: for (i = 1; (c = getc(fp)) != EOF; i++)
- 204: if (isspace(c) c == ';' c == ','
- 205: c == ':')
- 206: break;
- 207: else
- 208: tok[i] = tolower(c);
- 209: tok[i] = '\0';
- 210: ungetc(c, fp);
- 211: break;
- 212: }
- 213: return tok;
- 214: }
- 215:
- /* End of File */
-
-
- Listing 4
- 1: /*************************************************************
- 2: * Program: DMENU Menu Object File Analyzer
- 3: * Module: dmenu.c
- 4: * dmenu.c: Reads a menu object file, for debuging menu
- 5: * compiler
- 6: *
- 7: * Written by: Leor Zolman, 7/91
- 8: *
- 9: * Menu object file format:
- 10: *-----------------------------------------------------------
- 11: * <count> (integer count of # of menus in file)
- 12: * MENU 1 (MENU structure for 1st Menu)
- 13: * ITEM 1
- 14: * ITEM 2
- 15: * ...
- 16: * ITEM n_items
- 17: * MENU 2 (MENU structure for 2nd Menu)
- 18: * ...
- 19: * .
- 20: * .
- 21: * .
- 22: * MENU <count> (MENU structure for final Menu)
- 23: * ...
- 24: *
- 25: * ----------------------------------------------------------
- 26: * Compile:
- 27: * Xenix: cc dmenu.c -o dmenu
- 28: * DOS: bcc dmenu.c (Borland C++)
- 29: ************************************************************/
- 30:
- 31: #include <stdio.h>
- 32: #include "cmenu.h"
- 33:
- 34: #define OK 0
- 35:
- 36: MENU Menu;
- 37: ITEM Item, *ip = &Item;
- 38:
-
- 39: char obj_name[MAX_CMD];
- 40: int n_menus;
- 41:
- 42: int main(argc,argv)
- 43: int argc;
- 44: char **argv;
- 45: {
- 46: register i, j;
- 47: int count;
- 48: FILE *fp;
- 49:
- 50: if (argc != 2)
- 51: exit(puts("usage: dmenu <menu-object>\n"));
- 52:
- 53: strcpy(obj_name, argv[1]);
- 54: strcat(obj_name, ".mnc");
- 55:
- 56: if ((fp = fopen(obj_name, "rb")) == NULL)
- 57: {
- 58: fprintf(stderr,
- 59: "Cannot open %s for reading.\n", obj_name);
- 60: return ERROR;
- 61: }
- 62:
- 63: if (fread((Void *)&n_menus, sizeof n_menus, 1, fp) != 1)
- 64: {
- 65: fprintf(stderr,
- 66: "Error reading menu count from %s\n", obj_name);
- 67: return ERROR;
- 68: }
- 69: printf("Menu count = %d\n", n_menus);
- 70:
- 71: for (i = 0; i < n_menus; i++)
- 72: {
- 73: if (fread((Void *) &Menu, sizeof (MENU), 1, fp) != 1)
- 74: {
- 75: fprintf(stderr,
- 76: "Error reading from %s\n", obj_name);
- 77: return ERROR;
- 78: }
- 79:
- 80: printf("******** Start of Menu #%d: *********\n", i+1);
- 81: printf("\tTitle: %s\n", Menu.title);
- 82: printf("\tPath: %s\n", Menu.path);
- 83: printf("\tContains %d items.\n", Menu.nitems);
- 84: printf("\talign = %c\n", Menu.align);
- 85: printf("\tColumns = %d, ", Menu.columns);
- 86: printf("Spacing = %d, Widest = %d\n",
- 87: Menu.spacing, Menu.widest);
- 88: printf("\tShell escapes are %sallowed",
- 89: Menu.escape == YES ? "" : "NOT ");
- 90: printf("\n");
- 91:
- 92: for (j = 0; j < Menu.nitems; j++)
- 93: {
- 94: if (fread((Void *) ip, sizeof (ITEM), 1, fp) != 1)
- 95: {
- 96: fprintf(stderr,
- 97: "Error reading from %s\n", obj_name);
-
- 98: return ERROR;
- 99: }
- 100: printf("Item #%d:\n", j+1);
- 101: printf("\tTEXT = %s\n", ip->text);
- 102: printf("\tPATH = %s\n", ip->path);
- 103: printf("\tACTION = %s\n", ip->action);
- 104: printf("\tHELP = %s\n", ip->help);
- 105: printf("pre-clear = %c\n", ip->pre_clear);
- 106: printf("post-clear = %c\n", ip->post_clear);
- 107: printf("prompt = %c\n", ip->prompt);
- 108: printf("acttyp = %d\n", ip->acttyp);
- 109: printf("lmenunum = %d\n", ip->lmenunum);
- 110: printf("nextcode = %d\n", ip->nextcode);
- 111: printf("nextitem = %d\n", ip->nextitem);
- 112: printf("\n");
- 113: }
- 114: printf("**** END OF MENU #%d ****\n", i+1);
- 115: }
- 116: return OK;
- 117: }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- CUG New Releases
-
-
- GNU C++ And Spell Checker
-
-
-
-
- Kenji Hino
-
-
- Kenji Hino is a member of The C User's Group technical staff. He holds a
- B.S.C.S. from McPherson College and an undergraduate degree in metallurgy from
- a Japanese university. He enjoys playing drums in a reggae band.
-
-
-
-
- New Distribution Fees
-
-
- The order blanks in the center of this magazine reflect a new fee schedule for
- C Users Group Library Volumes. Under the new fee schedule, you will pay $4.00
- per disk (not volume). If the volume fits on one disk, it will cost only $4.
- If the volume spans three disks, it will cost $12. There is also a $3.50
- shipping and handling fee paid for each order.
- We've decided to make our fee schedule consistent with established practice in
- user-supported software. Our previous "one price per volume" fees have caused
- quite a bit of confusion. It's just too easy to compare the "per disk" rates
- of other sources with our "per volume" rate and conclude that we're gouging
- our customers. In fact, because so many of our volumes are multi-disk sets, we
- expect the average order to change very little. The major effect will be to
- decrease the price on orders which include several one-disk volumes and
- increase the price of orders which include several multi-disk volumes.
- Please examine the new order form and let us know if you have any questions or
- if you have suggestions for improving it.
-
-
- Update
-
-
-
-
- CUG350 PCX Graphics Library
-
-
- Ian Ashdown (CANADA) has updated his PCX Graphics Library, PCX_LIB. The new
- version is 1.00C and corrects the following bugs:
- 1. PCX_LIB didn't work properly when compiled under the 80x86 compact or large
- model and the data segments exceed 64KB in size.
- 2. The "usage" display of RD_DEMO and WR_DEMO didn't always display the full
- text.
-
-
- New Releases
-
-
-
-
- CUG359 GNU C/C++ for 386
-
-
- Written by Free Software Foundation, ported to DOS by DJ Delorie and submitted
- by Henri de Feraudi (FRANCE) and Mike Linderman (CANADA), this package
- contains a 32-bit 80386 DOS extender with symbolic debugger, a C/C++ compiler
- with utilities, development libraries, and source code. It generates full 32-
- bit programs and supports full virtual memory with paging to disk. The package
- requires a 80386-based IBM compatible PC or PS/2. The 80387 emulator currently
- does not emulate trancendental functions (exp, sin,.. etc). The software
- requires approximately 4-5 MB of hard drive space is required. 640KB RAM is
- required.
- The following hardware is supported:
- Up to 128MB of extended (not expanded) memory
- Up to 128MB of disk space used for swapping
- SuperVGA 256 color mode up to 1024x768
- 80387
- XMS & VDISK memory allocation strategies
- V86 programs, QEMM, 386MAX, DesqView, Windows/386 are not supported.
- The disk includes binary executable files: C/C++ compilers, LALR(1) parser
- (bison), lexical parser (flex), C/C++ preprocessor, 80386/80387 assembler,
- a.out (BSD) format linker (ld), archive utility, symbol stripper, compilation
- coodinator, basic 32-bit DOS extender, symbolic debugger, etc.
- In addition, libraries that support standard routines, math routines,
- graphics, and mouse routines (These are compiled with gcc. The source code is
- included in the disk.), include- header files, documentation, sources for
- extender and various VGA/SuperVGA drivers, diffs from FSF distributions to
- DOS-compatible, sources for the utilities, sample C++ sources using graphics
- and mouse, and 80387 emulator for non- 80386 systems.
- Due to the volume of files and DOS nature of programs, all files are archived
- by PKZIP (unzip utility is also included) and the archived file is separated
- into pieces by "split" utility. Thus, only MS-DOS disk format is distributed
- from us.
-
-
-
- CUG360 Uspell
-
-
- Bill McCullough (MO) has contributed a spell checker program, Uspell. Uspell
- is basically a modification of CUG217 Spell, however significantly optimized
- to improve the performance under UNIX. The optimization techniques Uspell uses
- include: replacing scanf with a single read, retaining the whole index in
- memory, converting input words to five-bit format before spell checking,
- reading the dictionary in increments of file system blocks caching locally,
- eliminating stdio functions, etc.
- The disk includes C source code for spell checker, ASCII text dictionary,
- compressed dictionary and index files, and a utility used to convert the ASCII
- text dictionary to compressed one.
-
-
- CUG Directory III
-
-
- The C Users' Group Directory III will be available the first of April. The
- directory has capsuled descriptions of CUG 250 through 299. The first
- (CUG100-199) and second (CUG 200- 249) volume of the directory are currently
- available through R&D Publications, Inc.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Editor's Forum
- I am in the midst of installing several new C and C++ compilers on my laptop.
- The process already has me talking to myself. The world has sure changed in
- the year I spent in Australia. (I bought little software then because it's a)
- scarce and b) expensive Down Under.) These new packages are big.
- I felt pretty smug a year ago buying a laptop with 6MB of RAM and 120MB of
- hard disk. I divvied up the former among RAM drive and various flavors of
- managed memory. I split the latter into six 20MB partitions, to provide a few
- fire walls. It was hard to imagine hitting limits with all that real estate.
- A large software package way back then ate up 3MB of disk. It might require
- over a megabyte of RAM to run well. Such parameters pale beside modern
- requirements, however.
- I'm not even talking about loading libraries for every possible memory model
- -- I usually stick with just small and large. No, it's all those Windows
- support libraries that eat disk space. And the gazillions of support tools we
- now take for granted. A 10MB package is typical these days. Some can be twice
- that size. If you're a child of the Nineties, let me give you some
- perspective. In early 1979, I was assured that few CP/M machines would ever
- have more than 32KB of RAM and 256KB of diskette storage. (You couldn't write
- enough assembly language to fill up the former. You couldn't afford two floppy
- drives to go beyond the latter.) Luckily, I ignored those projections and
- wrote C compilers for machines twice as large. The hardware was there when the
- software demanded it.
- My laptop now has 10MB of RAM. I use on-the-fly disk compression to double the
- effective hard disk size. I'm looking into networking my PCs so I can have
- faster file access to a really big disk.
- Now there's only one problem remaining. I'm not sure that I'm writing any more
- code with these wonderful new tools. Or any better code.
- P.J. Plauger
- pjp@plauger.uunet.uu.net
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New Products
-
-
- Industry-Related News & Announcements
-
-
-
-
- Enhanced Supernova Available
-
-
- Four Seasons Software has begun shipping SuperNOVA v2.2. This version
- introduces a business graphics feature for SuperNOVA v2.2. The business
- graphics feature allows developers to compile and display data from different
- databases in a variety of graphical formats including scanned images, graphs,
- bar and pie charts. This feature allows business application developers, MIS
- managers and even end-users to benefit from SuperNOVA's database independence,
- which allows data to be shared across most platforms and operating systems.
- One of SuperNOVA's capabilities is its ability to run in a distributed
- processing mode. Using an optional distributed processing capability,
- applications developed using SuperNOVA can run on a network consisting of
- various system types as well as different proprietary DBMS and operating
- systems. Running in the distributed mode, the complete network is available as
- one virtual computer system.
- SuperNOVA v2.2 enhances the distributed processing mode by adding an Optimized
- Packet Handling feature. Applications can now transparently access data and
- processing routines at high speeds between network nodes. The network can be
- comprised of VMS, UNIX, and MS/DOS machines, even MS/DOS systems with only
- 640KB of memory. SuperNOVA v2.2 supports access to Teradata databases along
- with access to a range of other commercial Data Base Management Systems
- (DBMS), such as Informix, Oracle, Ingres, and Sybase, as well as flat files.
- Utilizing SuperNOVA's fourth generation language, applications can
- simultaneously integrate data from more than one DBMS and can run on a variety
- of operating systems including DOS, UNIX and VMS.
- SuperNOVA v2.2 ranges in price from $995 to $115,000 depending on the hardware
- platform. Site licensing is available. For more information contact Four
- Seasons Software, 2025 Lincoln Highway, Edison, NJ 08817, (908) 248-6667, Fax
- (908) 248-6675.
-
-
- Borland Ships Three Object-Oriented Software Tools For Microsoft Windows
-
-
- Borland International, Inc. announced today the availability of three software
- development products that boost programmer productivity and accelerate the
- development and maintenance of software programs for Microsoft Windows and the
- DOS operating systems. The products which feature object-oriented C++, in
- addition to the C programming language, are: Turbo C++ for Windows 3.0,
- Borland C++ 3.0, and Borland C++ & Application Frameworks 3.0.
- Turbo C++ for Windows 3.0 offers an inexpensive route to Windows programming
- for entry level programmers. Major features include a graphical user
- interface, an object-oriented foundation that provides built-in objects to
- save developers time and coding, a library that enables DOS-based C and C++
- programs to run instantly under Windows, and Borland's resource editor, a tool
- for visually creating Windows resources such as icons, dialogs, fonts, and
- bitmap graphics without writing code. Borland's C++ v3.0 compiler now supports
- global optimization. Tools available in Borland C++ 3.0 include an
- object-oriented assembler; Turbo Debugger for tracking down bugs; Turbo
- Profiler for Windows and DOS for locating bottlenecks in programs; and
- WinSight, for monitoring Windows messages.
- Borland C++ & Application Frameworks 3.0 is the high-end product of Borland's
- C++ product line. It includes everything in Borland C++ 3.0, plus
- Object-Windows and Turbo Vision application frameworks, complete with source
- code for the frameworks and the runtime library. Borland's application
- frameworks define a standard user interface and behavior for Windows (using
- ObjectWindows) or DOS (using Turbo Vision). The application frameworks give
- programmers a head start toward creating applications by providing
- user-interface building blocks, fundamental data structures, and support for
- object-oriented programming.
- Turbo C++ for Windows 3.0 carries a suggested retail price of $149.95. Borland
- C++ 3.0 has suggested a retail price of $495 and Borland C++ & Application
- Frameworks 3.0 carries a suggested retail price of $749. All products are
- available now through major resellers or direct through Borland.
- Current users of Turbo C++ and Turbo C can upgrade to Turbo C++ for Windows
- 3.0 for $89.95. Turbo Pascal users can upgrade to Turbo c++ for Windows 3.0
- for $99.95. For owners of Borland C++ 2.0 and Turbo C Professional, it will be
- $125 to upgrade to Borland C++ 3.0 and $199.95 to upgrade to Borland C++ &
- Application Frameworks 3.0. Borland C++ & Application Frameworks 2.0 users
- will automatically receive Borland C++ & Application Frameworks 3.0 free of
- charge. All prices, including the upgrade and offers, are in U.S. dollars and
- apply only in the U.S. and Canada.
- For more information contact Borland, 1800 Green Hills Rd., P.O. Box 660001,
- Scotts Valley, CA 95067-0001, (408) 439-4825.
-
-
- Softaid Introduces 80186EB Emulator
-
-
- Softaid has released an In-Circuit Emulator for Intel's 80186EB
- macroprocessor. The UEM is composed of two tightly integrated parts: a full
- featured emulator, and Softaid's Source Level Debugger. The emulator hardware
- gives both EEs and firmware engineers the resources needed to debug an
- embedded system; the Source Level Debugger (SLD) wraps a shell around the
- emulator that gives the user full control over C and PL/M debugging. It
- includes 131,072 hardware breakpoints, each of which can be set on virtually
- any condition or machine cycle type. The user can nest breakpoint conditions
- up to five levels deep to find particularly devilish bugs.
- The UEM's real time trace collects machine cycles as the target program
- executes. The source level debugger shows trace data in the original source
- form, so the programmer can debug in the same context he wrote the code.
- The UEM does come with a real time performance analyzer. The SLD automatically
- couples this to the C code, assigning one function per performance bin (255
- maximum). A few seconds of work identifies time-critical routines that must be
- optimized. All UEM-Series emulators come with a memory access monitor that
- non-intrusively monitors all memory references. The user can specify areas of
- memory as being write protected, read and write protected, fetch protected
- (i.e., no fetches allowed), or open for any access. A breakpoint will occur if
- the mode is violated, immediately identifying code or pointers that "wander
- off." The 80186EB UEM costs $7500 and is available from stock. For more
- information contact Softaid, Inc., 8300 Guilford Road, Columbia, MD 21046,
- (410) 290-7760 or (800) 433-8812.
-
-
- Ethernet Starter Kit
-
-
- Crystal Computing Corporation has released an Ethernet starter kit, FreeLAN-E,
- which is suitable for small business or home office with 2 to 16 computers.
- With FreeLAN, the user can share printers/ plotters, share files, and run
- network version software at 10 Mbps speed. Starting from two users, FreeLAN
- can be easily upgraded to up to 256 users. And since it is peer-to-peer
- service, any machine on the FreeLAN network can share files or
- printers/plotters with any other users on the network. This Starter Kit
- package includes two 10 Mbps Ethernet cards, on e15' coax cable, two 50 ohm
- terminators, and Network Operating System for two users, featuring file
- sharing, file record locking, printer sharing/spooling, spool management and
- password protection. Furthermore, FreeLAN supports Novell Netware and Windows
- 3.0. It is NetBIOS based with NetBIOS included. FreeLAN supports IBM
- PC/XT/AT/386/486 and 100% compatibles, under MS-DOS 3.1 or later. FreeLAN
- starter kit is immediately available from Crystal Computing Corporation. The
- U.S. suggested retail price for FreeLAN-E, the Ethernet version, is $249. And
- the U.S. suggested list price for FreeLAN-A, the FreeLAN Arcnet starter kit,
- is $179.
- For more information contact Paul Chen, 3140 De La Cruz Blvd, Suite 200, Santa
- Clara, CA 95054, (408) 748-0685, (408) 748-0125, Fax (408) 748-0879.
-
-
- Embedded SQL for Microsoft Visual Basic
-
-
- Quadbase Systems Inc. has started shipping a new version of Quadbase-SQL/Win
- which includes a new library that allows Visual Basic programmers to use
- Embedded SQL to access the Quadbase's SQL engine under Microsoft Windows.
- Essentially, Embedded SQL allows interspersing of SQL and host programming
- language (in this case Visual Basic) statements. Called VBESQL, this new
- library supports ANSI standard Embedded-SQL syntax plus extensions such as
- scroll cursors and host variables of user-defined data types. An embedded SQL
- preprocessor for C is already included as a standard component of the product.
- Quadbase-SQL/Win is a full featured relational database engine in the form of
- a DLL that supports various Windows development languages such as Visual
- Basic, C, C++, Smalltalk/V Windows, ACTOR, SQLWindows etc. It fully supports
- ANSI SQL level 2 standard plus extensions offering advanced features such as
- referential integrity, outer join, multi-user concurrency control etc. The DLL
- can support multiple instances. It is very fast and compact. The underlying
- file formats are dBASE compatible. It is ideal for small to medium sized LAN
- file server systems or standalone or laptop machines. Users can benefit from
- advanced relational database features and be provided with migration path to
- SQL servers.
- The system requires an IBM or compatible 286, 386, or 486 PC with hard disk, a
- minimum of 2MB of RAM, and Windows 3.0. For more information contact Quadbase
- Systems, (408) 738-6989, Fax (408) 738-6980.
-
-
- Quality Assurance From Softran
-
-
- C-Verify is a quality assurance tool which proves that your test suite has
- taken every possible path through your C-language program.
- All source modules which are compiled into the executable program are passed
- through the C-Verify preprocessor. C-Verify analyzes the program's logic and
- generates an index file containing a unique label reference for each logic
- branch. C-Verify also outputs a new version of the source containing function
- calls which generate a file list of all the logic branches taken when the
- executable is run.
- The source output by C-Verify is compiled and run. As the program executes,
- the output file is built from the C-Verify label references for each logic
- branch taken during the run. Many different tests may be executed, passing the
- resulting logic probes into the output file for analysis.
-
- When the test suite is finished, C-Verify compares the file containing all
- possible logic branches with the file containing branches actually taken,
- identifying which logic has and has not been covered by the test suite. The
- programmer may then devise additional tests which pass through the neglected
- logic and run C-Verify again until 100% coverage has been attained. For more
- information contact Softran Corporation, One Naperville Plaza, Naperville, IL
- 60563, (800) 462-3932, Fax (708) 505-3457.
-
-
- C Development Environment
-
-
- Interactive Development Environments, Inc. (IDE) of San Francisco, California,
- has released an upgrade of the C Development Environment. IDE has added
- reverse engineering and code generation modules, which provide the capability
- to synchronize code and design. IDE also has improved facilities to query the
- shared repository and navigate easily though designs, code, and documentation.
- Using the C Development Environment, developers can reverse engineer existing
- C source code into designs in the form of specifications and graphical
- representations, or start with designs and generate C source code. The C
- Development Environment maintains consistency between designs and code by
- incrementally updating one to reflect changes in the other. This product
- significantly expands IDE's market, since it now offers solutions to
- development teams working on existing systems as well as to developers who
- work primarily at the source code level. For more information call IDE, 595
- Market St., 10th floor, San Francisco, CA 94105, (415) 543-0145, or
- 0483-579000 (U.K.), FAX (415) 543-0145.
-
-
- EMS Expanded Utility Library
-
-
- EMS Professional Shareware Libraries has released an expanded version of the
- dBUtility Library. Nearly 300 programs have been added to the library,
- bringing the total to 2000 different Public Domain and Shareware utilities
- designed specifically for Dbase language developers (including dBASE III/IV,
- FoxPRO, Clipper, QuickSilver, etc.) by over 1500 US and international firms.
- All files are compressed using PKZIP, but still fill 122 360KB or 31 1.44MB
- diskettes with over 110MB of material. The product includes a database index
- and search program for these and 900 additional commercial Dbase related
- utility programs, allowing searches by name, type, vendor, and free text
- searches. When programmers need to locate a particular type of Dbase utility
- or code routinne, they can find it without having to "reinvent the wheel". The
- library is $149 plus S/H. For further information contact EMS 4505 Buckhurst
- Ct., Olney MD 20832, (301) 924-3594, FAX (301) 963-2708.
-
-
- Teamone Systems Upgrades Teamnet
-
-
- TeamOne Systems, Inc. has released an upgrade of its TeamNet concurrent
- engineering environment for distributed configuration management. TeamNet 3.0
- includes a new X Window graphical interface that significantly enhances
- ease-of-use, and improved process control and conflict resolution mechanisms
- for concurrent development. The new release also includes major performance
- upgrades over TeamNet 2.0 for configuration management function, a floating
- license facility, and enhanced NFS client access.
- TeamNet is a UNIX-based configuration and data management system that supports
- software, electronic, and mechanical engineering projects. TeamNet provides
- configuration management and version control by transparently tracking product
- development using any tool that runs on any platform on an NFS-based network.
- New tools can be immediately integrated into the TeamNet environment with no
- need to modify or encapsulate tools prior to use.
- TeamNet 3.0 features an enhanced OPEN LOOK graphical interface, an enhanced
- conflict resolution process, and two-phase commit mechanism for checking in
- changes and managing work area and baseline updates. Network enhancements
- include virtual copy capability and a floating license system.
- In networked environments TeamNet licenses are priced at $3,000 and will
- support an average of two concurrent users per license. For more information
- contact TeamOne Systems, Inc., (408) 730-3500.
-
-
- Videotex Ships Upgraded Imaging Library
-
-
- Version 3 of T-BASE, the advanced picture and document imaging library from
- Videotex Systems, Inc., has been released. Bundled with T-BASE is ChromaTools,
- an advanced color manipulation and image conversion utility. ChromaTools is
- also from Videotex Systems, Inc. T-BASE allows developers to add pictures and
- document images to their database management applications written in C, C++
- and virtually every Xbase dialect. T-BASE supports any image in the PCX file
- format. Version 3 of T-BASE incorporates several new features never before
- available in an Xbase imaging library: Graphics @SAY and @GET commands,
- Automatic Image scaling, Image scaling on-the-fly, Support for 1024x768
- monitors with 256 colors, Automatic color correction for multiple VGA images
- with different palettes. Because it is 100% royalty-free, T-BASE is the most
- cost-effective imaging library on the market. Once you buy T-BASE, you are
- free to use T-BASE to develop and sell as many applications as you want.
- T-BASE is also hardware independent, so it does not limit you to certain types
- of video cards or other equipment. T-BASE automatically detects your hardware
- connfiguration and adjusts itself to work in that configuration. No other
- Xbase imaging library offers this level of hardware independence. ChromaTools,
- which is bundled with T-BASE, is an advanced color manipulation and image
- conversion utility that makes it easy to convert images in a variety of file
- formats into a single format for inclusion in desktop presentations, CD ROM
- libraries, picture databases, demo disks, bulletin boards, advertising, kiosks
- and more. ChromaTools is a $249 value. T-BASE has a suggested retail price of
- $495 and is available from dealers nationwide as well as from Videotex
- Systems, Inc. For more information contact Videotex Systems, Inc., 8499
- Greenville Ave., Suite 205, Dallas, Texas 75231, (800) 888-4336 or (214)
- 343-4500, FAX (214) 348-3821.
-
-
- High-Performance 80486-Based Eisa Single Board Computer
-
-
- Diversified Technology has introduced the ESP2000, an 80486 based EISA system
- processor. This processor is based on a passive backplane architecture and
- incorporates Intel's latest 82350DT chip set. Operating at 33MHz, the ESP2000
- utilizes a CPU-to-memory protocol which allows the memory subsection to
- operate independently of the CPU clock. This protocol also insures a smooth
- transition to 50MHz board options planned for future release.
- The ESP2000's high performance is due largely to the internal cache in the
- 80486. In addition to the internal cache, the ESP2000 provides a local memory
- architecture that consists of four-way interleaving and 128-bit "burst"
- accesses to provide near zero wait state performance. Up to 64MB of system
- memory is supported and this memory is field upgradable accepting 256KB,
- 512KB, 1MB and 2MB by 36 SIMM modules.
- The ESP2000 also provides support for an optional Intel Turbo cache module.
- Both 64KB and 128KB modules are supported. The architecture implemented is a
- "parallel" cache; thus, it resides in parallel with the CPU and local DRAM. As
- a result, local DRAM cycles can by initiated in parallel with cache TAGRAM
- lookup, precluding any penalty typically associated with a cache lookup.
- This single board computer's bus operates at the EISA standard and ISA
- backward compatible bus speed of 8.33MHZ. The EISA bus DMA channels can
- operate in both eight- and 16-bit modes with EISA/ISA bus slaves and masters,
- and 32 bit modes with EISA slaves and master. The interface of EISA masters to
- memory is optimized such that the memory is capable of sustaining the full 33
- megabytes per second bandwidth as defined in the EISA specification.
- Combined with one of Diversified Technologies' EBP series of EISA backplanes,
- the ESP2000 provides the EISA designer with a system capable of supporting six
- intelligent EISA bus masters simultaneously for demanding applications. To
- ensure compliance with FCC and Underwriters Laboratory regulations, the
- ESP2000 incorporates extensive design measures for EMI/RFI emissions control
- and U.L. safety requirements.
- The 33MHz EISA "486" ESP2000 without RAM is priced at $2,595 in single piece
- quantities. Allow 1 to 4 weeks delivery ARO. For further information contact
- Diversified Technology Inc., (800) 443-2667.
-
-
- Upgrade Of C Library
-
-
- Sequiter Software Inc., announced the release of CodeBase 4.5, a multi-user,
- database management library for C, C++, Visual Basic and Turbo Pascal for
- Windows. Programmers can now work directly with the data, index and memo files
- of dBASE, FoxPro, and Clipper from DOS or Microsoft Windows. The latest FoxPro
- 2.0 and dBASE IV index file formats are now supported.
- CodeBase 4.5 gives C/C++ programmers the ability to enhance the data entry
- capabilities of Microsoft Windows with entry validation and picture templates.
- CodeBase 4.5 database browse and edit windows can be designed using any
- resource toolkit. The CodeBase 4.5 library may also be used as a royalty free
- DLL.
- CodeBase 4.5, with complete multi-user source code and a 90 day money back
- guarantee, lists at US$395. For more information contact Sequiter Software
- Inc. #209, 9644-54 Ave., Edmonton, AB, Canada, T6E 5V1, (403) 437-2410, FAX
- (403) 436-2999.
-
-
- Object Professional For C++
-
-
- TurboPower Software announces Object Professional for C++, a library of
- textmode user interface objects. Object Professional for C++ (OPC) is a
- straight port of the user interface objects from Object Professional for Turbo
- Pascal.
- OPC includes high-level objects such as text editors, dialog boxes, scrolling
- data entry screens, help systems, and more. The user interfaces are
- ready-to-use, and built-in calls allow programmers to customize the behavior.
- OPC does not use event-driven programming. Applications built using OPC will
- run nicely in 640KB 8088-based PCs. OPC includes utilities to help build menu
- systems and data entry screens. The utilities support interactive design and
- testing and then automatically generate source code.
- OPC includes full source code, 1,300 pages of documentation, pop-up help, and
- plenty of example and demo programs. No payment of royalties is required. OPC
- requires Borland C++ 2.0 or 3.0. Object Professional for C++ costs $249. For
- futher information contact TurboPower Software, P.O. Box 49009, Colorado
- Springs, CO 80949-9009, (800) 333-4160, FAX (719) 260-7151.
-
-
- New Server For OS/2
-
-
-
- Laurel, MD-XDB Systems, Inc., has released an upgrade of their SQL Engine for
- OS/2, the XDB-SERVER. With the XDB-SERVER for OS/2 2.41, DB2 applications
- become portable to client-server platforms. Since the XDB-SERVER is network
- independent, DB2 compatibility can exist on multiple platforms and operating
- systems.
- Multiple physical disk volumes are now supported. Now developers can utilize
- multiple disk volumes to separate various data objects like indices and
- databases thereby decreasing the amount of time required to retrieve data. The
- size of a database is also no longer limited by the size of one OS/2 disk
- volume. In addition, the XDB-SERVER for OS/2 version 2.41 has enhanced its
- query optimization.
- XDB's SQL Engine provides the same mainframe quality implementation as DB2
- including referential integrity, record level locking, rollback and recovery,
- field level security, etc. The XDB-SERVER is built using the client-server
- architecture. The XDB SERVER for OS/2 supports both the Named Pipes and
- Netbios protocols.
- The price of the XDB-SERVER for OS/2 version 2.41 is $2495. For more
- information contact XDB Systems, Inc., 14700 Sweitzer Lane, Laurel, MD
- 20707-2921; (301) 317-6800.
-
-
- Database Application Generator
-
-
- Novato-based software developer Kedwell has just released a new version of its
- database application generator, DataBoss. Unlike database management systems
- such as dbase IV and Paradox, DataBoss doesn't have it's own language. To
- create applications you team it up with a Pascal, C, or C++ compiler. While
- this can make some things more complex, it also makes DataBoss very flexible.
- If DataBoss doesn't give you quite what you want you can add your own code or
- use library functions to add extra functionality. it also makes it very easty
- to port DataBoss applications to other platforms, and since you're delivering
- a compiled program you don't have to worry about run-time or licenses.
- DataBoss supports Borland's Turbo Pascal v5.0, v5.5, and v6.0; Borland C++;
- Turbo C v2.0, Turbo C++; and Microsoft's C compilers v5.1 and v6.0. It can
- also be used with Microsoft QuickC compilers but the integrations isn't as
- tight. DataBoss can be used to create mutliuser LAN-based applications as well
- as single-user applications. It supports Novell, 3Com, Tops, 10-Net and PC-MOS
- systems.
- DataBoss creates applications that use B-tree indexed files, which are
- reorganized whenever the database is updated so as to reduce data access
- times. Because it uses the B-tree indexes and because it is compiled, a
- DataBoss application is generally faster than a similar application developed
- using DBMS system.
- For more information contact Kedwell Software, 7460 Redwood Boulevard, Suite
- A, Novato, CA 94945, (415) 899-8525, FAX(415) 899-8524.
-
-
- Upgraded Library From Friendly Solutions
-
-
- The Friendly Solutions (TFS) Company has released their software package,
- C*DRIVE v1.1. C*DRIVE v1.1 has added features such as extensive validity
- checking for data entry in the 'GET' system, mouse capabilities incorporated
- into the menu system, virtual shadows for drawing boxes, complete mouse
- module, function for scrolling arrays, pop-up calculator, and pop-up calendar.
- The source code for whole library is included, rolyalty free. For further
- information contact The Friendly Solutions, 6309 Chimney Woods Ct, Alexandria,
- VA 22036, (703) 765-0654.
-
-
- Announcing Softcode Version 2.5
-
-
- Bottleworks Development Corporation has recently started shipping version 2.5
- of its template-driven code generator, SoftCode. SoftCode includes a
- menu-driven screen editor that allows developers to visually create customized
- field definitions and user interfaces. SoftCode's screen editor is designed to
- allow developers to create full working prototypes quickly and easily.
- Enhancements to the screen editor in version 2.5 include the ability to
- customize the screen editing environment and create windows; enhanced field
- validation, and the ability to select and use fields and screens from external
- programs.
- The template sets available for Soft-Code 2.5 have also been significantly
- enhanced. Templates are language-specific: there are separate templates for C,
- Basic, Pascal, dBASE IV, Clipper, FoxPro, Arago and Quicksilver/dBXL, each of
- which takes developers to generate complete multi-file applications including
- pop-up picklist menus, pop-up trigger windows and context-sensitive help. In
- addition, the Xbase-specific templates support scrollable, multi-record views.
- Each template includes on-line help and comes with source code which can be
- modified.
- SoftCode 2.5 retails for $295 and includes one template set and a 30-day
- guarantee. Additional templates retail for $149 each. For further information
- contact Bottleworks Development Co., 333 Hempstead Avenue, Suite 213,
- Malverne, N.Y 11565, (516) 596-9700, FAX (516) 596-9701.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- We Have Mail
- Dear Dr. Plauger,
- Do we get our money back for the repeated article on circular lists? Or is
- this a readership-attention-span-survey stunt?
- Regards,
- John Wheater
- Surrey, England
- You mean we didn't double your pleasure, double your fun?
- Seriously, we have received several such letters, and would like to say thank
- you all for the kindly pokes at our snafu. We promise to remove all the egg we
- are wearing because of it, so that we are more recognizable in the future. --
- dt
- Dear Mr. Plauger:
- You mentioned in The C Users Journal (October 1991) that you have the Turbo
- C++ function library but since it is a licensed product you would not pass it
- on or encourage anyone else to do so! I develop and market a C++ class library
- with source and I am so glad when one of the most influential person of the
- C/C++ community shows its support for honest practices. Thanks a thousand
- times.
- Patrick Nicolas
- Network Integrated Services, Inc.
- 221 West Dyer Road
- Santa Ana, CA 92707-3426
- I'm not completely altruistic. I've earned most of my money selling software
- that is easily pirated. I am counting on a certain amount of income from
- selling copies of the code from The Standard C Library. Still, I like to think
- that I'd encourage honesty even without a personal stake in the matter. -- pjp
- Dear Mr. Ward:
- Listing 2 in your article "Debugging Instrumentation Wrappers for Heap
- Functions" (CUJ, October 1991) caught my eye. I've had very similar problems
- regarding arithmetic on void pointers, indeed with any sort of manipulations
- using void pointers. One experience in particular dealt with the user-supplied
- comparison function expected by qsort, which takes two (void *) pointers as
- arguments.
- I needed a comparison between two struct objects. My solution was a kludge fix
- very similar to yours -- I defined two automatic pointer variables of the type
- I needed in the comparison function for qsort My compiler (Turbo C++) wouldn't
- allow me to do the comparison using the (void *) arguments to the comparison
- function directly, even with casting to (struct type *). I had to assign both
- arguments to the new pointers, then use them to do the comparison. Using
- something like strcmp as the comparison function, in contrast, presents no
- problem because strcmp takes two (void *) arguments in the first place.
- When studying your Listing 2, a solution to the kludge struck me. Simply
- redefine your my_free function and its redirection macro as
- void my_free( char * tree )
- #define free(x) my_free( (char *) x ),
- instead of using a (void *) pointer argument to my_free. You can then do
- pointer arithmetic directly on the (char *) argument passed to my_free. No
- explicit casting to (char *) is needed when calling free in source code since
- the redirection macro does this automatically. I suspect that something
- similar could be done with my_alloc to avoid the same kludge.
- I wouldn't be surprised if you've already thought of this one. Thanks for a
- great article, which indirectly helped to cast (pun not really intended) some
- light on my problem with . I'm not aware of any previous discussions in CUJ
- regarding this.
- Sincerely,
- Tom Nelson
- 5004 W. Mt. Hope Rd.
- Lansing, MI 48917
- Call it a kludge if you will. That's the sort of thing that X3J11 expected
- programmers to do when it changed the declaration of the comparison function
- for qsort. Honesty in type checking sometimes has its price. -- pjp
- Thanks for the feedback. I'm glad you found it useful. -- rlw
- Dear Sir:
- With reference to your call for papers on Numerical Applications, I would like
- to suggest two topics on which I have been working.
- In view of your inability to fill orders for your new book on the C library,
- articles which follow on those topics should prove interesting to your
- readers. I have followed up on the criticisms which I made of the sin and exp
- functions which appeared in CUJ, and found in fact that I was able to use the
- ideas published while fixing the deficiencies and get results better than I
- have seen elsewhere. I ordered the book and disk when they were first
- advertised. I spoke to your book department today and they say it will be more
- months before the book becomes available but they will try to ship the disk
- alone. In this case I propose to go through the math functions and show where
- they can be improved, and also where a slight variation on the code will
- improve performance without affecting accuracy on pipelined processors. I
- expect to have an HP720 available soon, in addition to SPARC, VAX, and 68000
- available now.
- In line with Ron Irvine's letter, I could write an article on how to
- specialize C so that standard compilers generate as efficient code as would be
- obtained by a FORTRAN compiler. This involves some uses of the const keyword
- which are mainly theoretical, as current compilers don't seem to be using it
- for optimization, as well as situations in which making a local copy of an
- argument which is accessed through a pointer will enable a compiler to perform
- the same optimizations which would be possible in FORTRAN. For recognition of
- common subexpressions, the same rules now are followed by several compilers,
- including gcc, Sun4, and IBM RS6000, so useful recommendations can be made to
- permit this optimization.
- Sincerely yours,
- Dr. Timothy C. Prince
- 452 Palmitas St
- Solana Beach CA 92075-2046
- Dr. Prince is one of the people I cited in The Standard C Library who
- demonstrably know more about math functions than I do. He has sent me several
- errata and suggestions for improvement, for which I am deeply grateful. I'm
- sure that CUJ will be publishing more articles where Dr. Prince illuminates
- the murkier corners of computer math.
- By the way, I believe that books and code disks are going out the door
- smoothly now. -- pjp
- Just for the record... We didn't publish the book. The delays have been the
- result of our difficulty in getting timely shipments from the publisher. --
- rlw
- Dear Sir:
- With reference to Ron Irvine's letter, I have written an article on the
- technical merits of C vs FORTRAN, but I don't know whether it will be
- published. It went out on Usenet in an earlier version, but I haven't found a
- way to access the net since I moved. This article expects the reader to be as
- familiar with FORTRAN as with C, and turned out to be fairly long. To
- summarize, the only situation of which I am aware in which ANSI C does not
- have the potential to equal the performance of FORTRAN is where a function has
- two or more in-out arguments.
- FORTRAN compilers can be implemented quite successfully by automatic
- translation to C, but the code required is not always a style which is natural
- to use in C. I have encountered codes which were written in FORTRAN with no
- intent of translation to C, which run faster when certain subroutines are
- translated automatically to C and compiled with gcc than when they are
- complied with a FORTRAN complier on a SPARC, because of gcc's better behavior
- when faced with a shortage of pointer registers. If programs were coded in C
- without using (intentionally or not) the full generality of the language,
- current compilers could generate equally fast code in C or FORTRAN. This is
- assuming that programmers and compilers take full advantage of ANSI C,
- including use of features such as the const qualifier; otherwise much C code
- cannot be optimized on a modular basis.
- The other side of this answer is that the C programmer needs more knowledge
- than the FORTRAN programmer, to avoid errors which FORTRAN does not permit, as
- well as to write optimizable code.
- Sincerely yours,
- Dr. Timothy C. Prince
- 452 Palmitas St.
- Solana Beach, CA 92075-2046
- C is often better than FORTRAN, but sometimes it is irretrievably worse. Both
- Cray Research and the Numerical C Extensions Group have educated me at length
- on this topic. Writers of Standard C compilers must catch up with three
- decades of determined numerical optimization in FORTRAN compilers. And C lets
- you do far more with pointers than FORTRAN permits with subscripts. Some
- optimizations are just not safe in C, at least not without additional semantic
- hints.
- Dr. Prince is again correct, however. The vast majority of FORTRAN programs
- (and programmers) would be better off in C. You must keep the numerical savvy
- of the FORTRAN community and adopt the coding style of C to profit completely
- from the transition. -- pjp
- Dear Mr. Plauger:
- I would like to present my own response to Ron Irvine, who asked (CUJ,
- November 1991) why FORTRAN is the preferred language for numerical
- applications.
- I am a multi-language programmer, and one-half of a team who currently writes
- and maintains a scientific analysis library in both C and FORTRAN. This
- library is compiled across many platforms (Vax, PC, MacIntosh, etc.). My great
- age and experience force me to speak out.
- I began serious scientific programming in FORTRAN in 1963, when I was asked to
- provide the surface temperature of Mars at the positions and times where the
- radio signal from a space probe grazed its surface during an occultation.
- Since that time I have programmed in many languages, and even taught a little
- programming to my undergraduate physics students.
- The power of FORTRAN lies in the numerous, robust mathematical function
- libraries intrinsic to all FORTRAN compilers. The limitations of FORTRAN are
- string handling and I/O.
- C will do scientific analysis as fast as FORTRAN. I have written the same
- scientific program in both languages. When compiled, you can't tell which is
- which. In FORTRAN, I struggle with string handling and I/O. In C, I struggle
- with data type conversion, and additionally must test for underflow, etc.
- "The tremendous power of C pointers" is part of the litany of C-chauvinism. I
- remind you that FORTRAN has always had pointers. Unfortunately, in the
- beginning (and I was there), it had nothing else. Everything was passed by
- pointer! C pointers have nothing to do with the issue. The issue is: FORTRAN
- intrinsic mathematical functions are more complete and more robust.
- Historically, scientific programmers began on mainframes without C compilers,
- and of necessity, have pushed the art of numerical programming further.
-
- I appreciate your efforts to bring C mathematical routines up to snuff. But
- your quest for "acceptance of C by the FORTRAN old guard"? I know the old
- guard. When C has enough intrinsic functions with the flexibility and
- stability of the FORTRAN functions, you could feed it to him, but only if you
- rename it FORTRAN.
- Very truly yours,
- Lin DeNoyer
- Spectrumm Square Associates
- 755 Snyder Hill
- Ithaca, NY 14850
- I too began programming Physics applications in FORTRAN back in 1963. I think
- you speak well for a significant fraction of the Old Guard, of which I was
- once a member. Those existing libraries are certainly important. So too are
- the millions of lines of existing FORTRAN applications. All that code can in
- principle be translated mechanically to Standard C. That should preserve its
- flexibility and stability.
- The price you pay sometimes is inferior performance. But that's not because
- you access arguments via pointers, as you rightly point out. At least not
- directly. The problem is that a C compiler can't optimize accesses via
- argument pointers nearly as aggressively as can a FORTRAN compiler. You have
- to watch what you do with a C pointer because you don't know where it's been.
- C permits all sorts of aliasing with pointers that is forbidden in simpler
- languages such as FORTRAN.
- Your final remark is the most telling. Many FORTRAN programmers will not
- switch to C simply out of habit. The new language is not sufficiently better,
- to them at least, for an old dog to want to learn new tricks. I can sympathize
- with that viewpoint. I feel the same way about C++ sometimes. -- pjp
- Dear Editor:
- Your magazine has always been helpful to me as a programmer, and perhaps you
- or one of your readers may be able to help me locate an algorithm to send
- voice through the IBM-PC's speaker.
- A common way to reproduce voice is to feed the AM signal into an 8-bit A to D
- converter, which is then sampled and the values stored in sequential order. To
- "play back" the values are sent to a D to A converter which reproduces the
- voice accurately enough to recognize a person's voice.
- Unfortunately, the PC's speaker is driven by digital logic circuitry and
- current is either on or off in the speaker coil. It is impossible, therefore,
- to send an AM signal to the PC's speaker. However, one can send a constant
- amplitude, variable width pulse train. The algorithm I need is: how does one
- convert a string of 8-bit numbers representing an AM signal into a list of
- number representing the variable width string of pulses the PC's speaker
- needs, with minimal loss of information?
- From what I have been able to gather, the AM waveform needs some high pass
- filtering and then is sent through a differentiator. The output should be a
- logical 0 if the differentiator's output is positive and 1 if negative. While
- it is easy to see how one could build such a system in hardware, I want to do
- it with software. Using the 8-bit code representing the AM information as
- input, how does one get the "differentiated" output? The speech type program
- found in shareware sources have not been of much help. Can someone point me in
- the right direction?
- Sincerely,
- Theron Wierenga
- P.O. Box 595
- Muskegon, MI 49443
- I haven't fiddled enough with digital filtering to be much help here. Anybody?
- -- pjp
- See "Writing for the PC Speaker" by Robert Bybee in Windows/DOS Developer's
- Journal, December, 1991. Call our Customer Relations department for back
- issues and reprints. -- dt
- Ladies and Gentlemen:
- I wanted to comment on a couple of things I have noted in the past few months
- in letters to the magazine. First, I would really hate to see your source code
- distribution system handled through CompuServe. Rather than bore you with gory
- details, I'll just say that I once subscribed to CompuServe. While I agree
- that the current system has its drawbacks, I believe it is infinitely
- preferable to CompuServe.
- Concerning the letters from those seeking the path to enlightenment (i.e.,
- learning C): the best thing I ever did was read a good book on compiler
- construction. I first read K&R, and achieved a certain amount of literacy in
- the language (I wrote programs that worked), but many times I wasn't sure just
- why things worked. In C, a language on intimate terms with its supporting
- hardware, such ignorance is cumbersome at best and dangerous at worst.
- However, I bought a used copy of Aho & Weinberger, and suddenly the
- "incantations" took on new clarity. My approach to pointers had always been
- just above the level of mysticism, but after learning how a compiler would
- generate code to declare a pointer, the mystery was gone. Studying compiler
- construction might seem like a big bite for a beginner, but I think that after
- one has achieved technical facility with the language, it's not too large a
- step. It helped me a lot more than any "teach yourself C" book I ever saw.
- Lastly, I wish to toss in my opinion regarding the C/C++ controversy, i.e.,
- whether you should lean more toward C++. I have a C++ compiler (Borland C++),
- I have read a number of books and articles on it, and I have had the
- opportunity to discuss C++ at length with a friend who recently took a course
- in it at the University of Washington. While I can envision reasons why
- someone would wish to use C++, those reasons do not apply to me -- nor, I
- believe, to a large number of programmers and designers. C++ seems to be more
- an idiom than anything else (that from my friend), and as has been stated in
- articles in CUJ, it is not the only way in which to obtain source code
- reusability. Further, I for one am not convinced that "object oriented
- programming" is the be-all and end-all which many of its proponents seem to
- claim. To my (possibly ignorant) way of thinking, C++ is a new flavor, not a
- new food.
- Having said all that, I would like to see you remain a magazine devoted to C
- programming. If you do ever decide to become The C++ Users Journal, I will
- probably find another magazine which addresses my favorite language as well as
- you do now. I like C. It works for me.
- Finally, regarding that guy who responded to my earlier letter in which I
- requested a nude centerfold: I guess some people have nothing better to do
- than to pick at nits. Can we code him a sense of humor in C, or would it take
- C++?
- Very truly yours,
- Ian S. King 520
- SW Yamhill Street #430
- Portland, OR 97204
- I too learn languages best by seeing how they are implemented. I'm not sure
- that approach works for everybody, but if it works for you too, go for it. As
- for C++, it seems that sanity is beginning to prevail. More and more people
- are finding good uses for C++ and other OO languages, but the religious zeal
- of the newly converted is dying out.
- I can think of several clever remarks about nude centerfolds, the need for a
- sense of humor, and the relative merits of C vs. C++ in this context. Robert
- will probably be happier if I print none of them here. -- pjp
- Maybe so, but I'm glad I got to read King's remark -- rlw
- Dear Mr. Plauger,
- I have a question that I was hoping you might be able to help with. I'm using
- VAX C with the curses library running on a VAX3100, I've created a selection
- menu, using curses, to execute other programs.
- The problem I'm having is, the getch function, available with the curses
- library, doesn't send the character you enter until you've also hit return. On
- an IBM PC, getch will get a single character from the keyboard, and send it to
- your program without having to hit the return key.
- Is there another way of doing this, on the VAX using C, without having to call
- other language routines? There is a function available in VAX Basic called
- INKEY which looks like "INKEY$ (0%, WAIT)". There is also SMG$ routines
- available on the VAX to get unsolicited keyboard input, but if I user either,
- porting to other systems will be a pain.
- Sincerely,
- James Cook
- 13213-66B Ave.
- Surrey, B.C.
- V3W-8P4
- The issue you face is common to many systems, not just VMS. (If you haven't
- hit it on the PC, you've just been lucky -- after a fashion.) The problem is
- that most of the time you want keyboard input to be "cooked" a line at a time.
- You want the typist to backspace over typos, even retype the whole line, until
- it is correct. That means the input cooker must wait until the typist strikes
- the return key before the program can see the first character on a line.
- Nearly every system I've ever encountered has some way to request "raw" input.
- The program gets each character as it's typed, with no line editing performed.
- Sounds like your PC getch gives raw input whether you ask or not, for good or
- for ill. The problem is, no standard way exists to specify raw input in C.
- Your best bet is to write your code in terms of a function with a name such as
- getraw. You'll have to tailor the function for each system, but at least it
- hides the irregularity. -- pjp
- Dear Mr. Plauger:
- I enjoyed reading Mr. Rothkin's article entitled "PC UART Device Driver" in
- the December 1991 issue of The C Users Journal. I have been programming the
- serial port for over eight years and have developed my own routines which will
- be modified based on information from this article. One thing he mentioned in
- his article that he says would be difficult to handle would be the proper
- handling of IRQs nine through fifteen. One method I have used is to have two
- entry points to the same routine, one for one interrupt vector, and a second
- for the other interrupt vector. You simply indicate which entry point was used
- and proceed from there. Then, when the interrupt routine has completed, it can
- check to see which entry point was used and respond to the appropriate 8259
- interrupt controller. Of course there are other ways to handle the situation,
- but this has been the way I have chosen for my device drivers.
- Thank you for this and other articles and keep up the good work.
- Very Truly Yours,
- Peter R. Vermilye
- The Programmers' Guild, Inc.
- 1833 New Riverdale Rd.
- Germantown, TN 38138
- Thanks. -- pjp
- Dear Sirs;
- I need your help! I use Borland C++ for a compiler, Btrieve or Paradox engine
- for a RDBMS, but I don't have any interface tools. I am searching for a C
- and/or C++ user interface package. I would like something that is
- comprehensive, yet easy to use. I want it to have a WYSIWYG designer for
- menus/screens/forms and to generate program stubs (option).
- I have demos of Greenleafs DataWindows, Island System's graphicsMenu,
- Softway's Hi-Screen Pro II, and Vermont Creative Software's Vermont Views with
- designer. These packages represent the price range I am working with. Running
- the demos only confuses me more; I like features in each.
- Do you have any recommendations for a C programmer looking to give his
- programs a quality interface? I will appreciate any advice you can give me.
- Thank you.
- Sincerely,
-
- Jeffrey S. Dreke
- 15709 North Lund Road
- Eden Prairie, MN 55346-1550
- Sorry, that's another area (of many) where I'm weak. Anybody? -- pjp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Porting Command Line User Interfaces to GUIs
-
-
- William Smith
-
-
- William Smith is the engineering manager at Montana Software, a software
- development company specializing in custom applications for MS-DOS and
- Windows. You may contact him by mail at P.O Box 663, Bozeman, MT 59771-0663.
-
-
- Not very long ago the computer industry's direction concerning operating
- systems and user interfaces was uncertain and confusing. You had to be
- psychic, lucky, or just plain good at reading the writing on the wall to
- assess the direction the industry was going. To name just a few of the
- possibilities, there was UNIX and X-Window, OS/2 and Presentation Manager,
- MS-DOS and a primitive Windows 2.0, and MS-DOS and countless lesser known
- third party user interface libraries. Right at the height of this confusion, I
- was faced with choosing an operating system and user interface for a software
- development project. I was developing a data acquisition and data management
- system for an automated athletic weight training system. The customer also
- wanted the program to have a modern graphical user interface (GUI).
- For economic reasons and availability of development tools, I decided to write
- the software in C for a target 80386 personal computer running MS-DOS. The
- user interface decision was much harder. I, like many people when faced with a
- tough decision, decided not to decide. I made an engineering decision to do
- the user interface last. This allowed me to postpone the commitment to a
- specific user interface library. This was contrary to many opinions about
- designing software at the time. Instead of designing the software from the
- user interface and screen level first, I designed the software by identifying
- core functionality and operations independent of the user interface. To get
- the project going, I specified that the software was to be first developed
- using a simple command line interface (CLI). Later, when a graphical user
- interface library was chosen, I would port the program to work with the GUI
- library. As soon as a clear direction in the software industry emerged, I
- would decide upon what GUI to use. Much to my delight and the satisfaction of
- the customer, this approach eventually paid off.
- Well as most of you know, Windows 3.0 came out and quickly became a success.
- It became obvious what user interface to use. Eventually the project was over
- and the customer ended up with both a command line version and after a
- successful port, a Windows version of the software. Along the way I learned a
- lot about the approach involved in making this port easy. I am going to share
- with you some of what I learned about porting command line interfaces to GUI's
- in general and Windows in particular.
- The concept of porting I am going to discuss requires work. It is not a simple
- recompile under a different environment. Granted, there are some features
- built into Microsoft QuickC for Windows and version 3.0 of Borland C++ that
- allow you to recompile your code under Windows with no changes. The approach
- is easy, but all it gets you is a command line within a window. I do not
- consider this a true GUI. To get true GUI behavior in your program you are
- going to have to write some code and make some changes. GUIs are here to stay
- and will become increasingly popular in the future. The effort to port your
- code to a GUI is worth it. With proper planning, it does not have to be that
- painful either.
-
-
- CLI Versus GUI
-
-
- With the popularity of GUIs, CLIs may seem antiquated, but they have their
- place. They are easy to write and if you stick to the standard C library, very
- portable. You can generate a test program for exercising new code quicker
- using a CLI then a GUI. CLIs are fast and can be easy to use. All that the
- program requires of the user is to type in the program name and some options
- at the operating system prompt. The command line interface is elegant in its
- simplicity and appreciated by the skilled software user. CLIs are also very
- adaptable to batch mode processing. Unfortunately, CLIs are cryptic and
- require the user to have knowledge and memory of the command line syntax. By
- providing a help screen defining proper usage, you can relieve this challenge
- somewhat for the inexperienced user. An easy to use CLI program should provide
- a provision to invoke this help screen when an h or ? option is passed on the
- command line. The program should also display the help information when there
- is an error in the command line syntax.
- GUIs are visually appealing and easier to negotiate then CLIs especially for
- the unskilled user. GUIs can require more steps to accomplish the same task
- then a CLI and are not as conducive to batch mode processing as CLIs. With
- some effort, you can add key stroke short cuts and batch ability to GUIs to
- satisfy the demands of the advanced user.
- Table 1 contains a list of user interface characteristics and features. It
- rates CLIs and GUIs for comparison.
-
-
- CLI Translated to GUI
-
-
- Programs in the simplest terms require input, perform some task and generate
- output. The input information is in the form of instructions and data. The
- output information consists of results and data. With a command line interface
- your choices of how to communicate instructions to a program are limited.
- Typically, CLIs use single characters, sometimes preceded by a delimiting
- character such as /, to specify options, commands, or flags. The user passes
- data to CLIs in the form of file names or lists of strings. On the other hand
- there are many more options available on how to communicate information to a
- program that employs a GUI. Table 2, lists the basic command line interface
- elements and corresponding GUI elements. The nomenclature is based on Windows.
- Notice there is not a one-to-one correspondence between a command line element
- and a GUI element. With a GUI, there can be many different ways to accomplish
- the same task. This gives the developer some flexibility in designing the
- interface.
- Listing 1 contains a code fragment from the main function of a program that
- processes a command line and performs database operations. This is an excerpt
- from a data base utility program that I first created as a CLI program and
- later ported to Windows. The command line is simple. To specify an operation,
- the program requires a single character as the first argument on the command
- line. The second and third arguments are a file name and a key name. The
- operation chosen determines which of these last two arguments are required.
- The command line syntax is
- PROGRAM OPERATION FILE KEY
- The possible operations are shown in Table 3.
- Listing 2 contains an excerpt from a Windows program. This code is taken from
- a program that accomplishes the same tasks as the program that contains
- Listing 1 . The code is from the windows procedure for the main window that
- responds to messages for the main window. The code fragment contains a switch
- statement that reacts to menu choices that are in the form of messages
- communicated from Windows. There is correspondence between the command line
- options and the menu choices. The user specifies the file and key through
- interaction with dialog boxes. Notice that the calls to the functions,
- add_data_to_db, del_data_from_db, get_data_from_db, replace_data_in_db,
- vrfy_data_in_db are the same for both the CLI version and the GUI version. The
- code for these function should be portable across user interfaces.
- Table 4 lists the CLI arguments and the corresponding GUI elements used to
- accomplish the same operations.
-
-
- GUI Portability Strategy
-
-
- There are three major guidelines that form the foundation of a strategy for
- portability between user interfaces:
- 1. Identify high level functionality and data structures
- 2. Isolate program functionality from user interface code
- 3. Use standard library and standard types
- Planning for user interface portability requires adopting a design philosophy
- of first identifying high level program functionality and avoiding the
- specifics of screen design. The idea is to isolate the major data structures
- and operations that the program supports. If you can wrap a command line
- interface around the operations that your program performs, you are on the
- right track. Granted some programs such as word processors do not lend
- themselves to command line interfaces. Even in this situation, you can isolate
- individual functions a word processor performs and group them in a utility
- program with a command line interface.
- Once you define the major functionality, make sure you strongly segregate the
- code you write to implement this functionality from the user interface code.
- The modules that contain the core operations of your program should be
- portable and not make any function calls to a user interface library.
- To help with portability, use the standard library functions and the standard
- types. Some of the issues encountered when porting among GUIs and operating
- systems are sizes of standard types, structure packing, and alignment. The
- size of some of the standard types will change from one platform to another.
- An example is the default int type. Under some compilers an int is 16 bits
- while with others it is 32 bits. If you do not care what size it is and want
- to use the native most efficient size, use just a plain int. If you only need
- 16 bits and want to conserve space in a platform where int is 32 bits use
- short int. If you need 32 bits even in a platform where int is 16 bits use
- long int. Avoid typedefing int and encoding the size in the type such as int16
- or int32. Some claim that this increases portability, but I have found the
- exact opposite to be true. I also recommend using the size_t type defined in
- standard C. size_t is defined as an unsigned integer. It is convenient to use
- variables of type size_t as array indexes.
- Related to type size is structure packing and alignment. In most situations,
- compilers align structure members (except chars) on boundaries that correspond
- to the most efficient type size. On a 16-bit system, structure members are
- aligned on word boundaries. On a 32-bit system, structure members are aligned
- on double word boundaries. Some compilers allow the program to control
- structure alignment.
- Try to avoid dependencies in your code on type size and structure alignment.
- The sizeof operator can help with types and the offsetof macro can help with
- structures. Pay careful attention to third party libraries if you use them.
- They may have size and structure alignment dependencies that could bite you
- later.
- Buffer sizes and string lengths should be set using manifest constants. For
- example, the maximum length of a string to hold a file name may change from
- one system to another. It is far easier to change the definition of a manifest
- constant in one place than find all places where space for a string is
- allocated.
-
-
- Porting to Windows -- the Gruesome Details
-
-
- Since Windows is such a popular GUI, it is worth talking about some of the
- specific issues encountered when porting existing C code to this environment.
- As a first step in porting your CLI program to the Windows GUI, you may want
- to create a user interface shell and spawn the CLI version of your program
- using the Windows WinExec function call. WinExec is similar to the spawn
- function family in standard C. This approach will get you up and running, but
- I found it unacceptable for a finished product. The major drawback is the lack
- of and the difficulty involved in communicating between MS-DOS and Windows
- programs.
- The next step is to replace the CLI interface and compile your code as a
- Windows application. Unfortunately, even if you prepared ahead for portability
- there are some problems that may surface.
-
-
-
- Types and Structure Alignment
-
-
- I have already mentioned data types and structure packing. They are especially
- important issues under Windows. Windows, in its present incarnation, is a
- 16-bit environment and the default int type is 16 bits, but Windows requires
- structures to be aligned on eight-bit (byte) boundaries. If your code expects
- structures to be aligned on 16-bit (word) boundaries this may affect you. I
- ran into alignment problems with a third party database library. The situation
- forced me into hand padding my structures so members greater than a single
- byte in size were aligned on word boundaries. I inserted eight-bit padding
- members of type char after an odd number of single byte members.
- The Windows programing environment contains many new types defined in the
- include file, WINDOWS.H. I recommend you use these types, but confine their
- usage to the user interface portions of your code.
-
-
- Memory Models
-
-
- Since Windows runs under MS-DOS and is subject to the caveats of the Intel
- segmented architecture, you will have to deal with near and far pointer issues
- and memory models. Since I wanted as much of my code to be as standard C-like
- as possible, I tried to avoid sprinkling my code with the keywords near and
- far that are not standard C keywords. The general wisdom on Windows claims
- that programs compiled using the small or medium memory model behave better
- under Windows than those compiled with the large or compact memory models.
- Using the small or medium memory model forces you to declare pointers with the
- far keyword if they happen to be in far heap space. Even though using the
- large and compact memory models is discouraged, many of the Windows library
- functions also require far pointers as parameters. The only way to get far
- pointers without adding the far keyword to every declaration is to use the
- large or compact memory model. Windows does not like programs compiled under
- these memory models because they may contain multiple data segments. Windows
- fixes multiple data segments in memory. This situation prevents Windows from
- running more than one instance of such programs and may cause inefficiencies
- in Windows memory management. This is the case with Microsoft C, but not
- always with Borland C++. Yes, that is right, the two compilers have a slightly
- different implementation of the large and compact memory models. Microsoft C
- creates multiple data segments when using the large or compact memory model
- and you have little control over the outcome. Borland C++ creates a single
- data segment unless you specifically tell it to create more. You can run
- multiple instances of a program compiled under Borland C++ using the large or
- compact memory model. The exact program compiled with Microsoft C using the
- large memory model will run as a single instance only.
- I have tried the large memory model under Windows and did not notice any
- performance problems with Windows in standard or enhanced mode. I never even
- tried real mode. Since Windows 3.1 eliminates real mode, I probably never will
- use real mode. If you need pointers to far data, your choices are to use the
- large memory model or to use mixed model programming by declaring pointers
- with the far keyword.
-
-
- Dynamic Memory
-
-
- Windows does support the malloc family of standard C library memory management
- functions. Unfortunately, they may have slightly different behavior then what
- you are use to. Depending on the memory model, malloc may allocate memory in
- the near heap. If you want to force allocation in the far heap independent of
- memory model, you will have to use the function _fmalloc. Since Windows maps
- _fmalloc to the Windows function GlobalAlloc, there is a limitation on how
- many times you can call _fmalloc. Every time you call GlobalAlloc, Windows
- uses a segment selector. There is a finite number of segment selectors
- available. This happens to be 8192 for all of Windows - not just your
- application. If your program requires the allocation of a lot of small pieces
- of memory, you can quickly run out of selectors even if you still have lots of
- free memory. The solution to this problem is to call GlobalAlloc sparingly and
- use subsegment allocation. This means you will have to write your own memory
- manager or buy one of the third party libraries on the market. Version 3.0 of
- Borland C++ supports subsegment memory allocation and eliminates this problem.
- I expect eventually all compilers that support Windows will support this
- feature.
-
-
- WINSTUB.EXE
-
-
- Windows allows a non-Windows program to be bound to your Windows program. You
- specify the stub program in the linker definition file. MS-DOS executes the
- stub program when you invoke the program from the MS-DOS prompt. I took
- advantage of this feature to bind the command line or MS-DOS version of a
- program to the GUI or Windows version of the same program. The only problem I
- encountered with this was that Borland C++ enforced a 64KB maximum size
- limitation on the stub program. Microsoft C allowed the stub program to be any
- size.
-
-
- UAEs and New Bugs under Windows
-
-
- When I first compiled my program under Windows as a Windows application, I was
- extremely disappointed when it would not run without generating UAEs
- (Unrecoverable Application Errors). Upon tracking down the offending lines of
- code, a pattern started to emerge. The majority of the UAEs where caused by
- dereferencing null pointers. This was occurring in the code I had written and
- also in the standard library code that I was passing null pointers to as
- parameters. Since the program worked fine under MS-DOS, there was some
- argument among co-workers about whether these were actual bugs. One of my
- partners claimed that functions such as strcmp should be able to handle a null
- pointer parameter. Since I could not find any reference that specified how
- some of the standard library functions responded to null pointers as
- parameters, I decided to be conservative on this issue and actually modified
- the program's code to avoid passing null pointers to functions where the
- behavior was not defined and caused UAEs. I recommend that you be careful
- about dereferencing null pointers and passing null pointers to standard
- library functions where the behavior is not specifically defined by the
- standard or defined in the function description that comes with your
- compiler's documentation.
-
-
- Conclusions
-
-
- Ease in portability between user interfaces requires planning. A decision to
- first develop an application with a command line interface and then port it to
- Windows made me deal head on with GUI portability problems. What I eventually
- ended up with is a program where most of the code will port to any GUI without
- rewriting it.
- Planning for portability requires you to identify the needed operations and
- the high level data elements independent of any user interface issues. You
- should isolate user interface specific code from the core program code. You
- should be able to access the functionality of your program through a simple
- command line interface. This can be handy for testing. For a CLI, the main
- function should do nothing but process the command line and make the requested
- function calls. These same function calls can then be called in a similar way
- when responding to messages or events in a program with a GUI. The GUI program
- will have to be more than just a simple main function module and may require
- many new modules that support the GUI functionality and screens. The goal is
- to have the business end of the code that does the data crunching and
- calculating remain unchanged when porting from one user interface to another.
- Table 1 User Interface Issues, CLI Versus GUI
- Characteristics/Features CLI GUI
- ---------------------------------------------
- Ease of Development Good Poor
- Portability Good Poor
- Batch Processing Good Poor
- Ease of Use Poor Good
- Aesthetics and Presentation Poor Good
- Visibility of Information Poor Good
- Flexibility of Program Operation Poor Good
- Number of Interaction Steps Few Many
- Table 2 CLI Elements and Analogous GUI Elements
- CLI Element Corresponding GUI Elements
- ----------------------------------------------------
- Option/Action Flag Menu Item, Radio Button,
- Check Box
- String Unlimited Choices Edit Box, Check Box
- String Limited Choices Menu Item, Radio Button,
- List Box
- File Name Edit Box, List Box,
- Dialog Box
- Script File Editor, Dialog Box,
-
- Custom Builder
- Table 3
- Operation Description
- ---------------------------------------------------------------
- A add data contained in FILE to database
- D delete data specified by KEY from database
- G get data specified by KEY from database and
- store in FILE
- L list all the keys in the database to standard output
- R replace data in database specified by KEY
- with data in FILE
- V verify data in database specified by KEY with
- data in FILE
- Table 4 CLI Arguments and Corresponding GUI Elements
- CLI Arguments GUI Elements
- ---------------------------------------
- Option Options (Menu)
- A Add...
- D Delete...
- G Get...
- L
- R Replace...
- V Verify...
- File name File Select Dialog Box
- Key name Key Select Dialog Box
-
- Listing 1
- switch ( argv[1][0] )
- {
- case 'a':
- case 'A':
- status = add_data_to_db( argv[2] );
- break;
- case 'd':
- case 'D':
- status = del_data_from_db( argv[2] );
- break;
- case 'g':
- case 'G':
- status = get_data_from_db( argv[2], argv[3] );
- break;
- case 'l';
- case 'L';
- status = list_keys_in_db();
- break;
- case 'r';
- case 'R';
- status = replace_data_in_db( argv[2], argv[3] );
- break;
- case 'v':
- case 'V':
- status = vrfy_data_in_db( argv[2], argv[3] );
- break;
- default:
- status = FAIL;
- break;
- } /*switch ( argv[1][0] )*/
-
- /* End of File */
-
-
-
- Listing 2
- switch ( wParam )
- {
- case IDM_ADD:
- /* Select a File */
- status = FileSelectDialog( hInstance, hWnd,
- Caption, FileSpec );
- if ( status == FAIL status == FALSE )
- break; /* Canceled the operation. */
- /* Add file to database */
- status = add_data_to_db ( FileSpec );
- break:
- case IDM_DELETE:
- /* Select a Key */
- status = KeySelectDialog ( hInstance, hWnd,
- Caption, Key );
- if ( status == FAIL status == FALSE )
- break; /* Canceled the operation. */
- /* Delete Keyed Data from database */
- status = del_data_from_db( Key );
- break;
- case IDM_GET:
- /* Select a Key */
- KeySelectDialog( hInstance, hWnd,
- Caption, Key );
- if ( status == FAIL status == FALSE )
- break; /* Canceled the operation. */
- /* Select a File */
- FileSelectDialog( hInstance, hWnd,
- Caption, FileSpec );
- if ( status == FAIL status == FALSE)
- break; /* Canceled the operation. */
- /* Get data from database and store in File */
- status = get_data_from_db( Key, FileSpec );
- break;
- case IDM_REPLACE:
- /* Select a Key */
- KeySelectDialog ( hInstance, hWnd,
- Caption, Key );
- if ( status == FAIL status == FALSE )
- break; /* Canceled the operation. */
- /* Select a data file */
- FileSelectDialog( hInstance, hWnd,
- Caption, FileSpec );
- if ( status == FAIL status == FALSE )
- break; /* Canceled the operation. */
- /* Replace Key data with data in FileSpec */
- status= replace_data_in_db ( Key, FileSpec );
- break;
- case IDM_VERIFY:
- /* Select a Key */
- KeySelectDialog( hInstance, hWnd,
- Caption, Key );
- if ( status == FAIL status == FALSE )
- break; /* Canceled the operation. */
- /* Select a File */
- FileSelectDialog( hInstance, hWnd,
-
- Caption, FileSpec );
- if ( status == FAIL status == FALSE )
- break; /* Canceled the operation. */
- /* Verify Data in database (Key) matches
- ** data in FileSpec */
- status = vrfy_data_in_db( Key, FileSpec );
- break:
- default:
- status = FAIL;
- break;
- } /* switch (wParam) */
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- A Versatile Menu Program for Turbo C
-
-
- Roger T. Stevens
-
-
- Dr. Roger T. Stevens is a member of the technical staff of the MITRE
- Corporation, Bedford, MA. He holds a B. A. degree in English from Union
- College, an M A in Mathematics from Boston University, an M. Eng. in Systems
- Engineering from Virginia Tech, and a PhD. in Electrical Engineering from
- California Western University. Dr. Stevens' books Graphics Programming in C,
- Fractal Programming in C, and Fractal Programming in Turbo Pascal are
- published by M & T Publishing, Inc., 501 Galveston Dr., Redwood City, CA
- 94063.
-
-
-
-
- Introduction
-
-
- Although there are menu programs available commercially and as shareware, most
- of them provide only the capability to display and select from a list of menu
- items, and do not permit interaction with the display. However, interaction
- with the display is often essential for efficient operation of a program. Look
- at the sample display of Figure 1. The two bottom lines are provided by the
- menu function. They give the user the choice of the actions: DISPLAY DATA,
- CHANGE DATA, ADD DATA, or QUIT. All you can do with most menu functions is to
- select one these actions. However, for the first two of these actions, we want
- to interact with the display, by selecting a name from the list for the action
- to be performed upon. The menu function described below will automatically
- switch mode after an action is selected. For the first two actions, the new
- mode can permit scanning the list of names with the up and down cursor arrows,
- highlighting each in turn with a contrasting color combination. The user then
- hits ESC to activate the selected function. The cursor is only permitted to go
- to the first letter of each name in the list.
- Now look at the display of Figure 2. This is a new user generated display
- which provides detailed data on the person selected from the first display.
- Again the menu function is activated; this time it just displays two lines of
- instructions at the bottom of the screen. This is the display that you see if
- you chose the action CHANGE DATA, from the first display. Those places on the
- screen where you are allowed to change the data are actually shown on this
- display in a different color from the rest of the display and the cursor is
- restricted to only these positions. After you have modified the data in any
- way you desire, hitting ESC returns to the main program. The user can then
- arrange to read any data changes from the screen into his data files. Note
- that as long as you are within the menu function, you have the capability to
- move the cursor to any permissible location and change or rechange the data
- there. If at the first screen, you selected the DISPLAY DATA action, the
- bottom explanatory lines simply say Hit any key to continue... and no
- modification of display data is permitted.
- For the ADD DATA action, no selection from the list of names is permitted;
- instead the display immediately switches to that of Figure 2, but with the
- data areas blank, ready to receive data on a new person.
- All of the menu and display interaction is controlled by a versatile menu
- function which provides a number of different modes of operation through
- passed parameters. This menu function is described in the text that follows,
- and the code is in Listing 1. The listing also includes a number of useful
- functions needed for program operation.
-
-
- What You Can Do with the Menu Program
-
-
- The menu program begins by displaying two adjacent lines of instructions or
- menu items on the screen. You can select the location of the first of these
- lines by setting a parameter called first_line_loc, which is passed to the
- menu function. The first line normally consists of general instructions; the
- second line, which is the next line after the first line, can be a list of
- menu items from which the user makes a selection, or can be another set of
- general instructions, depending upon the mode of operation of the menu
- program.
- You can specify the color combination for the menu items and the color
- combination which is used to highlight the currently selected menu item. The
- rest of the screen display remains as you generated it before calling the menu
- function. A menu item is selected by use of the cursor arrows; when selection
- is complete, this phase of the menu program is terminated by hitting the Enter
- key. For any number of menu items, beginning with the leftmost, you can
- specify an alternate (screen) mode of operation, which is entered after the
- Enter key is hit. You can specify each permissible cursor position for this
- alternate mode, and the cursor will only be allowed to go to these selected
- positions. (For example, if the down arrow is hit, the menu function will look
- at the current column and the next line and move the cursor there if it is a
- permissible position. If that position is not permissible, the function will
- look for the nearest permissible position on that line; if there is none, it
- will look at the next line, and so forth until a permissible location is
- found.) You can also specify two lines of text which will appear on the menu
- lines when the alternate mode is entered. You can specify whether this
- alternate mode of operation will be a select or an enter text type of
- operation. If selection is chosen, the text from the cursor to the next
- occurrence of two adjacent spaces is highlighted. Usually for this type of
- operation, the only allowable cursor positions are at the beginning of each
- selectable item on the display, so the entire selected item is highlighted.
- If you specified the enter text type of operation, the user may enter
- alphanumeric data in any permissible cursor location. You may specify the
- color combination which this entered data will have. When data entry is
- complete, the same escape character specified above is used to terminate the
- screen mode of operation.
-
-
- Determining Permissible Cursor Positions
-
-
- The heart of the menu program is the capability to specify which positions on
- the screen the cursor is permitted to occupy. When in the selection mode,
- there should only be one permissile cursor position for each item to be
- selected. That is usually the first character of the item description. When
- changing or entering data on the screen, a file structure is usually
- determined, which specifies the names of the data items for each file entry
- and the length of each of these items. A display location and length is
- established for the display of each item, and the cursor is only allowed to go
- to the allocated space for each item. These areas of the screen can then be
- read to obtain the modified data. By prohibiting the cursor from going to
- other areas, it becomes impossible for the user to overwrite item definitions
- or to enter data that is too long to fit into the file.
- The permissible cursor positions are controlled in the menu program by use of
- a bit map, which contains one bit for each character position on the screen.
- The bit map consists of a character array of 25 by l0 characters. The 25 is
- for the 25 screen lines; the 10 is for ten bytes of eight bits each to provide
- for the 80 character positions on a line.
- If a particular bit is one, the cursor is permitted to go to that location; if
- it is a zero, the cursor is prohibited from going there. The bit map for a
- particular menu may be generated dynamically or it may be generated manually
- by determining what bits need to be set and where they are located in the
- array. Since this latter process can be rather cumbersome, a utility function,
- set_cursor, has been provided to do the job automatically. You temporarily
- insert set_cursor into your program, just after the display has been
- generated. You can now move the cursor around the display and insert an x or X
- at every position where the cursor is to be allowed. You should also replace
- any x or Xs that occur naturally in the display in locations prohibited to the
- cursor with some other character. Make sure not to hit the Enter key until you
- have inserted all of the required xs in the display.
- When you hit Enter the program reads the entire screen and generates a file
- called MATRIX.C, which contains all of the ASCII data needed for initializing
- a cursor map array in your program. You can set up the bit map array by
- inserting the following line in your program:
- char nnnnnnnn[25][10] =
- where nnnnnnnn is the name of your bit map. If you are using the Turbo C total
- environment editor, place the cursor after the equals sign and type ^KR. When
- asked for the file name, type in MATRIX.C. The required data for the bit map
- will then be inserted into your program. You can then remove set_cursor from
- your listing and recompile the program. When you run the menu function, you
- will find that the cursor will only go to those positions which you marked
- with an x or X when you used set_cursor to construct the bit map.
-
-
- Key Designations
-
-
- Normal keys on the IBM PC keyboard generate the standard ASCII representation
- of the selected letter or number. Most special keys, such as the cursor arrow
- keys and the F1 through F10 function keys return two characters, first a hex 0
- and then some number from 1 to 255. To put all key returns into a common
- format, the menu program automatically reads a second character from the
- keyboard when the first character is 0 and adds 256 to this second character
- to obtain a unique number that will not duplicate one of the normal ASCII
- codes. This key input is stored in a variable called key_id. Thus, when
- looking at the program listing, some of the comparisons of key_id with various
- numbers may appear unfamiliar. They can be identified by taking any chart of
- keyboard codes and adding 256 to the second character generated by a
- particular key. The menu program has the flexibility of specifying which key
- will be used to escape from the screen type of operation. This capability will
- be described in furthur detail below. You should note, however, that whatever
- character you select for escape cannot be entered as data on the screen.
-
-
- Menu Options
-
-
- The programmer has almost unlimited flexibility in defining how the menu
- program is to be used. The menu options are selected by parameters passed
- through the menu function.
- The first parameter passed to the menu function is a string called values
- which defines the characteristics of the menu line. It begins with two digits
- which define the number of menu function items. Four digits then follow for
- each menu item. The first two represent the column of the display at which
- that menu item begins. The next two represent the number of characters in the
- menu item. These four digit entries must correspond to the actual spacing of
- the entries in the menu line string described below.
- The next parameter, called menu_first_line, is a string showing the first line
- of instructions when the program is showing the menu type display. Following
- that is a string called menu_second_line, which is the second line of
- instructions for the menu type (the actual set of menu function items). Next
- are two strings called screen_first_line and screen_second_line, which are the
- first and second lines of instructions when the menu program switches to the
- second type of operation.
- The next parameter, called menu_type, selects the mode of operation. There are
- four modes of operation for the menu function, which are controlled by this
- parameter. They are assigned by one of the numbers zero to three. The modes of
- operation are:
- 0 = When the menu program is called, it will start with the menu display. When
- it is in the screen display type of operation, all alphanumerics will be
- ignored.
- 1 = When the menu program is called, it will start with the menu display. When
- it is in the screen display type of operation, all alphanumerics will be
- displayed where typed.
- 2 = When the menu program is called, it will start with the screen display.
- When it is in the screen display type of operation, all alphanumerics will be
- ignored.
- 3 = When the menu program is called, it will start with the screen display.
- When it is in the screen display type of operation, all alphanumerics will be
- displayed where typed.
- The next parameter, screen_color, is the screen color. This has no effect upon
- the display that has already been generated, but does determine what color
- combination will be used for alphanumerics typed on the screen and to restore
- the background for that part of the two instruction lines that is not used.
- Normally it should be the same color combination used in generating the
- original display screen. Table 1 shows the color combinations represented by
- each number. The next parameter, called menu_color, is the color combination
- used by the two lines of instructions produced by the menu. The next
- parameter, called highlight_color, is the color combination used to highlight
- the selected menu item.
-
- The next parameter determines how many menu functions will switch to the
- screen type of operation when selected. Those functions which switch to the
- screen type of operation must be grouped at the beginning of the menu line,
- since the count in this variable begins with the leftmost function.
- The next parameter, called escape_char, is the representation of the key which
- must be hit to escape from the screen type of operation. It is a number which
- is the ASCII value produced by a regular key or the value plus 256 if the
- keyboard output is a two character output beginning with 0. Thus any keyboard
- output may be selected. When this key is hit, it will immediately cause an
- exit from the screen mode of operation. The next parameter, called
- first_line_loc, determines the line on which the first of the two menu lines
- begins. It may be any screen line from 0 to 23. Normally the menu lines should
- be either at the top or bottom of the screen.
- The final parameter is the address of the bit map, which determines which are
- the permissible positions of the cursor. The bit map has already been
- described above.
-
-
- Supporting Functions
-
-
- The menu function uses several supporting functions. These include
- clearscreen, which clears the screen by filling it with spaces of a designated
- color; gotoxy, which positions the cursor at a desired column and row;
- putcolorchar, which displays a character at the cursor location with a
- specified color combination and moves the cursor to the next column;
- color_printf, which acts like the standard C printf function except that it
- displays its data with a selected color combinations; and change_line_color,
- which changes the color of a line of characters from the current cursor
- position up until a double space is encountered. Listings of these functions
- are shown for completeness. Many of them may already be available in standard
- libraries, but they are not included with the current version of Turbo C. If
- you are adapting the menu funciton for a monochrome display, standard
- monochrome equivalent functions may be used in place of some of these
- functions.
-
-
- Conclusions
-
-
- The menu function provides a great deal of flexibility in manipulating data
- and making menu selections. About the only restriction is that all menu
- function item names must fit on one line. Colors, instructions, titles, order
- of mode, cursor settings, and whether or not alphanumerics are to be displayed
- are all under the control of the programmer through the manner in which he
- calls the menu function.
- Figure 1 Name List Display
- List of Names
-
- John S. Jones
- Arthur E. Smith
- William S. Thompson
- Thomas F. Doughty
- Edgar Snow
- J. Theophilus Johnson
- Peter T. Timkins
-
-
- Select desired menu function with cursor arrows - then hit 'Enter'
- DISPLAY DATA CHANGE DATA ADD DATA QUIT
- Figure 2 Display of Detailed Individual Data
- Name: John S. Jones
- Address: 132 Main St.
- City: Rutland State: VT Zip: 01023
-
- Phone: (802) 555-6432
-
-
- Change data as required:
- Then hit 'Esc'.
- Table 1 Color Table
- BACKGROUND Black Blue Green Cyan Red Magenta Brown Light Gray
- FOREGROUND
- ------------------------------------------------------------------------
- Bright White 15 31 47 63 79 95 111 127
- Yellow 14 30 46 62 78 94 110 126
- Light Magenta 13 29 45 61 77 93 109 125
- Light Red 12 28 44 60 76 92 108 124
- Light Cyan 11 27 43 59 75 91 107 123
- Light Green 10 26 42 58 74 90 106 122
- Light Blue 9 25 41 57 73 89 105 121
- Dark Gray 8 24 40 56 72 88 104 120
- Light Gray 7 23 39 55 71 87 103 119
- Brown 6 22 38 54 70 86 102 118
- Magenta 5 21 37 53 69 85 101 117
- Red 4 20 36 52 68 84 100 116
- Cyan 3 19 35 51 67 83 99 115
- Green 2 18 34 50 66 82 98 114
- Blue 1 17 33 49 65 81 97 113
- Black 0 16 32 48 64 80 96 112
-
-
- Listing 1
- /*
- menu = controls cursor movement and menu display and selection -
- returns the number of the selected menu item.
-
- values: a string consisting of two digits showing
- the number of menu choices, followed by
- four digits for each choice, the first
- two showing the starting column of the
- menu item and the second two its length.
-
- menu_first_line: the first line of instructions in the
- menu mode.
-
- menu_second_line: the second line in the menu mode. It
- contains the menu choices.
-
- screen_first_line: the first line of instructions in the
- screen mode.
-
- screen_second_line: the second line of instructions in the
- screen mode.
-
- menu_type: 0: Start with Menu Mode.
- Alphanumeric characters are ignored
- when entered in screen mode.
- 1: Start with Menu Mode.
- Alphanumeric characters are displayed
- when entered in screen mode
- 2: Start with Screen Mode.
- Alphanumeric characters are ignored
- when entered in screen mode.
- 3: Start with Screen Mode.
- Alphanumeric characters are displayed
- when entered in screen mode.
-
- screen_color: color for screen mode.
- menu_color: color for menu display.
-
- highlight_color: color of selected menu item
-
- select_no: number of menu items using selection
-
- escape_char: number of the key selected for escape
- from the screen display.
-
- map: bit map of permitted cursor locations.
- The cursor will go to permitted locations
- only and no others.
- */
-
- int menu(char values[],char menu_first_line[],char menu_second_line[],
- char screen_first_line[],char screen_second_line[],int menu_type,
- int screen_color,int menu_color,int highlight_color, int select_no,
- int escape_char, int first_line_loc, char map[25][10])
- {
- union REGS reg;
- int i,choices,indx,start,length,menu_second_line_length;
-
- int interim,remainder,temp;
- char spaces[80],prev_char;
-
- for (i=0; i<76; i++)
- spaces[i] = ' ';
- spaces[76] = '\0';
- menu_second_line_length = strlen(menu_second_line);
- gotoxy(2,first_line_loc);
- choice = 1;
- if (menu_type <= 1)
- {
- color_printf("%s",menu_color,menu_first_line);
- gotoxy(2,first_line_loc+1);
- length = values[4] - '0';
- length = 10 * length + values[5] - '0';
-
- for (indx = 0; indx < menu_second_line_length; indx++)
- if (indx < length)
- putcolorchar(menu_second_line[indx],
- highlight_color);
- else
- putcolorchar(menu_second_line[indx],
- menu_color);
- }
- else
- {
- color_printf(screen_first_line,menu_color);
- gotoxy(2,first_line_loc+1);
- color_printf(screen_second_line,menu_color);
- }
- choices = 10 * (values[O] - '0') + values[1] -'0';
- gotoxy(column,row);
- for(;;)
- {
- key_id = getch();
- if (key_id == 0)
- key_id = getch()+256;
- if (menu_type <= 1)
- {
- switch(key_id)
- {
- case 13:
- if(choice > select_no)
- goto ExitPoint;
- change_line_color(47);
- gotoxy(2,23);
- color_printf("%s",screen_color,spaces);
- gotoxy(2,23);
- color_printf("%s",menu_color,
- screen first_line);
- gotoxy(2,24);
- color_printf("%s",screen_color,spaces);
- gotoxy(2,24);
- color_printf("%s",menu_color,
- screen_second_line);
- gotoxy(column,row);
- menu_type +=2;
- break;
- case 333: /*Right Arrow*/
-
- choice = choice + 2;
- case 331: /*Left Arrowb*/
- --choice;
- if (choice < 1)
- choice = choices;
- if (choice > choices)
- choice = 1;
- start = 10 * (values[(choice-1)*4+2] - '0')
- +values[(choice-1)*4+3] - '0';
- length = 10 * (values[(choice-1)*4+4] - '0')
- +values[(choice-1)*4+5] - '0';
- gotoxy(2,24);
- for (indx = 0;indx < menu_second_line_length;
- indx++)
- {
- if ((indx >= start) && (indx < start
- + length))
- putcolorchar
- (menu_second_line
- [indx],
- highlight_color);
- else
- putcolorchar
- (menu_second_line
- [indx],
- menu_color);
- }
- gotoxy (column, row);
- break;
- default:
- if ((key_id >= 0x41) && (key_id <= 0x7A))
- {
- temp = toupper(key_id);
- for (indx=0; indx<=choices; indx++)
- {
- start = 10 * (values[
- (indx-1)*4+2] -
- '0')+values[(indx
- -1)*4+3] - '0';
- if (temp == menu_second_line
- [start])
- {
- choice = indx;
- if(choice >
- select_no)
- goto ExitPoint;
- change_line_color
- (47);
- gotoxy(2,23);
- color_printf("%s",
- screen_color,spaces);
- gotoxy(2,23);
- color_printf("%s",
- menu_color,
- screen_first_line);
- gotoxy(2,24);
- color_printf("%s",
- screen_color,spaces);
- gotoxy(2,24);
-
- color_printf("%s",
- menu_color,
- screen_second_line);
- gotoxy(column,row);
- menu_type += 2;
- }
- }
- }
- }
- }
- else
- {
- if (key_id == escape_char)
- goto ExitPoint;
- switch(key_id)
- {
- case 8: /*Backspace*/
- case 331: /*Left Arrow*/
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (screen_color);
- do
- {
- column--;
- if (column < 0)
- {
- column = 79;
- row --;
- if (row < 0)
- row = 24;
- }
- indx = column/8;
- remainder = column - indx*8;
- interim = map[row] [indx] &
- (0x01 << remainder);
- }
- while (interim == 0x00);
- gotoxy (column,row );
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (highlight_color);
- break;
- case 333: / *Right Arrow*/
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (screen_color);
- do
- {
- column++;
- if (column > 79)
- {
- column = 0;
- row ++;
- if (row > 24)
- row = 0;
- }
-
- indx = column/8;
- remainder = column - indx*8;
- interim = map[row] [indx] &
- (0x01 << remainder);
- }
- while (interim == 0x00);
- gotoxy(column,row);
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (highlight_color);
- break;
- case 13:
- column = 0;
- case 336: /*Down Arrow*/
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (screen_color);
- row++;
- if (row > 24)
- row = 0;
- indx = column/8;
- remainder = column - indx * 8;
- interim = map[row][indx] & (0x01 <<
- remainder);
- if (interim !=0x00)
- {
- gotoxy(column,row);
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (highlight_color);
- break;
- }
- column = 0;
- do
- {
- indx = column/8;
- remainder = column - indx*8;
- interim = map[row][indx] &
- (0x01 << remainder);
- if (interim != 0x00)
- {
- gotoxy(column,row);
- if ((menu_type == 0)
- ((menu_type == 2))
- change_line_color
- (highlight_color);
- break;
- }
- column++;
- if (column > 79)
- {
- column = 0;
- row ++;
- if (row > 24)
- row = 0;
- }
-
- }
- while (interim == 0x00);
- break;
- case 328: /*Up Arrow*/
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (screen_color);
- row--;
- if (row < 0)
- row = 24;
- indx = column/8;
- remainder = column - indx * 8;
- interim = map[row][indx] & (0x01
- << remainder);
- if (interim != 0x00)
- {
- gotoxy(column,row);
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (highlight_color);
- break;
- }
- column = 79;
- do
- {
- indx = column/8;
- remainder = column - indx*8;
- interim = map[row][indx] &
- (0x01 << remainder);
- if (interim != 0x00)
- {
- gotoxy(column,row);
- if ((menu_type == 0)
- (menu_type == 2))
- change_line_color
- (highlight_color);
- break;
- }
- column--;
- if (column < 0)
- {
- column = 79;
- row --;
- if (row < 0)
- row = 24;
- }
- }
- while (interim == 0x00);
- break;
- default:
- if ((menu_type == 1)
- (menu_type == 3))
- {
- putcolorchar(key_id,
- screen_color);
- do
- {
-
- column++;
- if (column > 79)
- {
- column = 0;
- row ++;
- if (row > 24)
- row =
- 0;
- }
- indx = column/8;
- remainder = column -
- indx * 8;
- interim = map[row]
- [indx] & (0x01
- << remainder);
- }
- while (interim == 0x00);
- gotoxy(column,row);
- }
- }
- }
- }
- ExitPoint:
- return choice;
- }
-
- /*
- change_line color = changes the color of a line up to a double space
- */
-
- void change_line_color(int color)
- {
- union REGS rin;
- char prev_char;
-
- prev_char = 's';
- for( ; ; )
- {
- rin.h.ah = 3;
- rin.h.bh = 0;
- int86(0x10,&rin,&rin);
- ch = read_char_from_screen();
- if((ch == ' ') && (prev_char == ' '))
- break;
- prev_char = ch;
- gotoxy(rin.h.dl,rin.h.dh);
- putcolorchar(ch,color);
- }
- gotoxy(column,row);
- }
-
- /*
- clearscreen = clears the screen and displays selected color background
- */
-
- void clearscreen(int color)
- {
- int indx;
- union REGS reg;
-
-
- gotoxy(0,0);
- reg.h.ah = 9;
- reg.h.al = 0x20;
- reg.h.bh = 0;
- reg.h.bl = color;
- reg.x.cx = 2000;
- int86(0x10,®,®);
- }
-
- /*
- color_printf = printf with selected foreground and background colors
- */
-
- void color_printf (char *msg,int color,...)
- {
- union REGS reg;
- char ch, string[2000];
- int i = 0;
- va_list (ap);
-
- va_start (ap,msg);
- vsprintf(string,msg,ap); /*do printf to string*/
- va_end(ap);
- while ((ch=string[i++]) != '\0') /*get chars from string till end*/
- {
- if (ch == 0x0A) /*is character a line feed*/
- {
- reg.h.ah = 3;
-
- int86(0x10,®,®);
- /*get cursor position*/
- reg.h.d1 = 0;
- reg,h.dh++;
- /*cursor value to beginning of next line*/
- reg.h.ah = 2;
- int86(0x10,®,®);
- /*set new cursor position*/
- }
- else
- {
- reg.h.ah = 9;
- reg.h.al = ch;
- reg.x.bx = color;
- reg.x.cx = 1;
- int86(0x10,®,®);
- /*send a color character to display*/
- reg.h.ah = 3;
- int86(0x10,®,®);
- /*get cursor position in D reg*/
- reg.x.dx++;
- reg.h.ah = 2;
- /*increment cursor position value*/
- int86(0x10,®,®);
- /*set cursor to new position*/
- }
- }
- }
-
-
- /*
- gotoxy = moves cursor to selected column and row
- */
-
- void gotoxy(int col, int row)
- {
- union REGS reg;
- reg.h.ah = 2;
- reg.h.bh = 0;
- reg.x.dx = (row << 8) +col;
- int86(0X10,®, ®);
- }
-
- /*
- putcolorchar = displays a character with selected color foreground
- and background
- */
-
- void putcolorchar(char character, int color)
- {
- union REGS reg;
- reg.h.ah = 3;
- reg.h.bh = 0;
- int86(0x10,®,®);
- reg.h.ah = 9;
- reg.h.al = character;
- reg.h.bl = color;
- reg.x.cx = 1;
- int86(0x10,®,®);
- reg.h.ah =2;
- reg.h.dl = reg.h.dl+1;
- int86(0x10,®,®);
- }
-
- /*
- read_char_from_screen = reads a character from the screen into 'ch'
- */
-
- char read_char_from_screen()
- {
- char ch;
- union REGS reg;
-
- reg.h.ah = 3;
- reg.h.bh = 0;
- int86(0x10,®,®);
- reg.h.ah. = 8;
- int86(0x10,®,®);
- ch = reg.h.al;
- attr = reg.h.ah;
- reg.h.ah. = 2;
- reg.h.dl = reg.h.dl+1;
- int86(0x10,®,®);
- return ch;
- }
-
- /*
- set_cursor = sets up array of permissible cursor positions
- */
-
-
- void set_cursor()
- {
- int i,j,indx,remainder,key_value;
- char interim,map[25][10];
-
- FILE *f1;
- f1 = fopen("matrix.c","w");
-
- for(i=0;i<=24;i++)
- {
- for(j=0;j<=9++)
- {
- map[i][j]=0x00;
- }
- }
- row=0;
- column = 0;
- gotoxy(column,row);
- while((key_value = getch()) != 13)
- {
- if (key_value == 0)
- key_value = getch()+128;
- switch(key_value)
- {
- case 8: /*Backspace*/
- case 203: /*Left Arrow*/
- --column;
- break;
- case 205: /*Right Arrow*/
- ++column;
- break;
- case 208: /*Down Arrow*/
- ++row;
- break;
- case 200: /*Up Arrow*/
- --row;
- break;
- default:
- putch(key_value);
- column++;
- }
- if (column > 79)
- {
- column = 0;
- row++;
- }
- if (column = < 0>
- {
- column = 0;
- row--;
- }
- if (row < 0)
- row = 0;
- gotoxy(column,row);
- }
- row=0;
- column=0;
- while (row*column < 1896)
-
- {
- gotoxy(column,row);
- ch = read_char_from_screen();
- if ((ch == 'x') (ch == 'X'))
- {
- indx = column/8;
- remainder = column - indx *8;
- interim = 0x01;
- interim = interim << remainder;
- map[row][indx] = map[row][indx] interim;
- }
- column++;
- if (column > 79)
- {
- column = 0;
- row++;
- }
- gotoxy(column,row);
- }
-
- clearscreen(30);
- fputc('{',f1);
- gotoxy(0,0);
- for (i=0;i<=24;i++)
- {
- for (j=0;j<=9;j++)
- {
- fprintf(f1,"0x%x",map[i][j]);
- if ((i != 24) (j != 9))
- fputc(',',f1);
- }
- fprintf(f1,"\n");
- }
- fputc('}',f1);
- fclose(f1);
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Multiple Copy Math Functions
-
-
- Timothy Prince
-
-
- Timothy Prince has a B.A. in physics from Harvard and a Ph.D. in mechanical
- engineering from the University of Cincinnati. He has 25 years of experience
- in aerodynamic design and computation. He can be contacted at 452 Palmitas
- St., Solana Beach, CA 92075.
-
-
- Pipelined architectures including vector and superscalar obtain their speed by
- depending in part on working on independent calculations in pipeline fashion.
- Most computationally intensive applications present enough opportunities for
- parallel or pipeline operation to make worthwhile increases in speed. The
- normal use of scalar math functions, which produce a single result, poses an
- obstacle to superscalar performance. These functions can be organized to
- increase the internal opportunities for parallelism as compared with optimum
- scalar processor code. Performance remains far short of the potential, unless
- more parallelism is exploited by working on more than one math function result
- at a time. A further reason for obtaining multiple results is that the
- overhead for calling functions which take less than 10 microseconds becomes
- excessive.
- Soon after the introduction of vector computers, vector math functions were
- introduced to provide vector performance in calculations involving such
- functions. With pipelined or superscalar processors, vector functions may be
- effective, but functions which calculate a small number of copies per call are
- more versatile. Since a typical RISC architecture employs a four-stage
- pipeline, functions which calculate two or four copies should be enough to
- maximize performance.
- Vector chunk math functions may be used whether or not your compiler makes
- specific provision for them. I will show actual examples of the C code of such
- functions. The functions sin_4 (four sins), cosf_sinf_2 (two pairs of float
- sin and cos), powf_2 (two float pows), and tan_2 (two tans) are chosen for
- their proven usefulness and because they illustrate the points which I want to
- make.
-
-
- Vector vs Superscalar Function Calls
-
-
- On a vector architecture, vector math functions naturally are performed on
- argument vectors, and normal vector performance is not approached except on
- long vectors. These architectures perform well when 50 or more functions are
- to be calculated at a time. Suppose we wanted to integrate a function
- involving sin and cos by Simpson's rule, producing a loop such as
- for(i=2 ; i<n ; i += 2){
- yint += q[i-2]*sin(t[i-2])
- +4*q[i-1]*sin(t[i-1])+q[i]*sin(t[i]);
- xint += q[i-2]*cos(t[i-2])
- +4*q[i-1]*cos(t[i-1])+q[i]*cos(t[i]);
- }
- which involves n evaluations of sin and cos. A vector compiler would start out
- by setting up the six vectors made up of the three sin and cos evaluations
- from each copy of the loop body. Almost a third of these evaluations would be
- duplicates, since the vector of sin(t[i-2]) is the same as the vector of
- sin(t[i]) except that sin(t[0]) and sin(t[n-1]) are not repeated. Each of
- these vectors has length (n+1)/2, so n would have to be around 100 before good
- vector performance could be achieved.
- In order to approach the performance potential of a vector architecture, we
- would have to rewrite the code to store the q[]*sin() and q[]*cos()
- intermediate results in vectors in a preliminary loop, and then add the
- appropriate values in another loop. Even after 20 years of vector compiler
- development, this is more analysis than any compiler can do without human
- assistance. Most reasonable attempts to improve the performance of this loop
- for a scalar architecture will prevent vectorization, and changes to improve
- vector performance will reduce scalar or superscalar performance. Although
- vector compilers now deal well with sum reductions such as this loop, this is
- done in effect by splitting the vectors again into six to 10 shorter vectors,
- making a vector architecture less than fully effective for this type of
- application.
- Unrolling compilers can eliminate most of the duplicate operations by
- combining the common subexpressions over several iterations of the loop. Each
- additional loop iteration will require an additional pair of sins and coss.
- Compilers have been available (e.g. Multiflow) which would detect this
- situation automatically and build in a call such as cosf_sinf_2(t[0],t[1])
- which returns cosf and sinf of both arguments, a total of four results from
- one function call. Such a function is a good match to the architecture of a
- superscalar processor. Even if your compiler does not perform the
- transformation automatically, manual rewriting is not unduly burdensome and
- need not detract from performance on scalar processors. A change as simple as
- ty = q[0]*sinf(t[0]);
- tx = q[0]*cosf(t[0]);
- for( i=2; i<n; i+=2){
- temp = cosf_sinf_2(t[i-1],t[i]);
- yint += ty+q[i]*temp.sin2+4*q[i-1]*temp.sin1;
- xint += tx+q[i]*temp.cos2+4*q[i-1]*temp.cos1;
- tx = q[i]*temp.cos2;
- ty = q[i]*temp.sin2;
- }
- should produce most of the advertised performance of any non-vector machine.
-
-
- Vector and Vector Chunk Function Coding Style
-
-
- Multiple copy or vector chunk math functions, like vector code, need to be
- written without conditionals which actually cause transfer of control. In a
- scalar trig function, it would usually be worth while to test the argument to
- find out whether it needs to be translated into the primary range. In a vector
- chunk function, the full range reduction should be performed whether it is
- needed or not, so that all of the code for the function can be compiled as one
- basic block.
- Transfer of control (branching) gives the compiler a choice of undesirable
- consequences. Either the pipelines must be allowed to empty, reducing the
- performance to scalar levels until they are refilled, or trace scheduling must
- be used to fill the pipeline with future operations along the preferred path
- of execution. Each branch can cause generation of another trace, and the
- length of compiled code may grow exponentially with the number of branches.
- The speed gained by keeping the pipelines full may be canceled by increased
- paging.
- A great deal of progress has been made in architecture and compilers in recent
- years, so that many simple conditional selections can be performed without a
- transfer of control, if this is necessary to keep a processor producing
- results at rated speed. This requires calculation of both alternative results
- followed by instructions which select the correct one. There is a good
- correspondence with the syntax of ?: in C, although the compiler should not be
- totally dependent on the programmer choosing to use ?. Existing compilers do
- not vectorize if..else. All of the operations in the sin, cos, tan, exp, and
- log functions can and should be written in vectorizable form, even when the
- overall scheme is to favor superscalar execution.
- Vector chunk functions do not fit well with the <errno.h> system for error
- reporting. The best that can be done is to report that ERANGE or EDOM
- exceptions have been raised for one of the arguments or results. Vector
- functions give an even hazier indication of trouble.
-
-
- Some Nuts and Bolts of Machine Dependence
-
-
- In some of the examples, the sign of a float or double is tested by assuming
- that it is in the same position as the sign of an int which shares the same
- storage. This is done either because it is faster or because it reduces
- register thrashing on certain machines. Generally, in these functions, there
- is an imbalance of double over integer arithmetic, and integer operations can
- be treated as a free resource whenever float operations are being performed at
- the same time.
- This code will work as is on most modern architectures which use the same byte
- order for double and int. On VAX-compatible architectures, the sign of a
- double falls in place with the sign of a short at the same address, apparently
- as a result of the PDP-11 ancestry.
- A few architectures also suffer from excess of divide and multiply operations
- over add and subtract, so, in the examples, addition is used instead of
- multiply by 2. In these examples, it occurs when there are plenty of pipeline
- slots open, but in other cases, one would not want to prevent an optimizing
- compiler from converting multiply by 2 to a ldexp operation.
- As the conditionals which would be required for architectures not conforming
- to IEEE P754 standards would clutter up the code, I have simply put in #error
- preprocessor directives, which are ignored by non-ANSI compilers because they
- are indented.
- Since a good pipelining compiler will give priority to finishing up the
- expressions which are placed first in the code, the later copies of the
- functions tend to fall behind. This may be aggravated by compilers which give
- priority to loading constants into registers long before they can be used. The
- way to compensate is to write the earlier copies so as to minimize use of
- registers and leave more empty pipeline slots which can be filled up by
- arithmetic from the later copies.
- The later copies are written for more available parallelism at the expense of
- register usage, so that, when the calculation of the earlier results has
- finished, the pipelines can still be kept full nearly to the end of the
- function. This can lead to somewhat greater round off errors in the later
- copies, in the double functions. In the float functions, use of double
- arithmetic eliminates the effect of order of operations on accuracy.
-
- Systems which are unable to perform simple conditional selections without
- branching may require sign changes to be performed by xoring the sign bit. To
- avoid branching, errno may be left alone or set by
- errno=ERANGE&(-(relational expresssion))
- which sets it to zero or to ERANGE. This is contrary to the normal requirement
- that errno never be set to zero, but may be a satisfactory compromise.
-
-
- Calculation of Coefficients
-
-
- Listing 1 shows a bc program for calculation of coefficients for sin, as used
- in sin_4.c. Running it with double arithmetic in C will produce the same
- results up through at least 10 digits. Because bc uses fixed point arithmetic,
- it needs extra fractional digits for sin, more than are needed for most
- problems. The same program will work if the t function is replaced by a(x)/x,
- with appropriate changes in the interval. The coefficients for log base 2,
- used in powf_2.c, are calculated by having the t function evaluate the
- appropriate Taylor-Maclaurin series. With overnight runs, bc can calculate
- coefficients up to 50 significant digits. These Chebyshef subroutines are
- adaptations of those given by Press, Flannery et al (1).
-
-
- Multiple Copy sin
-
-
- Listing 2 shows the four copy sin function. The code which performs range
- reduction, by subtracting off the nearest multiple of pi, uses a rint
- function, but takes advantage of the fact that dividing by pi does not change
- the sign. It assumes that addition is performed in the highest available
- precision, which may be more than double. rint is not covered by standards,
- and its result may depend on rounding mode, so it would not take care of
- portability. Use of long double precision in these operations is highly
- desirable, but of little value unless a true long double value of pi is
- available. long double should prevent degradation of accuracy for arguments up
- to pi*10^ (LDBL_DIG-DBL_DIG).
- The sign of the argument is ored into the rounding constant in order not to
- tie up as many double registers, so that the operations on subsequent copies
- will not be delayed. This procedure avoids branching on processors which do
- not have a select operation. Portability at the expense of speed can be
- obtained using expressions such as
- pm = (int)(x/pi+(x>O?.5:-.5))
- or
- pm = (int)(x/pi-.5+(x>0))
- since, if fabs(x/pi) exceeds INT_MAX, there probably aren't more than three
- digits significance left. Since FORTRAN and Pascal have round double to
- integer syntax, certain processors (e.g. MIPS) have implemented it as a single
- instruction, which is not used by C compilers.
- The integer overflow situation is reported as errno=ERANGE, without
- distinguishing which of the four arguments caused it. Non-portable code for
- testing the exponent field to identify this situation is used because, on the
- system where the code was tested, there weren't enough double registers to
- squeeze in any more standard arithmetic without stretching the code out by
- 30%. There are ways to test whether pm is odd without ever casting to int, so
- that range errors are avoided out to pi/DBL_EPSILON, but it's not worth the
- trouble.
- Covering the whole interval from -pi/2 to pi/2 with a single curve fit avoids
- conditional branches which are particularly troublesome for vector or vector
- chunk coding. An eight term Chebyshef-economized polynomial is just sufficient
- to hold the errors to 1 unit-in-the-last-place with DBL_MANT_DIG = 53, in the
- absence of other approximations. Putting the interval end points where the
- function has zero slope helps prevent round off error from introducing
- discontinuities.
- Horner polynomial evaluations are begun before the sign of the result has been
- determined, leaving the sign switching to be performed when the compiler finds
- the necessary pipeline slots. The third and fourth polynomial evaluations will
- lag well behind the first and second, so the third and fourth Horner
- polynomials are split in two so that the pipelines can be kept fuller after
- the earlier polynomial evaluations are complete. This adds two multiplications
- and one possibly significant round off error in each of the third and fourth
- results.
- The fourth copy differs from the third only in that the code is written with
- parentheses to force the final additions to occur in the most parallel (but
- not most accurate) order. The dummy multiply by 1 is needed to force K&R
- compilers to honor the parentheses, but has no effect in ANSI syntax. Since
- similar techniques are used to a greater extent in scalar math libraries for
- superscalar processors, these less accurate results are likely to be closer to
- the scalar results.
- This function should achieve a megaflop rating better than the LIN-PACK rating
- on many processors, which is unusual effectiveness for such complex code. One
- of the ways it could be used would be to combine calculation of unrelated sins
- and coss, using the relationship
- #define cos(x) sin(PI/2-(x))
- as needed. A similar tactic should pay off on vector architectures, in which
- the various arguments are copied to a temporary vector so that the vector sin
- function can be used.
- Effective pipelining of this function appears to require more than 16 double
- registers, along with special efforts to perform as many calculations as
- possible in int registers. Examination of results of an early MIPS compiler
- showed that it was able to economize on the size of generated code by setting
- the constants only once. Like many RISC architectures, MIPS has immediate
- constants available only to initialize registers, not to participate directly
- in floating point operations. This may not leave enough registers available
- for extensive pipelining.
- Optimization for reduction of length of generated code prior to scheduling of
- operations is less well correlated with execution speed on pipelined than on
- scalar processors. The MIPS software does not report the number of empty
- pipeline stages. The compiler for the original Multiflow 7/200 compiles this
- code in 96 major instruction cycles and obtains a superscalar speedup factor
- of 4. Only six of these instructions are empty, all occurring after the first
- copy result is complete. sin_4 on the Multiflow is twice as fast as their
- library sin, giving four results in 12 microseconds. On the Silicon Graphics
- 4D/25, both sin_4 and the library sin take about four microseconds per result.
- Listing 3 shows a test driver to compare the results of sin_4 with sin. While
- many compilers allow passing a double to a function which receives it as a
- union, other compilers push a union on the stack in a different order from a
- plain double. It is safer to make sin_4 copy the arguments into its unions. On
- one of the compilers tested, the generated code is the same either way.
- The Chebyshef fit of Listing 1 can be changed to use sin_4, after changing
- from bc to C syntax. The order of Chebyshef fit may as well be a multiple of
- 4. The accuracy of math function approximations, such as the functions
- discussed in this article, can be tested by fitting Chebyshef polynomials and
- comparing the coefficients with those obtained by a higher accuracy
- calculation in the same interval.
-
-
- Multiple Copy Float cos and sin
-
-
- Listing 4 shows a function to calculate cos and sin of two arguments in float
- precision. Since it uses rational polynomial approximations, there is more
- built-in opportunity for parallelism than in a Horner polynomial, and two such
- functions are enough to fill a four stage pipeline at the peak stages. Without
- prototypes, the only way to pass float arguments without widening to double is
- by unions. With prototyping, it would be better to pass float arguments and
- copy them to unions inside the function.
- One multiply can be eliminated from the critical path by scaling the arguments
- to multiples of pi/2 and adjusting the polynomial coefficients accordingly.
- The division by 2 of the half-angle formulae is buried in the coefficients, so
- the range reduction maps the arguments into the range -2 to +2. Adding and
- subtracting 4/LDBL_EPSILON produces a number which is rounded to the nearest
- multiple of 4. As long as promotion to IEEE double is used, so that no
- precautions against underflow are needed, there would be no problem in
- changing the scaling so that the code could start off
- tn = x1.flt/2/PI - rint(x1.flt/2/PI)
- in case that could be calculated more efficiently. The choice of scale was
- influenced by the desire to maintain accuracy if base 16 arithmetic is used.
- Scaling the arguments would produce an additional round off error if the
- calculations were performed in float precision, but double is almost mandatory
- anyway as it prevents degradation of accuracy for arguments up to
- 2pi*10^(DBL_DIG-FLT_DIG). A warning such as storing a value into errno could
- be provided when larger arguments arrive, but this is not clearly a failure
- meriting the ERANGE label unless the argument becomes so large that the rint
- code won't work. Basing the errno calculation on values which are calculated
- anyway minimizes the use of additional registers.
- The first rational polynomial is calculated Horner style, and the last
- attempts to catch up by calculating all terms individually, at the cost of one
- additional multiplication. The scheme of eliminating one of the coefficients
- by choice of scale allows the numerator to get a head start so that the final
- multiplication can be performed without delaying the division. The compiler
- may have to be forced into performing the first add in the denominator without
- waiting until the last term has been calculated. Certain compilers insist on
- converting the repeated divisions into multiplications, which is no problem
- when the operations have been promoted in precision.
-
-
- Vector Chunk float pow
-
-
- The pow function in C is expected to embody two entirely different types of
- operation. In order for it to be vectorizable, or to obtain good vector chunk
- performance enhancement with current compilers, it has to be restricted to the
- cases of positive base, where it can be replaced in effect by
- #define pow(x,y) exp(log(x)*y)
- This could be done with a top level powf_2 which determines whether both pairs
- of arguments are of one type, and, if so, invokes an appropriate vector chunk
- function. The usual test is whether y1 and y2 are changed by casting to int
- and back to float. It doesn't hurt much to use the log treatment anyway,
- unless x is negative. If the argument pairs cannot be processed by the same
- algorithm, it would have been more efficient not to have tried to treat them
- as a vector chunk at all.
- The function of Listing 4 does not take care of the negative base case, which
- is OK according to ANSI standards if it is called as the implementation of the
- FORTRAN real exponentiation operator. I use it in this form in time marching
- aerodynamics codes, where it gets executed millions of times.
- Promotion to double is really needed only in the sections involving addition
- of the integer exponent to the base range log2 up to the splitting of the exp2
- argument into an integer plus or minus a fraction, and then only when the
- result is far from 1. The somewhat complicated system for splitting the base
- into modified frexp form works quickly and accurately without widening on a
- system without gradual underflow. On architectures such as VAX which use a
- different byte order for float and int, the unions and constants are
- different.
- If gradual underflow is to be supported without widening the precision, it
- will require special case treatment. To reduce degradation of accuracy if
- widening is not used for addition of the integer and fraction parts of the
- log2 function, log2 should be split into a power of 2 plus a smaller term.
- This leads to complicated code which may require branching, thus defeating
- attempts to gain pipelined performance.
- Evaluation of log2(x2)*y2 is speeded up by grouping the terms in pairs. The
- calculation log2(x2)*y1 then becomes a bottleneck until the multiplication by
- y1 is distributed onto the two groups, one of which consists of the three-term
- Horner polynomial. Multiplication of y1 by the integer exponent is performed
- well before it is needed.
- Making such detailed adjustments for a given system is possible only with
- readable assembly language which displays the final scheduling of the
- pipelined operations, and is helped greatly by static profiling which gives
- the effective clock count for each block of generated code. Since we try to
- write these functions so that there is only one code block, and there are few
- memory accesses which could introduce bus delays, the speed will not depend on
- data and there should be no question what effect each change has on speed.
- In order to make the ROUND macro work the same under K&R syntax as it would in
- ANSI C, dummy multiplications by 1 are introduced. Otherwise it is a matter of
- luck whether a K&R compiler will generate the required code, although the left
- associativity of the + and - operators should produce a preference for left to
- right evaluation. From an algebraic point of view, ROUND would do nothing, and
- AI techniques could conceivably allow a compiler to know this. The peculiar
- syntax of K&R which requires such multiplications by 1 makes it
- semi-obligatory for optimizing compilers to eliminate the redundant operation,
- unless compiling for an architecture which may generate faster code with
- alternating multiplication and addition.
- If the compiler is unable to generate efficient code for the max and min
- macros, it would be better to perform the ldexp operations on doubles and hope
- that the extra range of double will take care of over and underflows.
-
-
-
- Multiple Copy tan
-
-
- The tan_2 function (Listing 5) requires the least non-portable coding for
- optimum results, but it illustrates optimizations which have not appeared in
- the functions discussed above.
- Range reduction consists simply of subtracting the nearest multiple of pi, and
- there is no advantage in playing games with unions. The comparisons start into
- the pipeline first and are completed before the divides, which may have been
- converted to multiplications by the compiler.
- The remainder of the calculation consists of evaluation of a rational
- polynomial. On a machine with a divide which pipelines at the same intervals
- as the other operations, straightforward Horner evaluation of the numerators
- and denominators might work as well as anything. On the processor for which
- this code was tuned, a divide operation delays the pipeline, but does not
- affect addition. For this reason, the order of operations is set up to push
- all of the multiplications into the pipeline before the divides, as well as to
- start the first division as soon as possible.
- The terms of the numerator and denominator are grouped so that operations are
- always ready to start into the pipeline, and to take advantage of
- architectures which have separate pipelines for multiplication and addition.
- In the denominator of the first copy, the high order terms must be added in
- order of increasing complexity. This could be done with parentheses with an
- ANSI compiler. Possibly better accuracy could be obtained by adding the high
- order term last. Order is not dictated in the low order terms of the first
- copy, so as to avoid delaying the second copy. In the numerator of the second
- copy, the final multiplications are distributed so that they may be performed
- before the final addition, in order to get the multiplications out of the way
- early, as well as to allow the second calculation to begin to catch up with
- the first.
-
-
- Summary
-
-
- Much of this article will appeal only to those who like to tweak code for
- another 20% in performance. I have concentrated on the points where
- architectural dependencies pop up and tried to show where their impact can be
- reduced for relatively small performance penalties. Math library functions are
- probably the closest thing to applications where non-portable code is
- appropriate. This is the reason for these functions (at least the scalar
- versions) being defined in the C standard so that they need not be carried as
- part of on application.
- The extent to which special vector chunk functions should be used to perform
- math library operations in groups may be questioned. An application which uses
- these functions probably should provide an alternative header file which will
- cause them to be replaced with standard functions. Compilers are most likely
- to begin to incorporate such functions automatically if they produce benefits
- on the standard benchmarks.
- Scalar math functions can detract from the performance of superscalar
- processors. The techniques shown enable superscalar performance to be obtained
- in the evaluation of grouped math library functions. In typical applications,
- the percentage of execution time spent in math functions can be reduced in
- comparison with a scalar processor.
- References
- John Ellis, Bulldog: A Compiler for VLIW Architectures, MIT Press, 1985
- (avoidance of code explosion, trace scheduled pipelined code).
- Press, Flannery, Teukolsky, Vettering, Numerical Recipes in C, Cambridge, 1988
- for Chebyshef analysis.
- John Palmer, Stephen Morse, The 8087 Primer, Wiley, 1984 for some math
- function concepts.
- P. J. Plauger, Jim Brodie, Standard C, Microsoft Press, 1989 for the simplest
- satisfactory explanation of float.h, limits.h, math.h.
- T. Prince, "Generating Source for <float.h>," The C Users Journal, V8N6, June
- 1990.
-
- Listing 1 (s)
- /*
- ** bc program to calculate Chebyshef economized polynomial
- ** for evaluation of sin(x) */
- /* use bc -1 to get c() and s() functions */
- define t(x) { /* sin(x)/x */
- if(x==0)return(1.); /* derivative of s function */
- return (s(x) / x); /* put function to be fit here */ }
- define b(x) {
- if (x < 0) return (-x);
- return (x); }
- define m(x, y) {
- if (x > y) return (x);
- return (y); }
- n = 22; /* number of Chebyshef terms */
- scale = 40;
- p = a(1.) * 4; /* pi */
- b = p * .5; /* upper end of curve fit interval */
- a = -b; /* lower end of interval */
- /* chebft adapted from Press Flannery et al */
- /* "Numerical Recipes" FORTRAN version */
- for (k = 1; k <= n; ++k) {
- c[k] = 0;
- f[k] = t(c((k - .5) * p / n) * (b - a) * .5 + (b + a) * .5);
- }
- /* because of symmetry, even c[] are 0 */
- for (j = 1; j <= n; j += 2) {
- s = 0;
- q = (j - 1) * p / n;
- for (k = 1; k <= n; ++k) s += c(q * (k - .5)) * f[k];
- (c[j] = 2 / n * s); }
- /* skip even terms, which are 0 */
- for (n = 5; n <= 19; n += 2) {
- /* chebpc */
- for (j = 1; j <= n; ++j) d[j] = e[j] = 0;
- d[1] = c[n];
-
- for (j = n - 1; j >= 2; -j) {
- for (k = n - j + 1; k >= 2; -k) {
- s = d[k];
- d[k] = d[k - 1] * 2 - e[k];
- e[k] = s; }
- s = d[1];
- d[1] = c[j] - e[1];
- e[1] = s; }
- for (j = n; j >= 2; -j) d[j] = d[j - 1] - e[j];
- d[1] = c[1] * .5 - e[1];
- /* pcshft */
- g = 2 / (b - a);
- for (j = 2; j <= n; ++j) {
- d[j] *= g;
- g *= 2 / (b - a); }
- for (j = 1; j < n; ++j) {
- h = d[n];
- for (k = n - 1; k >= j; -k) {
- h = d[k] - (a + b) * .5 * h;
- d[k] = h; }
- }
- "Chebyshev Sin fit x<Pi/2 coefficients"
- " Maximum Rel Error:"
- m(b(c[n + 2]), b(c[2])) / t(b);
- for (i = 1; i <= n; i += 2) d[i];
- }
- /* End of File */
-
-
- Listing 2 (sin_4.c)
- typedef struct {
- double X1, X2, X3, X4;
- } ARG_D_4; /* vector 4*/
- #include "float.h"
- ARG_D_4 sin_4(xx1, xx2, xx3, xx4)
- double xx1, xx2, xx3, xx4;
- /* use where cos of same argument not needed
- ** 16 digits precision, compare to 15 digits in "dtrig.h"
- ** T C Prince */
- {
- union dblfmt {
- double dbl;
- int idbl;
- struct dfmt { /* IEEE p754 */
- unsigned int sign:1;
- unsigned int ex:11;
- } fmt;
- } xi1;
- double xr, x2, x4, x8;
- #ifdef _STDC_____LINEEND____
- long double pi = 3.1415926535897932385, pml;
- #include <limits.h>
- #else
- register double pi = 3.1415926535897932385, pml;
- #define LONG_MIN 0x80000000
- #endif
- union dblfmt pm, round;
- ARG_D_4 res;
- #define BIAS DBL_MAX_EXP
-
- #include <errno.h>
- #ifndef errno
- extern int errno;
- #endif
- #define ODD(i) ((i)&1)
- /* use identity sin(x + n pi) = (-1)^n sin(x)
- ** to reduce range to -pi/2 < x < pi/2
- ** pml=rint(xi1/pi) */
- #if FLT_ROUNDS != 1
- #error "rounding mode not nearest; adjust code"
- #endif
- #if FLT_RADIX !=2 && FLT_RADIX != 10
- #error "code not optimum for accuracy in this RADIX"
- #endif
- #if DBL_DIG > 16
- #error "more terms needed for full accuracy"
- #endif
- /* shortcut test of sign, not portable to VAX */
- round.dbl = 1 / LDBL_EPSILON;
- xi1.dbl = xx1;
- round.idbl = xi1.idbl & LONG_MIN;
- pml = xx1 / pi + round.dbl;
- /* sign reversal may reduce register usage */
- xr = pi * (pml -= round.dbl) - xx1;
- /* shortcut test for fabs(pml) > INT_MAX */
- pm.dbl = pml;
- if (pm.fmt.ex > BIAS +31)
- errno = ERANGE;
- /* don't wait to calculate xr**2 until sign is fixed;
- ** another sign reversal is due if pm.dbl is odd */
- x2 = xr * xr;
- /* first sign reversal compensated in coefficient signs;
- ** conditional sign fixed by testing odd/even
- ** first two results are obtained by straight Horner
- ** polynomial evaluation */
- res.X1 = (-.9999999999999999 + x2 * (.1666666666666607
- + x2 * (-.833333333328281e-2 + x2 * (.19841269824875e-3
- + x2 * (-.2755731661057e-5 + x2 * (.25051882036e-7
- + x2 * (-.160481709e-9 + x2 * .7374418e-12)))))))
- * (ODD((int) pm.dbl) ? -xr : xr);
- /* sin(xi2) */
- round.dbl = 1 / LDBL_EPSILON;
- xi1.dbl = xx2;
- round.idbl = xi1.idbl & LONG_MIN;
- pml = xx2 / pi + round.dbl;
- xr = pi * (pml -= round.dbl) - xx2;
- pm.dbl = pml;
- if (pm.fmt.ex > BIAS + 31)
- errno = ERANGE;
- x2 = xr * xr;
- res.X2 = (-.9999999999999999 + x2 * (.1666666666666607
- + x2 * (-.833333333328281e-2 + x2 * (.19841269824875e-3
- + x2 * (-.2755731661057e-5 + x2 * (.25051882036e-7
- + x2 * (-.160481709e-9 + x2 * .7374418e-12)))))))
- * (ODD((int) pm.dbl) ? -xr : xr);
- /* sin(xi3) */
- round.dbl = 1 / LDBL_EPSILON;
- xi1.dbl = xx3;
- round.idbl = xi1.idbl & LONG_MIN;
-
- pml = xx3 / pi + round.dbl;
- xr = pi * (pml -= round.dbl) - xx3;
- pm.dbl = pml;
- if (pm.fmt.ex > BIAS + 31)
- errno = ERANGE;
- x2 = xr * xr;
- x4 = x2 * x2;
- /* split into 2 Horner polynomials to increase
- ** parallelism after 1st result finishes */
- res.X3 = (-.9999999999999999 + x2 * (.1666666666666607
- + x2 * (-.833333333328281e-2
- + x2 * .19841269824875e-3))
- + (-.2755731661057e-5 + x2 * (.25051882036e-7
- + x2 * (-.160481709e-9
- + x2 * .7374418e-12))) * x4 * x4) *
- (ODD((int) pm.dbl) ? -xr : xr);
- /* sin(xi4) */
- round.dbl = 1 / LDBL_EPSILON;
- xi1.dbl = xx4;
- round.idbl = xi1.idbl & LONG_MIN;
- pml = xx4 / pi + round.dbl;
- xr = pi * (pml -= round.dbl) - xi1.dbl;
- /* errno is set to ERANGE if any of the arguments are too
- ** large for reasonable range reduction */
- pm.dbl = pml;
- if (pm.fmt.ex > BIAS + 31)
- errno = ERANGE;
- x2 = xr * xr;
- x4 = x2 * x2;
- x8 = x4 * x4;
- /* multiply by 1 is K&R way to enforce parentheses */
- res.X4 = ((-.9999999999999999 + x2 * (.1666666666666607
- + x2 * (-.833333333328281e-2
- + x2 * .19841269824875e-3))) * 1
- + (-.2755731661057e-5 + x2 * (.25051882036e-7
- + x2 * (-.160481709e-9 + x2 * .7374418e-12))) * x8)
- * (ODD((int) pm.dbl) ? -xr : xr);
- return res;
- }
- /* End of File */
-
-
- Listing 3 (sin_4~bt.c)
- /* Tests sin_4() */
- typedef struct {
- double X1, X2, X3, X4;
- } ARG_D_4; /* vector 4 */
- ARG_D_4 sin_4();
- #include <math.h>
- main(){
- ARG_D_4 res;
- res=sin_4(-2.,-1.,1.,2.);
- printf(
- "\t%.17g\t%.17g\n\t%.17g\t%.17g\n\t%.17g\t%.17g\n\t%.17g\t%.17g\n",
- res.X1,sin(-2.),
- res.X2,sin(-1.),
- res.X3,sin(1.),
- res.X4,sin(2.));
- }
-
- /* End of File */
-
-
- Listing 4 (powf_2.c)
- typedef struct {
- float X1, X2;
- } ARG_F_2; /* vector 2 */
- #include "float.h"
- #include <errno.h>
- #ifndef errno
- extern int errno;
- #endif
- #define MANTBITS (FLT_MANT_DIG -1)
- ARG_F_2 powf_2(xi1, y1, xi2, y2)
- union fltfmt {
- float flt;
- int iflt; /* VAX: must change all this */
- struct ffmt {
- unsigned int ex:9;
- unsigned int mant:MANTBITS;
- } fmt;
- } xi1, xi2, y1, y2;
- {
- #define max(i,j) ((i)>(j)?(i):(j))
- #define min(i,j) ((i)<(j)?(i):(j))
- #if FLT_MANT_DIG != 24
- #error "use portable frexp() ldexp() */
- #endif
- #if FLT_ROUNDS == 1
- #if defined(_STDC_)
- /* This works on some non-ANSI compilers */
- #define ROUND(x) ((x)>=0?( \
- (x)+1/LDBL_EPSILON)-1/LDBL_EPSILON: \
- ((x)-1/LDBL_EPSILON)+1/LDBL_EPSILON)
- #else
- #define ROUND(x) ((x)>=0?( \
- (x)+1/LDBL_EPSILON)*1-1/LDBL_EPSILON: \
- ((x)-1/LDBL_EPSILON)*1+1/LDBL_EPSILON)
- #endif
- #else
- #define ROUND(x) ((x)>=0?(int)(x+.5):(int)(x-.5))
- #endif
- int mi, mi2, msign;
- double xr, x2, r, r1;
- ARG_F_2 res;
- /* Copy 1 */
- /* This frexp() operation would be done better after
- ** promotion to double
- ** but it's not mandatory unless dealing with gradual
- ** underflow;
- ** it would eliminate most cases of 0 and Inf changing
- ** to finite numbers
- ** if((xi1.flt=frexp(xi1.flt ,&mi))<sqrt(.5)){
- --mt;
- xi1.flt *= 2;
- } */
- mi = ((xi1.iflt & 0x7fffffff) -
- (mi2 = (xi1.fmt.mant < 0x3504f3 ?
- (2 - FLT_MIN_EXP) << MANTBITS :
-
- (1 - FLT_MIN_EXP) << MANTBITS)))
- >> MANTBITS;
- if (xi1.iflt < 0 xi2. iflt < 0) errno = EDOM;
- xi1.iflt = mi2 xi1.fmt.mant;
- /* Mult by y distributed to increase parallelism */
- r1 = (xr = (xi1.flt - 1) / (xi1.flt + 1)) * y1.flt;
- x2 = xr * xr;
- /* Coefficients determined by Chebyshef fitting
- ** double precision is only really needed from here */
- r = y1.flt * (double) mi + r1 * (2.8853904 +
- x2 * (.5958 * x2 + .961588));
- /* Msign = (r -= rint(r)) <0 */
- msign = (r -= r1 = ROUND(r)) < 0;
- r *= 125.0718418 + (x2 = r * r);
- x2 = 360.8810526 + 17.3342336 * x2;
- /* Xi1.flt = ldexp((x2+r) / (x2-r),(int)r1) */
- xi1.flt = (x2 + r) / (x2 - r);
- /* Preferably do this ldexp() operation in double,
- ** but it's slower,
- ** even though msign can be eliminated;
- ** it would always give Inf rather than NaN
- ** and would allow use of gradual underflow */
- xil.iflt += (max(FLT_MIN_EXP - 2 + msign,
- min(FLT_MAX_EXP + msign, (int) r1)) << MANTBITS);
- /* X.fmt.ex+=mi; with limiting to prevent exponent wraparound */
- res. X1 = xi1.flt;
- /* Copy 2 */
- mi = ((xi2.iflt & 0x7fffffff) -
- (mi2 = (xi2.fmt.mant < 0x3504f3 ?
- (2 - FLT_MIN_EXP) << MANTBITS :
- (1 - FLT_MIN_EXP) << MANTBITS)))
- >> MANTBITS;
- xi2.iflt = mi2 xi2.fmt.mant;
- r1 = (xr = (xi2.flt - 1) / (xi2.flt + 1)) * y2.flt;
- r1 *= x2 = xr * xr;
- r = y2.flt * ((double) mi + xr * 2.8853904) +
- r1 * (.5958 * x2 + .961588);
- msign = (r -= r1 = ROUND(r)) < 0;
- r *= 125.0718418 + (x2 = r * r);
- x2 = 360.8810526 + 17.3342336 * x2;
- xi2.flt = (x2 + r) / (x2 - r);
- xi2.iflt += (max(FLT_MIN_EXP - 2 + msign,
- min(FLT_MAX_EXP + msign, (int) r1)) << MANTBITS);
- res.X2 = xi2.flt;
- return res;
- }
- /* End of File */
-
-
- Listing 5 (cosf_~bs.c)
- typedef struct {
- float cos1, sin1, cos2, sin2;
- } ARG_FF_2; /* vector 2 pairs */
- ARG_FF_2 cosf_sinf_2(x1, x2)
- union {
- float flt;
- int iflt;
- } x1, x2;
- { /* 2 pair single precision
-
- sin/cos function */
- #include "float.h"
- #if FLT_ROUNDS != 1
- #error "rounding mode not nearest, fix code"
- #endif
- #include <errno.h>
- #ifndef errno
- extern int errno;
- #endif
- #include <math.h>
- #define M_2_PI 0.63661977236758134308
- #define T2PI 2*M_2_PI
- #define TP2 T2PI*T2PI
- #define TP3 TP2*T2PI
- #define TP4 TP2*TP2
- #define TP5 TP3*TP2
- ARG_FF_2 res;
- double tn, td, r;
- /* integer compare with 0 same as float, for IEEE
- ** since arg comes in int register, this is faster
- **
- ** doing everything in double, we won't lose accuracy
- ** by converting arg to multiple of PI/2
- ** this allows range reduction by subtracting an integer
- **
- ** reduce to range +- 2, divide by 2 later
- ** when it cannot underflow */
- tn = x1.flt * M_2_PI + (td = x1.iflt >= 0 ?
- 4 / LDBL_EPSILON : -4 / LDBL_EPSILON);
- if (fabs(x1.flt * M_2_PI) >= 4 / LDBL_EPSILON
- fabs(x2.flt * M_2_PI) >= 4 / LDBL_EPSILON)
- errno = ERANGE;
- tn -= td;
- tn = x1.flt * M_2_PI - tn;
- td = tn * tn;
- /* divide arg by 2 and rationalize numerator and denominator
- ** numerator of rational approx for tan(x1/2)
- ** Horner polynomials 1st time */
- tn *= 886.77348 * TP4 + td * (-99.398954 * TP2 + td);
- /* denominator */
- td = 886.77346 * TP5 + td *
- (-394.98971 * TP3 + td * 14.425694 * T2PI);
- /* cos, sin half angle formulae, rationalized */
- res.cos1 = (td * td - tn * tn) / (td * td + tn * tn);
- res.sin1 = (tn * td + tn * td) / (td * td + tn * tn);
- /* copy 2 */
- tn = x2.flt * M_2_PI + (td = x2.iflt >= 0 ?
- 4 / LDBL_EPSILON : -4 / LDBL_EPSILON);
- tn -= td;
- tn = x2.flt * M_2_PI - tn;
- td = tn * tn;
- /* distribute terms to finish polynomials quicker */
- tn *= 886.77348 * TP4 - td * 99.398954 * TP2 + td * td;
- r = 886.77346 * TP5 - td * 394.98971 * TP3;
- td = r + td * td * 14.425694 * T2PI;
- res.cos2 = (td * td - tn * tn) / (td * td + tn * tn);
- res.sin2 = (tn * td + tn * td) / (td * td + tn * tn);
- return res;
- }
-
-
- /* End of File */
-
-
- Listing 6 (tan_2.c)
- typedef struct {
- double X1, X2;
- } ARG_D_2; /* vector 2 */
- ARG_D_2
- tan_2(xi1, xi2)
- double xi1, xi2;
- {
- double x2, x, n1, x4;
- ARG_D_2 res;
- #include "float.h"
- #if FLT_ROUNDS != 1
- #error "rounding mode not nearest; adjust code"
- #endif
- #if FLT_RADIX !=2 && FLT_RADIX != 10
- #error "code not optimum for accuracy in this
- RADIX"
- #endif
- #include <errno.h>
- #ifndef errno
- extern int errno;
- #endif
- #include <math.h>
- #define M_PI 3.14159265358979323846
- x2 = (n1 = (xi1 > 0 ? 1 / LDBL_EPSILON :
- -1 / LDBL_EPSILON))7 + (x = xi1) / M_PI;
- x -= (x2 - n1) * M_PI;
- if (fabs(xi1 / M_PI) >= 1 / LDBL_EPSILON
- fabs(xi2 / M_PI) >= 1 / LDBL_EPSILON)
- errno = ERANGE;
- /* now in 1st or 4th quadrant */
- #define c0 33281881.3202530279
- n1 = c0 + (x2 = x * x) * (-15666569.8711211851);
- x4 = x2 * x2;
- res.X1 = x * (c0 + x2 * (-4572609.43103684572) + x4 *
- (131095.887915363619 + x2 * (-968.863245687503149 +
- x2))) / (n1 + x4 * (915701.668921990803
- + x2 * (-13491.7937027796916)
- + x4 * 44.4083322286368691));
- /* copy 2 */
- x2 = (n1 = (xi2 > 0 ? 1 / LDBL_EPSILON :
- -1 / LDBL_EPSILON)) + (x = xi2) / M_PI;
- x -= (x2 - n1) * M_PI;
- n1 = 915701.668921990803 - (x2 = x * x)
- * 13491.7937027796916;
- x4 = x2 * x2;
- res.X2 = (x * (c0 + x2 * (-4572609.43103684572)) +
- (131095.887915363619 + x2 * (-968.863245687503149 +
- x2)) * x4 * x) / (c0 + x2 * (-15666569.8711211851) +
- x4 * (n1 + x4 * 44.4083322286368691));
- return res;
- }
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Yet Another C++ Money Class
-
-
- Adolfo Di Mare
-
-
- This article is not available in electronic form.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lexical Analysis Using Search Tries
-
-
- John W. M. Stevens
-
-
- John Stevens is a graduate of Colorado State University with a bachelor's
- degree in computer science. He has worked at five different companies as a
- programmer and software engineer, writing everything from accounting programs
- for truck drivers to C compilers for high-speed parallel computers. He is
- currently working for Space Tech as a compiler writer.
-
-
- Recently, there has been a lot of talk about an old UNIX idea, that of user
- programmability. User programming presents some drawbacks, not least of which
- is the absence of a standard language. Each new program requires the user to
- learn a new language (unless the new program is a clone of another). Also,
- though different user languages may have similiar syntax, they may not
- interpret a statement the same way. In addition, user programming languages
- have, until just recently, been cryptic and difficult to learn.
-
-
- Language Interpreters
-
-
- To create a program that is user-programmable requires that the software
- engineer know how to design, write, and maintain a language interpreter. Such
- arts are taught in most computer science curriculums. A language
- interpretation system contains three basic components: the lexical analyzer,
- the parser, and the interpreter.
- The first part of the system, the lexical analyzer, takes ASCII input,
- separates it into words, and converts those words to numeric values, called
- tokens. Words that have special meaning in the language are called keywords.
- Punctuation characters, such as ; and :, also have special meaning in the
- language and must be tokenized as well. The lexical analyzer determines if the
- input contains illegal words or punctuation characters.
- While the lexical analyzer breaks the input text into words, it does not
- determine whether the words are arranged into legal sentences. This is the job
- of the parser. The parser takes a stream of tokens from the lexical analyzer
- and attempts to determine if they form a stream of legal sentences according
- to the language's grammar. A grammar consists of a set of rules that describe
- all legal sentences possible in the language. Not all legal sentences make
- sense. In most programming languages, the parser will accept legal sentences
- that the interpreter cannot understand or execute.
- Once the parser has decided that the token stream forms legal sentences, the
- interpreter combines the operations of semantic analysis and program
- execution. Semantic analysis determines what operations the program is telling
- the interpreter to execute. Execution is the operation of reading program
- tokens and translating them into a series of machine language function calls
- that instruct the CPU what to do. Interpreted languages execute slower than
- compiled languages, in part, because the interpreter must translate each
- program sentence into machine language every time it is executed. Sentences
- from compiled programs are already translated into machine language.
-
-
- Lexical Analyzers And Search Tries
-
-
- To facilitate the construction of lexical analyzers, I use a special class of
- search tree, called a trie. A trie is a tree data structure that allows
- strings with similiar character prefixes to use the same prefix data and store
- only the tails as separate data. One character of the string is stored at each
- level of the tree, with the first character of the string stored at the root,
- and the last character of the string stored at a sub-tree node or in a leaf
- node. Figure 1 shows how a trie would store the following collection of words:
- ape, append, able, bee, bearing, cape, caper, capable, us, use and user.
- Tries used for lexical analysis store token values with each character in the
- trie, as shown in Listing 1. Most of the token values are zero, indicating
- ILLEGAL KEYWORD. The token value of the last character of each legal word is
- the token value for that word. For example, in Figure 1, the struct for the
- letter e in the trie branch under the letter u stores the token value for the
- word USE. The letters s and r in the same branch would have the tokens US and
- USER stored with them, respectively.
- Using a trie in a lexical analyzer combines the operations of breaking the
- input text into words and determining whether or not the words are legal for
- the language. This scheme imposes language design constraints on the engineer,
- since words do not have to be delimited to be recognized. The constraint is
- either to design a requirement for delimitation into the language definition
- or to ensure that no two adjacent words of the language can ever be combined
- to make a longer, legal word of the language.
-
-
- Static Tries In C Arrays
-
-
- I prefer to store my tries as static data in the same file as the code for the
- lexical analyzer. This arrangement allows each program to use more than one
- lexical analyzer, as well as eliminates the need for external files. On the
- other hand, tries have a widely variable number of elements per trie level,
- making it imperative to use a dynamically-sized data structure. The method I
- adopted stores each level of the trie as a uniquely named array with elements
- corresponding to the structure in Listing 1.
- Storing tries as source code in the lexical analyzer can make for very large
- source files. Even a small language can have a search trie that is 1100 lines
- of source code. Such a file compiles to a relatively small amount of data, but
- just as no program is ever fast enough, no program is ever small enough
- either. Roughly half to two-thirds of the memory that a trie uses is for
- storing pointers to sub-tries. To minimize the memory requirements of a search
- trie after compilation, you should exploit your compiler's options to group
- data together. Doing so lets you use a smaller pointer size to reference that
- data.
- In order to make creating and maintaining lexical analyzers that use tries
- easier, I've written a program that accepts a text file of token words and
- token define names, creates the trie in memory, and dumps the trie to standard
- out as static C data arrays. This scheme facilitates adding or deleting a word
- from the trie.
-
-
- Example Lexical Analyzer
-
-
- The example lexical analyzer uses a trie that contains the keywords and token
- values defined in Listing 2. The first word on the line is the keyword, and
- the second word is the token value enumeration label for that keyword. The
- trie creation program processes this file. The program output is captured in a
- file that will be included in the source code file for the lexical analyzer.
- Listing 3 contains the token value enumeration, function prototypes, and type
- definitions necessary to use the lexical analyzer. To increase the readability
- of the parser source code, I've selected enumeration labels that are as
- similar as possible to the keywords they represent. Because of name space
- collision with type and/or define names used by the C compiler, some of the
- enumeration labels are postfixed with the string_T.
- Listing 4 contains the lexical analyzer. I've extracted this code from a
- program that acts as a user-programmable file selection shell. The function
- OpenPrg() initializes the lexical analyzer by opening the file that contains
- the source code to be analyzed. The parser then calls the function Lex()
- repeatedly to get tokens. Each time Lex() is called, it begins by reading and
- throwing away both white space characters (space, tab and newline characters)
- and comments. When the first character of a suspected keyword is found, it
- breaks out of the loop and attempts to get either a string constant, integer
- constant, time, or date.
- If the input is not a constant of some type, the trie search function is
- called. The function TrieSrch() begins by attempting to find the input
- character in the trie node. TrieSrch() accepts a pointer to a node of a trie,
- a character to search for, and a pointer to a buffer for storing the word read
- from the input file. The function uses a binary search because the characters
- in a trie node are stored in sorted order.
- If the input character is found in the trie node, TrieSrch() saves it in the
- word buffer. If the matching character in the trie node has a pointer to a
- child trie node, TrieSrch() reads another character from the file and calls
- itself recursively. If the return value from the recursive call indicates that
- the character was not found, TrieSrch() assumes that the input character for
- this call was the last character of a legal word and unreads the character
- read for the recursive call. The token value of the input character to this
- call is returned.
- If the matching character does not have a pointer to a child trie node, the
- keyword buffer is NUL-terminated and the token value stored with the matching
- character is returned. If the input character is not found in the trie node,
- TrieSrch() NUL-terminates the keyword buffer and returns a value indicating
- that the character was not found.
- Figure 2 presents an algorithm in structured English for separating words from
- an input character stream and searching for them in a search trie.
-
-
- Summary
-
-
- A trie is probably not the most efficient data structure for determining the
- legality of an input word. A sorted table of strings searched with a binary
- search would probably be faster and more memory efficient.
- So why use a trie if it isn't as fast or efficient as other methods? Since the
- hardest part of writing a lexical analyzer is in breaking an undifferentiated
- stream of input characters into words, the beauty of a trie is that it groups
- characters into words and determines their legality at the same time. It is
- also, in my opinion, a more elegant solution. This alone is reason enough for
- me to use a trie.
- Figure 1 Example Search Trie
- Figure 2
- 1) Read a character from the file.
-
- Call step 2 with the pointer to the root of the trie, the
- character read and a pointer to the begining of the key word
- save buffer.
-
- 2) Search for the input character in the current node of the trie.
- If the input character is found in the trie then
- Save the input character in the key word save buffer.
- Attempt to read a character from the file.
- If End of File then
- Return End of File.
- If the character found has a child trie pointer then
- Call Step 2 with the child trie pointer, the character
- read from the file and a pointer to the next byte in the
- key word save buffer.
- If the return value from the call to step 2 is NOT FOUND then
- Unread the character read from the file
- Return the token value stored with the input character
- of this call.
- else
- Return the return value of the recursive call.
- else If the character is not found then
- Save a NUL in the key word save buffer.
- Return the value NOT_FOUND.
-
- Listing 1 NODE Structure
- typedef struct key_st {
- char c; /* String character. */
- TKNS token; /* Token value. */
- struct key_st *child; /* Pointer to sub-trie. */
- } NODE;
-
-
- Listing 2
- ( L_PAREN
- ) R_PAREN
- , COMMA
- / F_SLASH
- action ACTION
- after AFTER
- and AND
- archive ARCHIVE
- attributes ATTRIBUTES
- before BEFORE
- directory DIRECTORY_T
- exec EXEC
- files FILES
- hidden HIDDEN
- label LABEL
- modified MODIFIED
- name NAME
- not NOT
- or OR
- print PRINT
- readonly READONLY
- recurs RECURS
- search SEARCH
- select SELECT
- system SYSTEM
- { L_BRACE
-
- BAR
- } R_BRACE
-
-
- Listing 3
- /***********************************************************************
- * Module : Lexical Analyzer --- Header file containing token value
- * enumeration, type definitions and function prototypes for
- * the lexical analyzer functions.
- *
- * Copyright (C) 1990 John W. M. Stevens, All Rights Reserved
- *
- * Author : John W. M. Stevens
- ***********************************************************************/
-
- #if ! defined( LEXICAL_ANALYZER )
- #define LEXICAL_ANALYZER 1
-
- #include <dos.h>
-
- #define TRUE 1
- #define FALSE 0
- #define ERROR -1
- #define OK 0
-
- #define PATH_SZ 65
- typedef unsigned int UINT;
- typedef unsigned char UCHAR;
- typedef char PATH[PATH_SZ];
-
- /* Definition of structure filled in and returned by lex. */
- typedef struct {
- char str[257];
- long no;
- struct time ftime;
- struct date fdate;
- } TOKEN;
-
- /* Token defines. */
- enum tkn_en {
- STRING = 128,
- NUMBER, TIME, DATE,
-
- L_PAREN, R_PAREN, COMMA, F_SLASH, ACTION,
- AFTER, AND, ARCHIVE, ATTRIBUTES, BEFORE,
- DIRECTORY_T, EXEC, FILES, HIDDEN, LABEL,
- MODIFIED, NAME, NOT, OR, PRINT,
- READONLY, RECURS, SEARCH, SELECT, SYSTEM,
- L_BRACE, BAR, R_BRACE
- };
- typedef enum tkn_en TKNS;
-
- /* Function prototypes. */
- extern TKNS Lex(TOKEN *);
- extern void OpenPrg(char *);
- extern void ParsErr(char *);
-
- #endif
- /* End of File */
-
-
-
- Listing 4
- /***********************************************************************
- * Module : Lexical Analyzer --- Process the input text file into tokens
- * that the parser can understand.
- *
- * Copyright (C) 1990 John W. M. Stevens, All Rights Reserved
- *
- * Routines : Lex - Return the next token from the file.
- * OpenPrg - Open the source file.
- * ParsErr - Report a parsing error.
- *
- * Author : John W. M. Stevens
- ***********************************************************************/
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <ctype.h>
- #include <string.h>
-
- #include "lex.h"
-
- /* Structure of trie branch. */
- typedef struct key_st {
- char c;
- TKNS token;
- struct key_st *child;
- } NODE;
-
- /* Constants local to this file. */
- #define MAX_STR 256
- #define NOT_FND -2
-
- /* Object Data. */
- static char word[MAX_STR + 1]; /* Last string analyzed. */
- static char PrvWd[MAX_STR + 1]; /* Previous word. */
- static int LnNo = 0; /* The current line number in the file. */
- static FILE *PrgFl; /* File pointer. */
-
- /* Trie data structure containing all the keywords and punctuation
- marks for
- * the language being tokenized.
- */
- static
- NODE T5[2]= {
- { ' ', 2, NULL },
- { 'n', ACTION, NULL }
- };
-
- static
- NODE T4[2] = {
- { ' ', 2, NULL },
- { 'o', 0, T5 }
- };
-
- static
- NODE T3[2] = {
- { ' ', 2, NULL },
-
- { 'i', 0, T4 }
- };
-
- static
- NODE T2[2] = {
- { ' ', 2, NULL },
- { 't', 0, T3 }
- };
-
- static
- NODE T8[2] = {
- { ' ', 2, NULL },
- { 'r', AFTER, NULL }
- };
-
- static
- NODE T7[2] = {
- { ' ', 2, NULL },
- ( 'e', 0, T8 }
- };
-
- static
- NODE T6[2] = {
- { ' ', 2, NULL },
- { 't', 0, T7 }
- };
-
- static
- NODE T9[2] = {
- { ' ', 2, NULL },
- { 'd', AND, NULL }
- };
-
- static
- NODE Te[2] = {
- { ' ', 2, NULL },
- { 'e', ARCHIVE, NULL }
- };
-
- static
- NODE Td[2] = {
- { ' ', 2, NULL },
- { 'v', 0, Te }
- };
-
- static
- NODE Tc[2] = {
- { ' ', 2, NULL },
- { 'i', 0, Td }
- };
-
- static
- NODE Tb[2] = {
- { ' ', 2, NULL },
- { 'h', 0, Tc }
- };
-
- static
- NODE Ta[2] = {
-
- { ' ', 2, NULL },
- { 'c', 0, Tb }
- };
-
- static
- NODE T16[2] = {
- { ' ', 2, NULL },
- { 's', ATTRIBUTES, NULL }
- };
-
- static
- NODE T15[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T16 }
- };
-
- static
- NODE T14[2] = {
- { ' ', 2, NULL },
- { 't', 0, T15 }
- };
-
- static
- NODE T13[2] = {
- { ' ', 2, NULL },
- { 'u', 0, T14 }
- };
-
- static
- NODE T12[2] = {
- { ' ', 2, NULL },
- { 'b', 0, T13 }
- };
-
- static
- NODE T11[2] = {
- { ' ', 2, NULL },
- { 'i', 0, T12 }
- };
-
- static
- NODE T10[2] = {
- { ' ', 2, NULL },
- { 'r', 0, T11 }
- };
-
- static
- NODE Tf[2] = {
- { ' ', 2, NULL },
- { 't', 0, T10 }
- };
-
- static
- NODE T1[6] = {
- { ' ', 6, NULL },
- { 'c', 0, T2 },
- { 'f', 0, T6 },
- { 'n', 0, T9 },
- { 'r', 0, Ta },
-
- { 't', 0, Tf }
- };
-
- static
- NODE T1b[2] = {
- { ' ', 2, NULL },
- { 'e', BEFORE, NULL }
- };
-
- static
- NODE T1a[2] = {
- { ' ', 2, NULL },
- { 'r', 0, T1b }
- };
-
- static
- NODE T19[2] = {
- { ' ', 2, NULL },
- { 'o', 0, T1a }
- };
-
- static
- NODE T18[2] = {
- { ' ', 2, NULL },
- { 'f', 0, T19 }
- };
-
- static
- NODE T17[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T18 }
- };
-
- static
- NODE T23[2] = {
- { ' ', 2, NULL },
- { 'y', DIRECTORY_T, NULL }
- };
-
- static
- NODE T22[2] = {
- { ' ', 2, NULL },
- { 'r', 0, T23 }
- };
-
- static
- NODE T21[2] = {
- { ' ', 2, NULL },
- { 'o', 0, T22 }
- };
-
- static
- NODE T20[2] = {
- { ' ', 2, NULL },
- { 't', 0, T21 }
- };
-
- static
- NODE T1f[2] = {
-
- { ' ', 2, NULL },
- { 't', 0, T20 }
- };
-
- static
- NODE T1e[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T1f }
- };
-
- static
- NODE T1d[2] = {
- { ' ', 2, NULL },
- { 'r', 0, T1e }
- };
-
- static
- NODE T1c[2] = {
- { ' ', 2, NULL },
- { 'i', 0, T1d }
- };
-
- static
- NODE T26[2] = {
- { ' ', 2, NULL },
- { 'c', EXEC, NULL }
- };
-
- static
- NODE T25[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T26 }
- };
-
- static
- NODE T24[2] = {
- { ' ', 2, NULL },
- { 'x', 0, T25 }
- };
-
- static
- NODE T2a[2] = {
- { ' ', 2, NULL },
- { 's', FILES, NULL }
- };
-
- static
- NODE T29[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T2a }
- };
-
- static
- NODE T28[2] = {
- { ' ', 2, NULL },
- { '1', 0, T29 }
- };
-
- static
-
- NODE T27[2] = {
- { ' ', 2, NULL },
- { 'i', 0, T28 }
- };
-
- static
- NODE T2f[2] = {
- { ' ', 2, NULL },
- { 'n', HIDDEN, NULL }
- };
-
- static
- NODE T2e[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T2f }
- };
-
- static
- NODE T2d[2] = {
- { ' ', 2, NULL },
- { 'd', 0, T2e }
- };
-
- static
- NODE T2c[2] = {
- { ' ', 2, NULL },
- { 'd', 0, T2d }
- };
-
- static
- NODE T2b[2] = {
- { ' ', 2, NULL },
- { 'i', 0, T2c }
- };
-
- static
- NODE T33[2] = {
- { ' ', 2, NULL },
- { 'l', LABEL, NULL }
- };
-
- static
- NODE T32[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T33 }
- };
-
- static
- NODE T31[2] = {
- { ' ', 2, NULL },
- { 'b', 0, T32 }
- };
-
- static
- NODE T30[2] = {
- { ' ', 2, NULL },
- { 'a', 0, T31 }
- };
-
-
- static
- NODE T3a[2] = {
- { ' ', 2, NULL },
- { 'd', MODIFIED, NULL }
- };
-
- static
- NODE T39[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T3a }
- };
-
- static
- NODE T38[2] = {
- { ' ', 2, NULL },
- { 'i', 0, T39 }
- };
-
- static
- NODE T37[2] = {
- { ' ', 2, NULL },
- { 'f', 0, T38 }
- };
-
- static
- NODE T36[2] = {
- { ' ', 2, NULL },
- { 'i', 0, T37 }
- };
-
- static
- NODE T35[2] = {
- { ' ', 2, NULL },
- { 'd', 0, T36 }
- };
-
- static
- NODE T34[2] = {
- { ' ', 2, NULL },
- { 'o', 0, T35 }
- };
-
- static
- NODE T3d[2] = {
- { ' ', 2, NULL },
- { 'e', NAME, NULL }
- };
-
- static
- NODE T3c[2] = {
- { ' ', 2, NULL },
- { 'm', 0, T3d }
- };
-
- static
- NODE T3e[2] = {
- { ' ', 2, NULL },
- { 't', NOT, NULL }
- };
-
-
- static
- NODE T3b[3] = {
- { ' ', 3, NULL },
- { 'a', 0, T3c },
- { 'o', 0, T3e }
- };
-
- static
- NODE T3f[2] = {
- { ' ', 2, NULL },
- { 'r', OR, NULL },
- };
-
- static
- NODE T43[2] = {
- { ' ', 2, NULL },
- { 't', PRINT, NULL }
- };
-
- static
- NODE T42[2] = {
- { ' ', 2, NULL },
- { 'n', 0, T43 }
- };
-
- static
- NODE T41[2] = {
- { ' ', 2, NULL },
- { 'i', 0, T42 }
- };
-
- static
- NODE T40[2] = {
- { ' ', 2, NULL },
- { 'r', 0, T41 }
- };
-
- static
- NODE T4a[2] = {
- { ' ', 2, NULL },
- { 'y', READONLY, NULL }
- };
-
- static
- NODE T49[2] = {
- { ' ', 2, NULL },
- { 'l', 0, T4a }
- };
-
- static
- NODE T48[2] = {
- { ' ', 2, NULL },
- { 'n', 0, T49 }
- };
-
- static
- NODE T47[2] = {
- { ' ', 2, NULL },
-
- { 'o', 0, T48 }
- };
-
- static
- NODE T46[2] = {
- { ' ', 2, NULL },
- { 'd', 0, T47 }
- };
-
- static
- NODE T4d[2] = {
- { ' ', 2, NULL },
- { 's', RECURS, NULL }
- };
-
- static
- NODE T4c[2] = {
- { ' ', 2, NULL },
- { 'r', 0, T4d }
- };
-
- static
- NODE T4b[2] = {
- { ' ', 2, NULL },
- { 'u', 0, T4c }
- };
-
- static
- NODE T45[3] = {
- { ' ', 3, NULL },
- { 'a', 0, T46 },
- { 'c', 0, T4b }
- };
-
- static
- NODE T44[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T45 }
- };
-
- static
- NODE T52[2] = {
- { ' ', 2, NULL },
- { 'h', SEARCH, NULL }
- };
-
- static
- NODE T51[2] = {
- { ' ', 2, NULL },
- { 'c', 0, T52 }
- };
-
- static
- NODE T50[2] = {
- { ' ', 2, NULL },
- { 'r', 0, T51 }
- };
-
- static
-
- NODE T55[2] = {
- { ' ', 2, NULL },
- { 't', SELECT, NULL }
- };
-
- static
- NODE T54[2] = {
- { ' ', 2, NULL },
- { 'c', 0, T55 }
- };
-
- static
- NODE T53[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T54 }
- };
-
- static
- NODE T4f[3] = {
- { ' ', 2, NULL },
- { 'a', 0, T50 },
- { 'l', 0, T53 }
- };
-
- static
- NODE T59[2] = {
- { ' ', 2, NULL },
- { 'm', SYSTEM, NULL }
- };
-
- static
- NODE T58[2] = {
- { ' ', 2, NULL },
- { 'e', 0, T59 }
- };
-
- static
- NODE T57[2] = {
- { ' ', 2, NULL },
- { 't', 0, T58 }
- };
-
- static
- NODE T56[2] = {
- { ' ', 2, NULL },
- { 's', 0, T57 }
- };
-
- static
- NODE T4e[3] = {
- { ' ', 3, NULL },
- { 'e', 0, T4f },
- { 'y', 0, T56 }
- };
-
- static
- NODE T0[21] = {
- { ' ', 21, NULL },
- { '(', L_PAREN, NULL },
-
- { ')', R_PAREN, NULL },
- { ',', COMMA, NULL },
- { '/', F_SLASH, NULL },
- { 'a', 0, T1 },
- { 'b', 0, T17 },
- { 'd', 0, T1c },
- { 'e', 0, T24 },
- { 'f', 0, T27 },
- { 'h', 0, T2b },
- { '1', 0, T30 },
- { 'm', 0, T34 },
- { 'n', 0, T3b },
- { 'o', 0, T3f },
- { 'p', 0, T40 },
- { 'r', 0, T44 },
- { 's', 0, T4e },
- { '{', L_BRACE, NULL },
- { '', BAR, NULL },
- { '}', R_BRACE, NULL }
- };
-
- /*----------------------------------------------------------
- Routine : OpenPrg() --- Open the ASCII text file
- that contains the back up program.
-
- Inputs : FileNm - File name of source file.
- ----------------------------------------------------------*/
-
- void OpenPrg(char *FileNm)
- {
- /* Open the program script file. */
- if ((PrgFl = fopen(FileNm, "rt")) == NULL)
- {
- fprintf(stderr, "OpenPrg (fopen) : Could not open
- file '%s' for "
- "reading.\n", FileNm);
- exit( -1 );
- }
-
- /* Initialize object variables. */
- *word = *PrvWd = '\0';
- }
-
- /*----------------------------------------------------------------------
- Routine : ParsErr() --- Report a parse error.
-
- Inputs : Err - Error string.
- ----------------------------------------------------------------------*/
-
- void ParsErr(char *Err)
- {
- /* Print line number and error massage. */
- fprintf(stderr, "Error in Line: %d, %s.\n", LnNo + 1, Err);
-
- /* If there is a previous word, show it. */
- if ( *word )
- fprintf(stderr, "\t0n or after word '%s'\n", word);
- exit( -1 );
- }
-
-
- /*---------------------------------------------------------------------
- Routine : TrieSrch() --- Search the trie for a key word.
-
- Inputs : Trie - The trie level pointer.
- ch - The current character to search for.
- WordPtr - The pointer to the current byte of the word buffer.
- Returns : Returns either a token value or
- NOT_FND - For key word not found.
- EOF - For end of file.
- ----------------------------------------------------------------------*/
-
- static
- int TrieSrch(NODE *Trie,
- int ch,
- char *WordPtr)
- {
- register int mid; /* Mid point of array piece. */
- register TKNS ret; /* Return value of comparison. */
-
- auto int lo; /* Limits of current array piece for */
- auto int hi; /* binary search. */
-
- /* Make sure that input is lower case. */
- ch = tolower( ch );
-
- /* Search for a token. */
- hi = Trie[0].token - 1;
- lo = 1;
- do
- {
- /* Find mid point of current array piece. */
- mid = (lo + hi) >> 1;
-
- /* Do character comparison. */
- ret = ch - Trie[mid].c;
-
- /* Fix the array limits. */
- if (ret <= 0)
- hi = mid - 1;
- if (ret >= 0)
- lo = mid + 1;
-
- } while (hi >= lo);
-
- /* If the character matches one of the entries in this level and this
- * entry has a child, recurse. If a match is found but the matching
- * entry has no child, return the token value associated with the
- * match. If the return value from the recursive call indicates that
- * no match was found at a lower level, return the token value
- * associated with the match at this level of the trie.
- */
- if (ret == 0)
- {
- /* Save the current character in the buffer for error reporting. */
- *WordPtr++ = ch;
-
- /* Are we looking for more characters in this string? */
- if ( Trie[mid].child )
-
- {
- /* Get the next character. */
- if ((ch = fgetc( PrgFl )) == EOF)
- return( EOF );
-
- /* Search next level. */
- if ((ret = TrieSrch(Trie[mid].child, ch, WordPtr)) == NOT_FND)
- {
- ungetc(ch, PrgFl);
- return( Trie[mid].token );
- }
- return( ret );
- }
- else
- {
- /* Properly NUL terminate the buffer that the keyword is
- * being saved in and return the token value.
- */
- *WordPtr = '\0';
- return( Trie[mid].token );
- }
- }
-
- /* Terminate string in keyword buffer and return not found. */
- *WordPtr = '\0';
- return( NOT_FND );
- }
-
- /*----------------------------------------------------------------------
- Routine : GetNo --- Get a number from the file.
-
- Inputs : word - Pointer to word buffer for error reporting.
- Outputs : RetNo - Returns the number read from the file.
-
- Returns : Returns the last character read from the file or EOF.
- ----------------------------------------------------------------------*/
-
- static
- int GetNo(char **word,
- long *RetNo)
- {
- auto int c;
-
- /* Get number. */
- *RetNo = OL;
- while ((c = fgetc ( PrgFl )) >= '0' && c <= '9')
- {
- /* Save character in word buffer. */
- *(*word)++ = c;
-
- /* Calculate value of number. */
- *RetNo = *RetNo * 10L + (long) (c - '0');
- }
- return( c );
- }
-
- /*----------------------------------------------------------------------
- Routine : Lex() --- Get the next key word from the input file.
-
-
- Outputs : sym - The symbolic data read from the file.
-
- Returns : Returns the token read or EOF.
- ----------------------------------------------------------------------*/
-
- TKNS Lex(TOKEN *sym)
- {
- register int i;
- register int tkn;
- auto int ch;
- auto char *bf;
-
- /* Strip comments and white space. If the character read is a '#',
- * every thing to the end of the line is a comment.
- */
- ch = fgetc( PrgFl );
- while (ch == ' ' ch == '\t' ch == '\n' ch == '#')
- {
- /* Process the special characters '#' and '\n'. */
- if (ch == '\n')
- /* End of line, increment the line number. */
- LnNo++;
- else if (ch == '#')
- {
- /* Found a comment character, strip all characters to end
- * of line and increment the line number.
- */
- while (fgetc( PrgFl ) != '\n')
- ;
- LnNo++;
- }
-
- /* Get the next character. */
- ch = fgetc( PrgFl );
- }
-
- /* Get strings, etc. */
- if (ch == ' " ')
- {
-
- /* Get contents of string. */
- bf = sym->str;
- for (i = 0; i < MAX_STR; i++)
- if ((ch = fgetc( PrgFl )) != ' " ' && ch != EOF)
- *bf++ = ch;
- else
- break;
- *bf = '\0';
-
- /* Return string token. */
- strcpy(word, sym->str);
- return( STRING );
- }
- else if (ch >= '0' && ch <= '9')
- {
- auto long no;
-
- /* Establish a pointer to the word buffer and unget the
- * numeric character for re-reading.
-
- */
- bf = word;
- ungetc(ch, PrgFl);
-
- /* Get number, time or date. */
- if ((ch = GetNo(&bf, &no)) == ':')
- {
- /* Getting time, not number. */
- *bf++ = ch;
-
- sym->ftime.ti_hour = (unsigned char) no;
- sym->ftime.ti_hund = (unsigned char) 0;
-
- /* Get minutes. */
- if ((ch = GetNo(&bf, &no)) == ':')
- {
- /* Save minutes. */
- *bf++ = ch;
- sym->ftime.ti_min = (unsigned char) no;
-
- /* Get seconds. */
- if ((ch = GetNo(&bf, &no)) == '.')
- {
- *bf = '\0';
- ParsErr( "Hundredths of seconds not allowed in "
- "time expressions" );
- }
- sym->ftime.ti_sec = (unsigned char) no;
- }
- else
- {
- /* No seconds to get. */
- sym->ftime.ti_min = (unsigned char) no;
- sym->ftime.ti_sec = (unsigned char) 0;
- }
-
- /* This is a time. */
- tkn = TIME;
- }
- else if (ch == '/')
- {
- /* Getting date, not number. */
- *bf++ = ch;
- sym->fdate.da_mon = (char) no;
-
- /* Get day. */
- if ((ch = GetNo(&bf, &no)) == '/')
- {
- /* Save character. */
- *bf++ = ch;
- sym->fdate.da_day = (char) no;
-
- /* Get year. */
- ch = GetNo(&bf, &no);
- if (no > 1980L)
- no -= 1980L;
- else if (no > 80L && no < 100L)
- no -= 80L;
- else
-
- {
- *bf = '\0';
- ParsErr( "Error, bad year value in date expression." );
- }
- sym->fdate.da_year = (int) no;
- }
- else
- {
- *bf = '\0';
- ParsErr( "Missing year in date expression" );
- }
-
- /* This is a date. */
- tkn = DATE;
- }
- else
- {
- /* Just an integer constant. */
- sym->no = no;
- tkn = NUMBER;
- }
-
- /* Return the unused character. */
- *bf = '\0';
- ungetc(ch, PrgFl);
- return( tkn );
- }
- else if (ch == EOF)
- return( EOF );
-
- /* Call the trie search routine to return the next token, EOF
- * or NOT_FND. If not found, print an error and quit.
- */
- if ((tkn = TrieSrch(TO, ch, word)) == NOT_FND)
- {
- /* Illegal first character in word. */
- if ( *PrvWd )
- fprintf(stderr, "Parse - Error in Line: %d, cannot identify "
- "word after '%s'.\n", LnNo + 1, PrvWd);
- else
- fprintf(stderr, "Parse - Error in Line: %d, cannot identify "
- "first word in file.\n", LnNo + 1);
- exit( -1 );
- }
- else if (tkn == 0)
- {
- /* Illegal word. */
- fprintf(stderr, "Parse - Error in Line: %d, cannot identify "
- "word '%s'.\n", LnNo + 1, word);
- exit( -1 );
- }
- strcpy(PrvWd, word);
-
- /* Return the token found. */
- return( tkn );
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Standard C
-
-
- The Header <stdlib.h>
-
-
-
-
- P.J. Plauger
-
-
- P.J. Plauger is senior editor of The C Users Journal. He is secretary of the
- ANSI C standards committee, X3J11, and convenor of the ISO C standards
- committee, WG14. His latest book is The Standard C Library, published by
- Prentice-Hall. You can reach him care of The C Users Journal or via Internet
- at pjp@plauger.uunet.uu.net.
-
-
-
-
- Introduction
-
-
- The header <stdlib.h> is a hodgepodge. Committee X3J11 invented this header as
- a place to define macros and declare functions that had no other sensible
- home:
- Many existing functions, such as abs and malloc, had no traditional headers to
- declare them. X3J11 felt strongly that every functions should be declared in a
- standard header. If such a function seemed out of place in all other headers,
- it ended up declared in <stdlib.h>.
- New groups of macros and functions ended up in new standard headers wherever
- possible. <float.h> and <locale.h> are clear examples. Additions to existing
- groups ended up in existing headers. strcoll, declared in <string.h>, and
- strftime, declared in <time.h>, are also fairly clear. Other macros and
- functions are harder to categorize. These ended up defined or declared in
- <stdlib.h>.
- This header is not the only hodgepodge. I discuss the evolution of the header
- <stddef.h> in Standard C, CUJ December 1991.
- To provide some structure, I organize the functions into six groups:
- integer math (abs, div, labs, and ldiv) -- performing simple integer
- arithmetic
- algorithms (bsearch, qsort, rand, and srand) -- capturing operations complex
- and widespread enough to warrant packaging as library functions
- text conversions (atof, atoi, atol, strtod, strtol, and strtoul) --
- determining encoded arithmetic values from text representations
- multibyte conversions (mblen, mbstowcs, mbtowc, wcstombs, and wctomb) mapping
- between multi-byte and wide-character encodings
- storage allocation (calloc, free, malloc, and realloc) -- managing a heap of
- data objects
- environmental interactions (abort, atexit, exit, getenv, and system) --
- interfacing between the program and the execution environment
- I discuss separately how to implement the functions in each of these groups.
- This month, I cover only the first two groups. I won't bother to present the
- header as a whole. It's pretty straightforward.
-
-
- The C Standard on Integer Math
-
-
-
-
- 7.10.6 Integer arithmetic functions
-
-
-
-
- 7.10.6.1 The abs function
-
-
-
-
- Synopsis
-
-
- #include <stdlib.h>
- int abs(int j);
-
-
- Description
-
-
-
- The abs function computes the absolute value of an integer j. If the result
- cannot be represented, the behavior is undefined.130 [FN130. The absolute
- value of the most negative number cannot be represented in two's complement.]
-
-
- Returns
-
-
- The abs function returns the absolute value.
-
-
- 7.10.6.2 The div function
-
-
-
-
- Synopsis
-
-
- #include <stdlib.h>
- div_t div(int numer, int denom);
-
-
- Description
-
-
- The div function computes the quotient and remainder of the division of the
- numerator numer by the denominator denom. If the division is inexact, the
- resulting quotient is the integer of lesser magnitude that is the nearest to
- the algebraic quotient. If the result cannot be represented, the behavior is
- undefined; otherwise, quot * denom + rem shall equal numer.
-
-
- Returns
-
-
- The div function returns a structure of type div_t, comprising both the
- quotient and the remainder. The structure shall contain the following members,
- in either order:
- int quot; /* quotient */
- int rem; /* remainder */
-
-
- 7.10.6.3 The labs function
-
-
-
-
- Synopsis
-
-
- #include <stdlib.h>
- long int labs(long int j);
-
-
- Description
-
-
- The labs function is similar to the abs function, except that the argument and
- the returned value each have type long int.
-
-
- 7.10.6.4 The ldiv function
-
-
-
-
-
- Synopsis
-
-
- #include <stdlib.h>
- ldiv_t ldiv(long int numer, long int denom);
-
-
- Description
-
-
- The ldiv function is similar to the div function, except that the arguments
- and the members of the returned structure (which has type ldiv_t) all have
- type long int.
-
-
- Using the Integer Arithmetic Functions
-
-
- Here is a brief summary of the four arithmetic functions: abs -- Call abs(x)
- instead of writing the idiom x < 0 ? -x : x. A growing number of Standard C
- translators generate inline code for abs that is smaller and faster than the
- idiom. In addition, you avoid the occasional surprise when you inadvertently
- evaluate twice an expression with side effects. Note that on a
- two's-complement machine, abs can generate an overflow.
- div -- You call div for one of two reasons:
- div always computes a quotient that truncates toward zero, along with the
- corresponding remainder, regardless of how the operators / and % behave in a
- given implementation. This can be important when one of the operands is
- negative. The expression (-3)/2 can yield either -2 or -1, while div(-3,
- 2).quot always yields -1. Similarly, (-3)%2 can yield either 1 or -1, while
- div(-3, 2).rem always yields -1.
- div computes both the quotient and remainder at the same time. That can be
- handy when you need both results. It might even be more efficient if the
- function expands to inline code that contains only a single divide.
- Note that the members of the resulting structure type div_t can occur in
- either order. Don't make any assumptions about the representation of this
- structure.
- labs -- is the long version of abs.
- ldiv -- is the long version of div.
-
-
- Implementing the Arithmetic Functions
-
-
- Listing 1 shows the file abs.c. The absolute value function abs is the
- simplest of the integer math functions. You cannot provide a masking macro,
- however, because you have to access the value of the argument twice. Some
- computer architectures have special instructions for computing the absolute
- value. That makes abs a prime candidate for special treatment as a built-in
- function generating inline code.
- Listing 2 shows the file div.c. It provides a portable implementation of the
- div function. You can eliminate the test if you know that negative quotients
- truncate toward zero. Most computer architectures have a divide instruction
- that develops both quotient and remainder at the same time. Those that develop
- proper negative quotients are also candidates for built-in functions. An
- implementation is at liberty to reorder the members of the structure type
- div_t to match what the hardware generates.
- Listing 3 shows the file labs.c and Listing 4 shows the file ldiv.c. Both
- define functions that are simply long versions of abs and div.
-
-
- The C Standard on the Algorithmic Functions
-
-
-
-
- 7.10.2 Pseudo-random sequence generation functions
-
-
-
-
- 7.10.2.1 The rand function
-
-
-
-
- Synopsis
-
-
- #include <stdlib.h>
- int rand(void);
-
-
- Description
-
-
- The rand function computes a sequence of pseudo-random integers in the range 0
- to RAND_MAX.
-
- The implementation shall behave as if no library function calls the rand
- function.
-
-
- Returns
-
-
- The rand function returns a pseudo-random integer.
-
-
- Environmental Limit
-
-
- The value of the RAND_MAX macro shall be at least 32767.
-
-
- 7.10.2.2 The srand function
-
-
-
-
- Synopsis
-
-
- #include <stdlib.h>
- void srand(unsigned int seed);
-
-
- Description
-
-
- The srand function uses the argument as a seed for a new sequence of
- pseudo-random numbers to be returned by subsequent calls to rand. If srand is
- then called with the same seed value, the sequence of pseudo-random numbers
- shall be repeated. If rand is called before any calls to srand have been made,
- the same sequence shall be generated as when srand is first called with a seed
- value of 1.
- The implementation shall behave as if no library function calls the srand
- function.
-
-
- Returns
-
-
- The srand function returns no value.
-
-
- Example
-
-
- The following functions define a portable implementation of rand and srand.
- static unsigned long int next = 1;
- int rand(void) /* RAND_MAX assumed to be 32767 */
- {
- next = next * 1103515245 + 12345;
- return (unsigned int)
- next/65536) % 32768;
- }
- void srand(unsigned int seed)
- {
- next = seed;
- }
-
-
- 7.10.5 Searching and sorting utilities
-
-
-
-
-
- 7.10.5.1 The bsearch function
-
-
-
-
- Synopsis
-
-
- #include <stdlib.h>
- void *bsearch(const void *key,
- const void *base, size_t nmemb,
- size_t size, int (*compar
- const void *, const void *));
-
-
- Description
-
-
- The bsearch function searches an array of nmemb objects, the initial element
- of which is pointed to by base, for an element that matches the object pointed
- to by key. The size of each element of the array is specified by size.
- The comparison function pointed to by compar is called with two arguments that
- point to the key object and to an array element, in that order. The function
- shall return an integer less than, equal to, or greater than zero if the key
- object is considered, respectively, to be less than, to match, or to be
- greater than the array element. The array shall consist of: all the elements
- that compare less than, all the elements that compare equal to, and all the
- elements that compare greater than the key object, in that order.129 [FN129.
- In practice, the entire array is sorted according to the comparison function.]
-
-
- Returns
-
-
- The bsearch function returns a pointer to a matching element of the array, or
- a null pointer if no match is found. If two elements compare as equal, which
- element is matched is unspecified.
-
-
- 7.10.5.2 The qsort function
-
-
-
-
- Synopsis
-
-
- #include <stdlib.h>
- void qsort(void *base, size_t nmemb, size_t size,
- int (*compar)(const void *, const void *));
-
-
- Description
-
-
- The qsort function sorts an array of nmemb objects, the initial element of
- which is pointed to by base. The size of each object is specified by size.
- The contents of the array are sorted into ascending order according to a
- comparison function pointed to by compar, which is called with two arguments
- that point to the objects being compared. The function shall return an integer
- less than, equal to, or greater than zero if the first argument is considered
- to be respectively less than, equal to, or greater than the second.
- If two elements compare as equal, their order in the sorted array is
- unspecified.
-
-
- Returns
-
-
- The qsort function returns no value.
-
-
- Using the Algorithmic Functions
-
-
-
- Here is a brief summary of the four algorithmtic functions:
- bsearch -- Use this function to search any array whose elements are ordered by
- pairwise comparisons. You define the ordering with a comparison function that
- you provide. For example, you can build a keyword lookup function from the
- basic form as shown in Listing 5.
- A few caveats:
- If a key compares equal to two or more elements, bsearch can return a pointer
- to any of these elements.
- Beware of changes in how elements sort when the execution character set
- changes -- call qsort, described below, with a compatible comparison function
- to ensure that an array is properly ordered.
- Be careful using the functions strcmp or strcoll, declared in <string.h>,
- directly. Both require that strings be stored in the array to be searched. You
- cannot use them to search an array of pointers to strings. To use strcmp, for
- example, you must write a function pointer argument that looks like (int
- (*)(const void *, const void *))&strcmp.
- qsort -- Use this function to sort any array whose elements are ordered by
- pairwise comparisons. You define the ordering with a comparison function that
- you provide. The comparison function has a specification similar to that for
- the function bsearch, described above. Note, however, that the bsearch
- comparison function compares a key to an array element. The sort comparison
- function compares two array elements.
- A few caveats:
- Don't assume that the function uses the "Quicksort" algorithm, despite the
- name. It may not. If two or more elements compare equal, qsort can leave these
- elements in any relative order. Hence, qsort is not a stable sort.
- Beware of changes in how elements sort when the execution character set
- changes.
- Be careful using the functions strcmp or strcoll declared in <string.h>,
- directly. Both require that strings be stored in the array to be sorted. You
- cannot use them to sort an array of pointers to strings. To use strcmp, for
- example, you must write a function pointer argument that looks like (int
- (*)(const void *, const void *))&strcmp.
- rand -- Call rand to obtain the next value in a pseudo-random sequence. You
- get exactly the same sequence following each call to srand with a given
- argument value. That is often desirable behavior, particularly when you are
- debugging a program. If you want less predictable behavior, call clock or
- time, declared in <time.h> to obtain an argument for srand. The behavior of
- rand can vary among implementations. If you want exactly the same
- pseudo-random sequence at all times, copy the version presented here.
- Use RAND_MAX to scale values returned from rand. For example, if you want
- random numbers of type float distributed over the interval [0.0, 1.0], write
- the expression (float)rand()/RAND_MAX. The value of RAND_MAX is at least
- 32,767.
- srand -- See rand above. The program effectively calls srand(1) at program
- startup.
-
-
- Implementing the Algorithmic Functions
-
-
- Listing 6 shows the file qsort.c. It defines the related function qsort that
- sorts an array beginning at base. I introduced the type _Cmpfun just to
- simplify the declaration of arguments for the functions bsearch and qsort.
- Don't use this declaration in code that you write if you want it to be
- portable to other implementations.
- This logic is much less simple and more debatable. It is based on the
- Quicksort algorithm first developed by C.A.R. Hoare. That requires you to pick
- a partition element, then partially sort the array about this partition. You
- can then sort each of the two partitions by recursive application of the same
- technique. The algorithm can sort quite rapidly. It can also sort very slowly.
- How best to choose the pivot element is the debatable issue. Pick the first
- element and an array already in sort eats a lot of time. Pick the last element
- and an array in reverse sort eats a lot of time. Work too hard at picking an
- element and all arrays eat a lot of time. I chose simply to pick the last
- element. That favors arrays that need little rearranging. You may have reason
- to choose another approach.
- qsort calls itself to sort the smaller of the two partitions. It loops
- internally to sort the larger of the two. That minimizes demands on dynamic
- storage. At worst, each recursive call must sort an array half as big as the
- earlier call. To sort N elements requires recursion no deeper than log2(N)
- calls. (You can sort 1,000,000 elements with at most 20 recursive calls.)
- Listing 7 shows the file bsearch.c. The function bsearch performs a binary
- search on the sorted array beginning at base. The logic is simple but easy to
- get wrong.
- Listing 8 shows the file rand.c. The function rand generates a pseudo-random
- sequence using the algorithm suggested in the C Standard. That has reasonable
- properties, plus the advantage of being widely used. One virtue of a random
- number generator is randomness. Another virtue, ironically, is
- reproducibility. You often need to check that a calculation based on
- pseudo-random numbers does what you expect. The arithmetic is performed using
- unsigned long integers to avoid overflows.
- Listing 9 shows the file srand.c. The function srand simply sets _Randseed,
- the seed for the pseudo-random sequence generated by rand. I provide a masking
- macro for srand. Hence, the header <stdlib.h> declares _Randseed, defined in
- rand.c.
- References
- Donald Knuth, The Art of Computer Programming, Vols. 1-3 (Reading, Mass.:
- Addison-Wesley, 1967 and later). Here is a rich source of algorithms, complete
- with analysis and tutorial introductions. Volume 1 is Fundamental Algorithms,
- volume 2 is Seminumerical Algorithms, and volume 3 is Sorting and Searching.
- Some are in second edition.
- You will find oodles of information on:
- maintaining a heap
- computing random numbers
- searching ordered sequences
- sorting
- converting between different numeric bases
- Before you tinker with the code presented here, see what Knuth has to say.
- This article is excerpted in part from P.J. Plauger, The Standard C Library,
- (Englewood Cliffs, N.J.: Prentice-Hall, 1992).
-
- Listing 1 (abs.c)
- /* abs function */
- #include <stdlib.h>
-
- int (abs)(int i)
- { /* compute absolute value of int argument */
- return ((i < 0) ? -i : i);
- }
-
- /* End of File */
-
-
- Listing 2 (div.c)
- /* div function */
- #include <stdlib.h>
-
- div_t (div)(int numer, int denom)
- { /* compute int quotient and remainder */
- div_t val;
-
-
- val.quot = numer / denom;
- val.rem = numer - denom * val.quot;
- if (val.quot < 0 && 0 < val.rem)
- { /* fix remainder with wrong sign */
- val.quot += 1;
- val.rem -= denom;
- }
- return (val);
- }
-
- /* End of File */
-
-
- Listing 3 (labs.c)
- /* labs function */
- #include <stdlib.h>
-
- long (labs)(long i)
- { /* compute absolute value of long argument */
- return ((i < 0) ? -i : i);
- }
-
- /* End of File */
-
-
- Listing 4 (ldiv.c)
- /* ldiv function */
- #include <stdlib.h>
-
- ldiv_t (ldiv)(long numer, long denom)
- { /* compute long quotient and remainder */
- ldiv_t val;
-
- val.quot = numer / denom;
- val.rem = numer - denom * val.quot;
- if (val.quot < 0 && 0 < val.rem)
- { /* fix remainder with wrong sign */
- val.quot += 1;
- val.rem -= denom;
- }
- return (val);
- }
-
- /* End of File */
-
-
- Listing 5
- #include <stdlib.h>
- #include <string.h>
-
- typedef enum {FLOAT, INTEGER} Code;
- typedef struct {
- char *s;
- Code code;
- } Entry;
- Entry symtab[] = {
- {"float", FLOAT},
- {"integer", INTEGER}}
-
-
- static int cmp(const void *ck, const void *ce)
- { /* compare key to table element */
- return (strcmp((const char *)ck, ((Entry *)ce)->s));
- }
-
- Entry *lookup(char *key)
- { /* lookup key in table */
- return (bsearch(key, symtab,
- sizeof symtab / sizeof symtab[0],
- sizeof symtab[0], &cmp));
- }
- /* End of File */
-
-
- Listing 6 (qsort.c)
- /* qsort function */
- #include <stdlib.h>
- #include <string.h>
-
- /* macros */
- #define MAX_BUF256 /* chunk to copy on swap */
-
- void (qsort)(void *base, size_t n, size_t size, _Cmpfun *cmp)
- { /* sort (char base[size])[n] using quicksort */
- while (1 < n)
- { /* worth sorting */
- size_t i = 0;
- size_t j = n - 1;
- char *qi = (char *)base;
- char *qj = qi + size * j;
- char *qp = qj;
-
- while (i < j)
- { /* partition about pivot */
- while (i < j && (*cmp)(qi, qp) <= 0)
- ++i, qi += size;
- while (i < j && (*cmp)(qp, qj) <= 0)
- --j, qj -= size;
- if (i < j)
- { /* swap elements i and j */
- char buf[MAX_BUF];
- char *q1 = qi;
- char *q2 = qj;
- size_t m, ms;
-
- for (ms = size; 0 < ms;
- ms -= m, q1 += m, q2 -= m)
- { /* swap as many as possible */
- m = ms < sizeof (bur) ? ms : sizeof (buf);
- memcpy(buf, q1, m);
- memcpy(q1, q2, m);
- memcpy(q2, buf, m);
- }
- ++i, qi += size;
- }
- }
- if (qi != qp)
- { /* swap elements i and pivot */
- char buf[MAX_BUF];
-
- char *q1 = qi;
- char *q2 = qp;
- size_t m, ms;
-
- for (ms = size; 0 < ms; ms -= m, q1 += m, q2 -= m)
- { /* swap as many as possible */
- m = ms < sizeof (buf) ? ms : sizeof (buf);
- memcpy(buf, q1, m);
- memcpy(q1, q2, m);
- memcpy(q2, buf, m);
- }
- }
- j = n - i - 1, qi += size;
- if (j < i)
- { /* recurse on smaller partition */
- if (1 < j)
- qsort(qi, j, size, cmp);
- n = i;
- }
- else
- { /* lower partition is smaller */
- if (1 < i)
- qsort(base, i, size, cmp);
- base = qi;
- n = j;
- }
- }
- }
-
- /* End of File */
-
-
- Listing 7 (bsearch.c)
- /* bsearch function */
- #include <stdlib.h>
-
- void *(bsearch)(const void *key, const void *base,
- size_t nelem, size_t size, _Cmpfun *cmp)
- { /* search sorted table by binary chop */
- const char *p;
- size_t n;
-
- for (p = (const char *)base, n = nelem; 0 < n; )
- { /* check midpoint of whatever is left */
- const size_t pivot = n > 1;
- const char *const q = p + size * pivot;
- const int val = (*cmp)(key, q);
-
- if (val 0)
- n = pivot; /* search below pivot */
- else if (val == 0)
- return ((void *)q); /* found */
- else
- { /* search above pivot */
- p = q + size;
- n -= pivot + 1;
- }
- }
- return (NULL);/* no match */
-
- }
-
- /* End of File */
-
-
- Listing 8 (rand.c)
- /* rand function */
- #include <stdlib.h>
-
- /* the seed */
- unsigned long_Randseed = 1;
-
- int (rand)(void)
- { /* compute pseudo-random value */
- _Randseed = _Randseed * 1103515245 + 12345;
- return ((unsigned int)(_Randseed >> 16) & RAND_MAX);
- }
-
- /* End of File */
-
-
- Listing 9 (srand.c)
- /* srand function */ #include <stdlib.h>
-
- void (srand)(unsigned int seed)
- { /* alter the seed */
- _Randseed = seed;
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Doctor C's Pointers(R)
-
-
- Data Structures, Part 11: Yet More on Stacks
-
-
-
-
- Rex Jaeschke
-
-
- Rex Jaeschke is an independent computer consultant, author, and seminar
- leader. He participates in both ANSI and ISO C Standards meetings and is the
- editor of The Journal of C Language Translation, a quarterly publication aimed
- at implementors of C language translation tools. Readers are encouraged to
- submit column topics and suggestions to Rex at 2051 Swans Neck Way, Reston, VA
- 22091 or via UUCP at rex@aussie.com.
-
-
-
-
- Handling Multiple Stacks of the Same Type
-
-
- Last month's column described how to implement a stack and to use push and pop
- functions on it. However, it would be a waste to have a different set of push
- and pop functions for each stack. This month I will discuss how to share the
- code.
- Consider Listing 1. An object of type stack contains the current context of a
- given stack. This context includes the stack's name (for debugging or
- trace-back purposes), the base address of the stack, the stack's size and its
- current stack pointer. The stack descriptors stack1, stack2, and stack3
- therefore describe the three different int stacks. The first stack is stored
- in a global array, the second in a file scope static array, while the third is
- allocated at runtime using malloc. In short, the stack management functions
- don't know and don't care where the stacks reside.
- Listing 2 tells push and pop which stack to use but the notation is not
- particularly unwieldy. Listing 3 could be made a little bit cleaner by passing
- the stack descriptor by value, but that could be just a little more expensive
- since the descriptor is a structure. The output produced is shown in Figure 1.
-
-
- Handling Multiple Stacks of Different Types
-
-
- Can this idea be extended to manage stacks of different types? The answer is a
- qualified yes. One way could be to use generic pointers, as shown in Listing
- 4. Here, a void pointer is used to hold the stack's base address. This also
- requires an extra member to indicate the type of elements in any given stack,
- as in
- void push(stack *, void *);
- void *pop(stack *);
- The interface to push and pop gets very messy, however. Since an object of
- arbitrary type cannot be passed by value, it must be passed by assigning it to
- a named object and then passing that object by address, as shown in Listing 5.
- (This eliminates the ability to push a constant directly.) This is possible
- since all data pointer types are compatible with void *. Similarly, an
- arbitrary typed value can't be returned, so the address is returned instead.
- To use the value returned by pop you must use an explicit cast as well as a
- dereference since pop returns a pointer (see Listing 6). In fact, since pop
- returns the address of the object just popped from the stack, if you don't
- dereference the pointer returned immediately, the location it points to will
- be overwritten by the very next push and the value popped will change.
- The source for push and pop is far from pretty. Since it cannot perform
- arithmetic (via subscripting) on void pointers, you must explicitly provide
- the scaling factor.
-
-
- Using Unions
-
-
- Another way to look at the problem is rather than have stacks of different
- types, have one stack that can handle objects of different types. You can
- achieve this via a union, as shown in Listing 7.
- Each entry is a union of all the possible types along with a flag member that
- indicates which type this entry currently represents. We push nodes by value
- and return them by value using simple and obvious notation. If you try to pop
- from an empty stack, instead of complaining, pop returns a copy of a local
- node that has a special type field value of Error.
- Next, consider Listing 8. Note the interesting controlling expression in the
- while loop -- it uses the comma operator in an effective manner.
- The stack management functions presented in Listing 9 are quite
- straightforward.
-
-
- Opposing Stacks
-
-
- A stack grows and shrinks from one end only so it is possible to have two
- stacks based at opposite ends of an array with their tops growing toward each
- other. This can save space if both stacks don't grow very large at the same
- time. However, when one is smaller, the other can grow larger and vice-versa.
- The dump_stack function in Listing 10 allows us to see the contents of the
- underlying array as the two stacks grow and shrink. Note that it pops in a
- different order than it pushes, so the operations are staggered.
- The stack in Listing 11 can only handle four elements. This helps you monitor
- the progress as elements are pushed and popped. Clearly, it would be larger in
- a real application. Of course, the bases of the two stacks are at either end
- of the underlying array and in one stack the stack pointer increments as we
- push and in the other it decrements. Stack overflow is detected when the two
- stack pointers bump into each other. Note that it's OK if both of them point
- to the same element since that last free element is available to which ever
- stack can use it first.
- The two versions of push and pop in Listing 12 are different enough that it is
- not obvious you can write a single version that is both elegant and efficient.
- The output produced is shown in Figure 2.
- Note that even after a value is popped from a stack it still remains there --
- only the stack pointer is adjusted. This is exactly what happens when a C
- function returns; the values of it's automatic variables are still on the
- stack but are outside the bounds of the newly adjusted stack pointer. They
- remain intact until that part of the stack is overwritten for some other
- purpose.
- Figure 1
- Stack stack1 is full
- stk1: 10
- stk2: 15
- Stk3: 20
-
- Stack stack3 is empty
- stk3: 0
- Figure 2
- Stack contains: 0 0 0 0 sp1 = 0, sp2 = 3
- Stack contains: 10 0 0 0 sp1 = 1, sp2 = 3
- Stack contains: 10 0 0 12 sp1 = 1, sp2 = 2
- Stack contains: 10 15 0 12 sp1 = 2, sp2 = 2
- Stack contains: 10 15 34 12 sp1 = 2, sp2 = 1
- Stack 1 is full
- Stack contains: 10 15 34 12 sp1 = 2, sp2 = 1
- Stack 2 is full
- Stack contains: 10 15 34 12 sp1 = 2, sp2 = 1
- stack 1: 15
- Stack contains: 10 15 34 12 sp1 = 1, sp2 = 1
- stack 1: 10
- Stack contains: 10 15 34 12 sp1 = 0, sp2 = 1
- Stack 1 is empty
- stack 1: 0
- Stack contains: 10 15 34 12 sp1 = 0, sp2 = 1
- stack 2: 34
- Stack contains: 10 15 34 12 sp1 = 0, sp2 = 2
- stack 2: 12
- Stack contains: 10 15 34 12 sp1 = 0, sp2 = 3
- Stack 2 is empty
- stack 2: 0
- Stack contains: 10 15 34 12 sp1 = 0, sp2 = 3
-
- Listing 1
- #include <stdio.h>
- #include <stdlib.h>
-
- #define NUMELEMENTS(x) (sizeof(x)/sizeof(x[0]))
-
- typedef struct {
- const char *stack_name;
- int *pstack;
- size_t stack_ptr;
- size_t max_stack;
- } stack;
-
- int array1[1];
- static int array2[30];
-
- stack stack1 = {"stack1", array1, 0, NUMELEMENTS(array1)};
- stack stack2 = {"stack2", array2, 0, NUMELEMENTS(array2)};
- stack stack3 = {"stack3", NULL, 0, 0};
-
- /* End of File */
-
-
- Listing 2
- void push(stack *, int);
- int pop(stack *);
-
- main()
- {
- int size = 50;
-
- stack3.pstack = malloc(size * sizeof(int));
-
- if (stack3.pstack == NULL) {
- printf("Can't allocate space for stack3\n");
- exit(1);
- }
-
- stack3.max_stack = size;
-
- push(&stack1, 10);
- push(&stack1, 12);
- push(&stack2, 15);
- push(&stack3, 20);
- printf("stk1: %d\n", pop(&stack1));
- printf("stk2: %d\n", pop(&stack2));
- printf("stk3: %d\n", pop(&stack3));
- printf("stk3: %d\n", pop(&stack3));
-
- return 0;
- }
-
- /* End of File */
-
-
- Listing 3
- void push(stack *st, int value)
- {
- if (st->stack_ptr == st->max_stack)
- printf("Stack %s is full\n", st->stack_name);
- else
- st->pstack[st->stack_ptr++] = value;
- }
-
- int pop(stack *st)
- {
- if (st->stack_ptr == 0) {
- printf("Stack %s is empty\n", st->stack_name);
- return 0;
- }
-
- return st->pstack[-st->stack_ptr];
- }
-
- /* End of File */
-
-
- Listing 4
- #include <stdio.h>
- #include <stdlib.h>
-
- #define NUMELEMENTS(x) (sizeof(x)/sizeof(x[0]))
- #define INT 1
- #define LONG 2
- #define DOUBLE 3
-
- typedef struct {
- const char *stack_name;
- void *pstack;
- const int type; /* type of entries */
- size_t stack_ptr;
- size_t max_stack;
-
- } stack;
-
- int array1[10];
- static long array2[30];
-
- stack stack1 = {"stack1", array1, INT, 0, NUMELEMENTS(array1)};
- stack stack2 = {"stack2", array2, LONG, 0, NUMELEMENTS(array2)};
- stack stack3 = {"stack3", NULL, DOUBLE, 0, 0};
-
- /* End of File */
-
-
- Listing 5
- main()
- {
- int size = 50;
- int vali = 10;
- long vall = 456789;
- double vald = 123.456;
-
- stack3.pstack = malloc(size * sizeof(double));
- if (stack3.pstack == NULL) {
- printf("Can't allocate space for stack3\n");
- exit(1);
- }
-
- stack3.max stack = size;
-
- push(&stack1, &vali);
- push(&stack2, &vall);
- push(&stack3, &vald);
- printf("stk1: %d\n", *((int *)pop(&stack1)));
- printf("stk2: %ld\n", *((long *)pop(&stack2)));
- printf("stk3: %f\n", *((double *)pop(&stack3)));
-
- return 0;
- }
-
- /* End of File */
-
-
- Listing 6
- void push(stack *st, void *pvalue)
- {
- if (st->stack_ptr == st->max stack)
- printf("Stack %s is full\n", st->stack_name);
- else
- switch (st->type) {
-
- case INT:
- ((int *)st->pstack)[st->stack_ptr++] = *(int *)pvalue;
- break;
-
- case LONG:
- ((long *)st->pstack)[st->stack_ptr++] = *(long *)pvalue;
- break;
-
- case DOUBLE:
- ((double *)st->pstack)[st->stack_ptr++] = *(double *)pvalue;
-
- break;
- }
- }
-
- void *pop(stack *st)
- {
- if (st->stack_ptr == 0) {
- printf("Stack %s is empty\n", st->stack_name);
- return 0;
- }
-
- switch (st->type) {
-
- case INT:
- return &((int *)st->pstack)[-st->stack_ptr];
- break;
-
- case LONG:
- return &((long *)st->pstack)[-st->stack_ptr];
- break;
-
- case DOUBLE:
- return &((long *)st->pstack)[-st->stack_ptr];
- break;
- }
- }
-
- /* End of File */
-
-
- Listing 7
- #include <stdio.h>
-
- enum node_type {Error, Char, Int, Double, String};
-
- typedef struct {
- enum node_type type;
- union {
- char c;
- int i;
- double d;
- char *s;
- } value;
- } node;
-
- void push(node);
- node pop(void);
-
- /* End of File */
-
-
- Listing 8
- main()
- {
- node n;
-
- n.value.d. = 9.87;
- n.type. = Double;
- push(n);
-
-
- n.value.s = "some text";
- n.type = String;
- push(n);
-
- n.value.i = 123;
- n.type = Int;
- push(n);
-
- n.value.c = 'A';
- n.type = Char;
- push(n);
-
- while (n = pop(), n.type != Error) {
-
- switch (n.type) {
-
- case Char:
- printf("Char = %c\n", n.value.c);
- break;
-
- case Int:
- printf("Int = %d\n", n.value.i);
- break;
-
- case Double:
- printf("Double = %f\n", n.value.d);
- break;
-
- case String:
- printf("String = %s\n", n.value.s);
- break;
- }
- }
-
- return 0;
- }
-
- /* End of File */
-
-
- Listing 9
- #include <stdio.h>
-
- #define STACK_SIZE 10
-
- static node stack[STACK_SIZE];
- static size_t stack_ptr = 0;
-
- void push(node n)
- {
- if (stack_ptr == STACK_SIZE)
- printf("Stack is full\n");
- else
- stack[stack_ptr++] = n;
- }
-
- node pop(void)
- {
-
- static node error_node = {Error, 0};
-
- if (stack_ptr == 0)
- return error_node;
- else
- return stack[-stack_ptr];
- }
-
- /* End of File */
-
-
- Listing 10
- #include <stdio.h>
-
- void push1(int);
- void push2(int);
- int pop1(void);
- int pop2(void);
- void dump_stack(void);
-
- main()
- {
- dump_stack(); push1(10);
- dump_stack(); push2(12);
- dump_stack(); push1(15);
- dump_stack(); push2(34);
- dump_stack(); push1(99);
- dump_stack(); push2(65);
- dump_stack(); printf("stack 1: %d\n", pop1());
- dump_stack(); printf("stack 1: %d\n", pop1());
- dump_stack(); printf("stack 1: %d\n", pop1());
- dump_stack(); printf("stack 2: %d\n", pop2());
- dump_stack(); printf("stack 2: %d\n", pop2());
- dump_stack(); printf("stack 2: %d\n", pop2());
- dump_stack();
-
- return 0;
- }
-
- /* End of File */
-
-
- Listing 11
- #include <stdio.h>
-
- #define STACK_SIZE 4
-
- static int stack[STACK_SIZE];
-
- static size_t stack_ptr1 = 0;
- static size_t stack_ptr2 = STACK_SIZE - 1;
-
- /* End of file */
-
-
- Listing 12
- void push1(int value)
- {
- if (stack_ptr1 > stack_ptr2)
-
- printf("Stack 1 is full\n");
- else
- stack[stack_ptr1++] = value;
- }
-
- void push2(int value)
- {
- if (stack_ptr1 > stack_ptr2)
- printf("Stack 2 is full\n");
- else
- stack[stack_ptr2-] = value;
- }
-
- int pop1(void)
- {
- if (stack_ptr1 == 0) {
- printf("Stack 1 is empty\n");
- return 0;
- }
-
- return stack[-stack_ptr1];
- }
-
- int pop2(void)
- {
- if (stack_ptr2 == STACK_SIZE - 1) {
- printf("Stack 2 is empty\n");
- return 0;
- }
-
- return stack[++stack_ptr2];
- }
-
- void dump_stack(void)
- {
- int i;
-
- printf("Stack contains: ");
- for (i = 0; i < STACK_SIZE; ++i)
- printf("%4d", stack[i]);
-
- printf("\tsp1 = %lu, sp2 = %lu\n",
- (unsigned long)stack_ptr1, (unsigned
- long)stack_ptr2);
- }
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- On The Networks
-
-
- It's Back!
-
-
-
-
- Sydney S. Weinstein
-
-
- Sydney S. Weinstein, CDP, CCP is a consultant, columnist, author, and
- president of Datacomp Systems, Inc., a consulting and contract programming
- firm specializing in databases, data presentation and windowing, transaction
- processing, networking, testing and test suites, and device management for
- UNIX and MS-DOS. He can be contacted care of Datacomp Systems, Inc., 3837
- Byron Road, Huntingdon Valley, PA 19006-2320 or via electronic mail on the
- Internet/Usenet mailbox syd@DSI. COM (dsinc!syd for those who cannot do
- Internet addressing).
-
-
- It's official. Paul Vixie has indeed taken over the moderation of
- comp.sources.unix. He also has the assistance of two co-moderators, Mike Stump
- and Nick Lai, to help him prevent the kind of backlog that occurred recently.
- Submissions to the news group, and comments to the moderators should be sent
- to unix-sources-moderator@pa.dec.com or decwrl!unix-sources-moderator. The
- queue has been cleared, and the backlog posted. However, it includes over six
- megabytes of sources in 37 separate postings. Here are just the highlights.
- The first posting of the "new era" was chop from George Sicherman <gls@hrmso.
- att.com>. chop extracts selected fields or columns of lines from the specified
- files or standard input, and writes them to standard output. It is similar to
- cut, a tool from the UNIX Documenters Workbench. chop allows for variable
- field separators and for output of the fields in the order specified on the
- command line. chop is Volume 25, Issue 1.
- Victor Abell <obe@mace.cc.purdue.edu> has obsoleted two of his older postings,
- ofiles and fstat, with a new version of lsof. LiSt Open Files displays the
- names of files opened by processes on selected UNIX systems. This new version
- in Volume 25, Issue 2, supports any file system based on the SunOS vnode
- model. This includes SunOS, AIX, HP-UX, NeXTStep, Dynix and some others. It
- understands most vnode extensions, NFS connections, FIFOs, multiplexed files,
- and UNIX and INET sockets.
- Abell having obsoleted ofiles, Robert Ehrlich <ehrlich@margaux. inria.fr> then
- went and reworked the original ofiles and released ofiles2 for Volume 25,
- Issue 72. This new version is more portable, but is still designed for BSD
- derived UNIX systems. As with lsof, ofiles2 displays the names of files that
- are being used by processes.
- A major update to the "Threaded RN" Newsreader was issued by Wayne Davison
- <davison@boreland. com> for Volume 25, Issues 4-16. TRN is based on RN 4.4,
- and the posting includes both RN and the changes to make TRN from RN. TRN uses
- the References header to build a discussion thread out of the news group. The
- articles are then presented in discussion thread order.
- One of the long promised postings, held in the queue for ages, is ease v3.5.
- ease is a decompiler that converts sendmail.cf into a language that is much
- easier to understand, and then allows editing of that intermediate language.
- It also includes a compiler to convert the ease language back into a
- sendmail.cf file. Considering how unreadable sendmail.cf files are, anything
- is an improvement. However, ease is a vast improvement. B r u c e B a r n e t
- t <barnett@crdgw1.ge.com> contributed ease for Volume 25 Issues 17-22.
- W e n - K i n g S u <wenking@vlsi.cs.caltech.edu> contributed fsp, one of the
- more interesting postings. It consists of a set of programs that implement a
- public-access archive server similar to anonymous-ftp. While the actual code
- is probably not of much use to most CUJ subscribers, the concept and the
- implementation of that concept are a very well thought out lesson in
- client/server computing. It shows the tradeoffs between multiple servers, and
- a single stateless server. FSP was posted as Volume 25, Issues 24-26.
- Several of the newer UNIX shells provide interactive command line editing. The
- ile program from Robert C. Pendleton <bobp@hal.corn> provides this same
- capability for any program. It runs the program as a subprocess so it does not
- need to modify the program, nor does it even need the source of the program it
- is servicing. It cannot provide a command history from program to program
- however. ile is Volume 25, Issue 29.
- One of the nicer capabilities of the Bitnet sites is the listserv program
- provided to automate maintenance of mailing lists. Anastasios Kotsikonas
- <tasos@cs.bu.edu> has provided similar capabilities in his listserv v5.31
- package posted in Volume 25, Issues 35-40. listserv provides support for list,
- list-owner, and list-request aliases for the mailing list and provides the
- back end programs for archives, moderated lists, peer lists, automated
- subscription, changes of address, and canceling of subscriptions.
- I get spoiled by running on a bit-mapped screen with a good windowing system.
- Just writing this column I have several windows open, one for the column, one
- for the list of files to write about, and one containing the file I am
- reviewing. For those stuck on standard ASCII terminals Juergen Weigert
- <jnweiger@immd4. informatik. uni-erlangen.de> has released version 3 of his
- multi-session package, screen3, for Volume 25, Issues 41-48. It allows several
- different virtual screens on a single terminal using a short "hot key"
- sequence to switch between the sessions.
- One of the more popular UNIX shells for interactive use has been csh, the
- c-shell. Over the years, many interactive extensions to csh were published as
- tcsh. These extensions used to require the source of csh as the base for the
- patches. Now with version 6, the full source of tcsh can be released. tcsh is
- a "kitchen sink" shell. It supports command line editing, command and file
- name completion, lists, manual lookup, job control, and has been ported to
- many different UNIX systems. For those that prefer csh to ksh, then tcsh will
- give you all the benefits of ksh with the csh syntax. The newest version,
- 6.01, was contributed by Christos Zoulas <christos@ee.cornell. edu> for Volume
- 25, Issues 54-71.
- Another major update is the latest version of the Revision Control System. RCS
- v5.6, which is a very flexible source code librarian not only supports source
- files, but also can track changes to binary files. RCS v5.6 was contributed by
- Adam Hammer <hammer@cs.purdue.edu> for Volume 25, Issues 77-87. New in v5.6
- are changes to fix security problems, efficiency changes for retrieving older
- versions, and the following of symbolic links instead of breaking them, and
- more reliable lock files under NFS.
- Emmet Gray <fthood!egray@uxc.cso.uiuc.edu> has updated his mtools package to
- version 2.0.5. mtools allows UNIX systems read, write, and manipulate files on
- an MS-DOS filesystem (typically a diskette). It emulates the commands ATTRIB,
- CD, COPY, DEL/ERASE, DIR, FORMAT, LABEL, MD/MKDIR, RD/RMDIR, COPY, REN/RENAME,
- TYPE, and COPY. The FORMAT command only adds the MS-DOS File system to the
- diskette. It depends on the UNIX low-level format routines to actually low
- level format the diskette. mtools2 was posted in Volume 25, Issues 97-99.
- Volume 25, Issue 103 is a set of patches to mtools 2.0.5 from Henry van Cleef
- <vancleef@netcom.netcom.com> to support XENIX 286 systems. There were
- portability problems in the original release in regards to assuming that ints
- are at least 32 bits long. In XENIX 286, an int is 16 bits.
- Recent patches appearing in comp.sources.unix include:
- psroff had patches 5, 6, and 7 posted as Volume 25, Issues 32, 33, and 104.
- psroff allows both older C/A/T troffs and the newer di-troffs to work with
- Postscript printers and with HP Laserjet printers. Patch 5 is minor fixes
- mostly for compilation on 80286 style machines. Patch 6 is very important as
- several major features were broken and fixed by this patch. Patch 7 is fixes
- for groff/di-troff users using HP laserjets.
- pathalias, the USENET map routing program, had patch 10 released by Peter
- Honeyman <honey@citi.umich.edu> as Volume 25, Issue 89. This is purely a bug
- fix patch and it is very small, but since so much depends on pathalias, I
- though it worth mentioning.
-
-
- No Reviews
-
-
- The status postings show several projects in the re-review stage, but nothing
- appeared in comp.sources.reviewed over the past two months. Now that
- comp.sources.unix is back, this is not unexpected.
-
-
- misc Tries an Experiment
-
-
- Kent Landfield, the moderator of comp.sources.misc tried an experiment this
- time. He took a very large posting, made a tar file of it, and then compressed
- the tar file. He then uuencoded the compressed tar file and posted that. Even
- posted that way it was 42 parts (plus part 00 with the text on what the
- posting is and how to convert it back to a tar file). If posted as the normal
- shar format it would have been over 140 parts, probably the largest single
- posting ever tried. Reviews of this method were mixed, with a large amount of
- complaints on how it is harder to determine what is there, and to sort and
- deal with it, but I had no problems saving the files, running my concatenation
- script and feeding that to uudecode to convert the ASCII back to binary. It
- then uncompressed cleanly and produced a 7.5MB tar file.
- And what was this grand experiment..., pp v6.0, a Mail Transfer Agent (MTA).
- pp is designed for high volume message switching, protocol conversion, and
- format conversion. pp supports X.400, RFC-822, and RFC-1148bis conversion
- between RFC-822 and X.400. PP is designed as a replacement for MMDF or
- sendmail. pp speaks X.400 (1984 and 1988), SMTP, JNT, UUCP, DECNET Mail-11,
- X.500, alias files, RFC-822 local delivery, and Fax Internetworking. No User
- Agents are provided, but a line oriented and an X-Window based management
- console program are provided. pp v6.0 was contributed by Steve
- Hardcastle-Kille <S.kille@cs.ucl.ac.uk> for Volume 27, Issues 24-66.
- The shadow log-in suite for UNIX systems was rereleased by John F. Haugh II
- <jfg@rpp386.cactus.org> for Volume 26, Issues 54-64. New in release 3 is
- support for SVR4 style maintenance utilities and the grouping of the code into
- libraries to make maintenance easier. This suite provides shadow
- login/password management to many UNIX systems that do not yet have such
- support natively. One file was left out of the distribution and was posted as
- patch 1 in Volume 26, Issue 75.
- Robert Davis<robert@am.dsir.govt.nz> contributed a new version of his C++
- matrix package for Volume 26, Issues 87-91. newmat04 supports matrix, upper
- and lower triangle, diagonal and symmetric matrices, row and column vectors
- and the element type float/double. Operators include *, +, --, inverse,
- transpose, submatrix, determinant, decomposition, triangularization,
- eigenvalues, sorting and fast Fourier transforms. It is intended for matrices
- in the size range 4x4 to 90x90.
- Have an HP Laserjet with the Pacific Data Systems 25-in-One font cartridge? If
- so, Bill Walker's <bkw@uecok.ecok.edu> wroff is what you need. It is a text
- formatter in the spirit of nroff, but designed specifically for this
- combination of hardware. wroff runs on UNIX, XENIX, MS-DOS and CPM-68K. It
- does kerning and some other troff like items as well. wroff is Volume 26,
- Issues 97-101.
- Ted Campbell <tcamp@hercules.acpub.duke.edu> contributed sfs the space flight
- simulator for IBM PC's with EGA or VGA, UNIX- PC's with MGR or UNIX with X11.
- A 21-part posting in Volume 27, Issues 1-21, sfs offers a graphics-based
- real-time animated simulation of orbital flight. You can simulate a complete
- range of orbital parameters and can also simulate multiple planets in a solar
- system. A particularly full map is given of the earth and can be displayed as
- viewed from the orbiting spacecraft, as a ground track map, or as a distance
- perspective in which the orbital track can be seen.
- A compatible regex (regular expression) package without any licensing
- restrictions was contributed by Tatu Ylonen <ylo@ngs.fi> for Volume 27, Issue
- 23. It is fully compatible with the GNU regex library and can handle arbitrary
- data including binary patterns. It also can compile and run on 16-bit machines
- such as MS-DOS.
- Those interested in genealogical research may want to get Steve Murphy's
- <murf@oakhill.sps.mat.com> gcom from Volume 21, Issues 72-78. gcom reads in
- GEDCOM format files containing genealogical data and merges them, utilizing
- not only name and date match heuristics, but familial ties as well.
- In my February column, I mentioned Archie, the service that keeps track of
- which sites archive which data. Brendan Kehoe <brendan@cs.widener.edu> has
- updated his archie client in Volume 27, Issues 79-84. Version 1.3 of archie is
- his Prospero client for the archie service. Note, using this client requires
- TCP/IP access to an Archie server, which means you must be on an Internet.
- Last on the new release front is the latest release of dmake. Version 3.8
- replaces version 3.7 and hopefully addresses all the little obscure bugs and
- features that remained. Dennis Vadura <dvadura@plg.waterloo.edu> has provided
- this version of the make utility. This package is the extended make ala BSD
- 4.4 release including many more features than the traditional versions. It
- includes support for UNIX, XENIX, MS-DOS, OS/2, and Atari-ST TOS. dmake 3.8 is
- Volume 27, Issues 101-142.
- On the patch front, patch 9 was issued for parseargs in Volume 26, Issues 65
- and 66 by Brad Appleton <brad@ssd.csd.harris.com>. parseargs provides a set of
- functions to parse command-line arguments. It can do much more than the getopt
- variety of parser. Patch 9 is mostly bug fixes and was followed by patch 10 in
- Volume 26, Issue 116 for a bit more cleanup.
- qbatch from Alan Saunders <tharr!alan> had its patch 2 posted for Volume 26,
- Issue 70 and patch 3 posted in Issue 85. Again, these were bug fix releases.
- The KSH lookalike, pdksh, posted by Simon Gerraty <sjg@zen.void.oz.au> was
- updated to patch 1 in Volume 26, Issues 71 and 72. The build process was
- cleaned up and some portability issues were addresses.
- The tin threaded newsreader had patches 6 and 7 posted by Iain Lea
- <stevax!iain> in Volume 26, Issues 76-82. New are support for Minix 386, more
- -M options for From lines, unread articles, and scrolling, plus some bug
- fixes. Patch 6 was in five parts and patch 7 in two parts.
- PBMPLUS the multi-format image manipulation toolset was also updated to fix
- some bugs and to add several new programs. Jef Poskanzer <jef@gwell.sf.ca.us>
- provided patch 10 in Volume 26, Issues 106-110. New are pgmcrater, ppmforge,
- ppmtoacad, and sldtoppm.
-
-
-
- Alternative Games
-
-
- Two different versions of a "get the money game" called sokoban were
- contributed by two different authors. Kevin Solie <kevins@ms.uky.edu>
- contributed his xsokoban for Volume 13, Issues 1-2. This X-based game is a
- very incomplete implementation of an idea from a similar PC game.
- The other version, xsokoban2, is from Joseph Traub <jt1o+@andrew.cmu.edu> and
- was released in Volume 13, Issues 13-15. It provides a slightly different user
- interface than Kevin's version, but is essentially the same game.
- Volume 13, Issue 3 provides dr_mario from Scott Noecker
- <noecker@seq.uncwil.edu>. This is a one-player lookalike version of Dr. Mario,
- a popular game for Nintendo that has nothing to do with the Mario Brothers
- series. It uses the standard keyboard and curses for the display driver.
- A complete revision of the WCST Nethack spoilers file updating it to version 7
- was contributed for Volume 13, Issues 4-9 by Paul Waterman <wheaton!water>. A
- complete reformatting has been done to make the information easier to use, and
- of course, more changes and sections have been added.
- A connect-five-in-a-row-game, make5 was contributed by Chih-Hung Hsieh
- <hsiehch@spunky.cs.nyu.edu> for Volume 13, Issues 10-12. This game, written in
- C++ provides both a curses and an X-Window interface. The X version uses the
- athena widgets library.
- For those with multi-user networks, a multi-player networked bridge game was
- contributed by Matthew Clegg <mclegg@cs.ucsd.edu>. okbridge, Volume 13, Issues
- 16-23, is a computer mediated bridge game that allows four players to
- participate in a game of rubber or duplicate bridge. The program handles
- dealing, scoring and communication of bids and plays. Issue 23 is a patch to
- fix a small problem in one of the distribution files.
-
-
- Previews from alt.sources
-
-
- Its been a quiet two months in alt.sources, only 4MB worth of notable postings
- (that or I am being more selective in what I consider notable). The most
- notable posting is a release of a uucp work-alike package from Ian Lance
- Taylor <ian@airs.cam>. taylor-uucp v1.01 was posted on November 24, 1991 in 18
- parts. It is a complete replacement for HDB style UUCP. It supports V2 style
- configuration (L.sys, L-devices) and BNU (aka HDB) style configuration files
- (Systems, Devices). It is a complete system, except for the fancy maintenance
- shell scripts and uusched (the latter is in the works).
- Curt Mayer <hoptoad!curt> posted his disassembler for Z80/Z280 CP/M .COM files
- on November 27, 1991. The output is capable of being assembled and can detect
- code sequences by tracing jump targets.
- Along with the CP/M disassembler, D'Arcy J. M. Cain <druid!darcy> posted on
- December 19, 1991 in three parts his Z80 CP/M emulator for UNIX. Most of the
- Z80 instruction set is implemented, except for the I/O section. The emulator
- also uses the UNIX shell commands to simulate some of the CP/M commands and
- uses the UNIX file system for drives. All I/O must go via the BDOS or BIOS as
- IN and OUT opcodes are not fully implemented.
- On the opposite front, Quinn Jenson <jensenq@qcj.icon.com> posted a DSP56001
- assembler on November 29, 1991 in four parts. The syntax was intended to be
- compatible with Motorola's syntax, but without the docs he could only guess.
- It does allow for UNIX based DSP code development for those not lucky enough
- to have a NeXT.
- pcal, the postscript calendar program has been updated to Version 4.3 by
- Joseph Brownlee <jbrO@cbnews.cb.art.com> on December 16, 1991 in seven parts.
- pcal allows for creation of personal calendars. New in 4.3 are generation of
- UNIX calendar files, move the previous and following month boxes around on the
- page, allow notes in any empty day box, change both note font and size via
- command line options, addition of the nearest keyword as in "workday nearest
- every 10th", plus about a page more worth of additions (something had to fill
- the 13000 lines of the posting).
- Mayan Moudgill <moudgill@cs.cornell.edu> has posted his C++ socket library
- that adds UNIX and INET sockets to the iostreams functions. It was posted in
- late December, and then an updated posting was made on January 8, 1992 in two
- parts. Supported are convenient connection setup routines, use of the usual <<
- & >> operators for i/o, support for out of band message transmission and
- reception, and an easy method of specifying per socket SIGIO, SIGURG and
- SIGPIPE handlers. Also include is an interface to the select system.
- A gateway between the UNIX and the Citadel style BBS was posted on December
- 24, 1991 in four parts by Ken MacLeod <unidel@bitsko.slc.ut.us>. uccico
- implements the Citadel BBS networking for a UNIX system much like uucico does
- for UUCP. It handles USENET news and RFC822 mail conversion.
- A large posting was mixview, posted by Robert Lau but written by Douglas Scott
- <doug@woof.columbia.edu> in 11 parts on January 12, 1992. It is an X-Window
- program that allows for editing sound files. It supports any BSD style system
- that has sound capabilities, such as Sun's and NeXT's.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Questions & Answers
-
-
- Calling Functions from Within a Function
-
-
-
-
- Ken Pugh
-
-
- Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language
- courses for corporations. He is the author of C Language for Programmers and
- All On C, and was a member on the ANSI C committee. He also does custom C
- programming for communications, graphics, image databases, and hypertext. His
- address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax
- questions for Ken to (919) 489-5239. Ken also receives email at kpugh@dukemvs.
- ac. duke.edu (Internet).
-
-
- Q
- I work with ANSI-C and I have a general problem. How is it possible to call
- one function with a variable number/types of arguments inside a second
- function with variable number/types of arguments, with the same arguments I
- called the second function?
- For example, I have three functions with a variable number and variable type
- of arguments (myfunction1, myfunction2, myfunction3 shown in Listing 1). I
- want to call the functions myfunction1 and myfunction2 from inside
- myfunction3, with the same arguments I called myfunction3. Is there an easy
- way to do that?
- Thank you very much for your answer.
- Willi Fleischer
- Moerfelden, Germany
- A
- The error involves the difference between a variable parameter list and a
- parameter list containing a value of type va_list. When you pass parameters to
- a function, the values of those parameters are pushed onto the stack. Usually
- the first or second parameter to a variable parameter function indicates
- directly (with a count) or indirectly (as with format specifiers) how many
- values have been passed.
- The printf and fprintf functions expect to see the values on the stack. Each
- value has an address (its position on the stack). The vfprintf function
- expects its third parameter will be the address of the first value of a set of
- parameters on the stack. It then uses this address to retrieve those
- parameters. To use each parameter, it needs to know the type of the value.
- That information it gets from the format list specifiers, just like printf and
- fprintf. The type of the parameter is also used to find the next parameter.
- Perhaps it might be instructive to look at typical definitions for these
- macros. Those in Listing 2 are from Microsoft C.
- Variables of type va_list are actually addresses. va_start puts into the first
- argument the address of the first variable parameter. va_arg increments the
- first argument (ap) by the sizeof the type which is passed as the second
- argument (t), as well as yielding a value of that type.
- How does vfprintf get to a value on the stack? Since it knows the type of
- argument (from the format list), it uses the va_arg macro with the appropriate
- type.
- Your my_function1 and my_function2 require a list of values to be passed to
- them. When you used the va_start macro in my_function3, you retrieved the
- address of the first variable parameter that was passed to my_function3. You
- then passed that address to my_function1 and my_function2 and they produced
- garbage. You need to rewrite my_function1 and my_function2 such that they
- expect an address (i.e. type va_list). Listing 3 shows how they should look.
- Q
- I've been programming in C for about two years now and have not seen anything
- in the literature about my question. The company I work for does quick and
- dirty production programming. We are in the process of moving from PL/I to C.
- The programming involves sequential processing of mostly fixed fielded files.
- In PL/I we read the file into an input buffer and use the string function to
- load it into a structure. Listing 4 is an instance.
- What this in effect does is load the structure recin from the input buffer
- inn. There is no corresponding C function for loading such a structure and
- I've been trying to develop something that will do this. What I've come up
- with is Listing 5.
- While the coding for load_struct works (at least in VAX C) I am not convinced
- this is the best way or even an effective way for doing what I desire. I
- realize the error checking is non-existent, but is it correct to assume that a
- structure of character arrays will have contiguous addresses? Is there another
- method used by more experienced programmers? I need something that I can put
- into a library that is very generic. How would you tackle this problem?
- Tom Crosman
- Brooklyn Park, MN
- A
- On some machines individual fields in a structure do have packing bytes
- between them to align them to word boundaries, giving them contiguous
- addresses. Records which consist of character only (such as your example) tend
- not to have packing bytes.
- Your method in general is fine. Since you asked for my solution, I've given it
- in Listing 6. Let me explain the modifications that I've made. The first is
- that one should usually never declare a variable in the same statement that
- declares the structure template. Anyone who wishes to use that template gets
- stuck with that extra variable. Second, I used an array of sizes for each of
- the fields. These could actually be picked up using the sizeof operator. As
- another alternative, one could #define the size of each field and use those in
- the initialization list.
- I prefer using an array for the sizes instead of passing them in the parameter
- list. The declaration can be close to the structure template. Any changes in
- order or size of the fields can be simply coordinated. Using an array to pass
- the sizes also simplifies the function, since it is no longer concerned with a
- variable parameter list.
- To show an additional use for the array, I included a print function. It
- requires most of the same parameters as the load function.
- To make the function more generic, I added a nul_terminated flag. Some
- programmers do not like using the extra character space to hold a terminator
- in each field. The nul, if present, can signify the end of a value less than
- the field length. If not present, the field length is the size of the field.
- Though this requires a slight bit more coding, it does save significant disk
- space if you store thousands of copies of a structure.
- If you were concerned with the packing of the fields in either the input
- record or the output structure, then you could add an array of field addresses
- to the calls. If that is necessary, I might suggest not using a generic
- function and simply hard coding any record conversions required. At some point
- the work of providing and using a generic interface exceeds the benefit.
-
-
- Reader Feedback
-
-
-
-
- Coding style
-
-
- I notice in your find_maximum function that you declare temporary storage for
- the return value. Is there some reason this is preferred over just returning
- the value directly? This allows the simplifications in Listing 7.
- Also in your listings, for the function put_line, it seems to me that putchar
- would be preferred to printf("%c",...). It would not have to process the
- format string and convert the character before putting it on stdout.
- I enjoy your column and comments.
- Edward C. Sarlls, III
- Houston, TX
- I tend to use an automatic variable for the return value from a function. That
- makes it easier to put a printf statement in the code to print the return
- value of the function. Or if you are a debugger person, it makes it easy to
- watch the value.
- The disadvantage is a slight decrease in speed. If I made it a register
- variable (or if the compiler does so automatically), even that should not be a
- problem.
-
- If you decide to change the return value of the function that uses an
- expression, and then with the local parameter, you only have to change the
- expression in one place. If you have debugging output, you need to change it
- in two.
- I must admit that I have had this style for a long long time. In one of my C
- classes that I taught back in the early '80s, I had a student who insisted
- that parentheses were required around the expression that follows the return
- statement. It turns out that all the examples of return statements he had seen
- had complex expressions around them (as the one in your second example).
- Psychologically the parenthesis were needed to surround the expression and
- "make it one." That points up the other coding style that I use quite often
- (see Listing 8). I state in All on C that having a single return statement
- with gotos is preferable to having multiple return statements. If I want to
- trace the return value, with multiple return statements, I have to do
- something like the code shown in Listing 9.
- If I were using a debugger, there would be several breakpoints to set
- (assuming the function was long enough that I simply didn't single step
- through it).
- The difference in the complexity of code between multiple returns and multiple
- gotos (to the end of a function) does not seem to be a big issue, at least to
- me.
- I may get hundreds of letters regarding this seemingly idiosyncratic style of
- programming (or maybe with an adjective using only the first two syllables).
- Before that occurs, I wish to make a few caveats regarding it. First, I try to
- program the logic not to require multiple returns/gotos. Hence the original
- listing has neither in it. Second, there should be a single label at the end
- of the function with a standard name (say end), that is the label for the
- goto. If that is the case, then ret = xxx; goto end takes on the same meaning
- as return xxx;.(KP)
- Q
- I just read your question and answer article entitled "Using typedef" in the
- December 1991 issue of The C Users Journal. I am writing in reference to the
- question regarding the writing of a function returning the lowest and highest
- integer out of the three integers passed. Listing 10 is my attempt at
- answering this question.
- With all due respect, I think this approach is more eloquent and less
- convoluted than the methods offered in Listing 2 (December 1991 issue) and
- Listing 3 on page 120 (December 1991 issue) and in Listing 4 on page 122
- (December 1991 issue), whether or not you choose to use the conditional
- shorthand.
- I am also puzzled by the fact that you were a member of the ANSI C committee
- and you didn't use prototypes in your code. Did you have a special reason for
- not prototyping your examples?
- S.J. Stern
- Bothell, WA
- A
- Eloquence is in the eye of the beholder. Some people might consider the second
- part of Listing 7 (from Mr. Sarlls letter previously in this column) the most
- eloquent. I don't particularly prefer any of my Listing 2, Listing 3, or
- Listing 4. My favorite is Listing 5 (from the December 1991 issue, reproduced
- here as Listing 11). It computes the maximum for any number of input
- parameters. The logic in your sample matches the logic in that listing.
- I guess I could have arranged the logic in my function as shown in Listing 12.
- It matches your logic and has fewer lines than my previous listing. Unless I
- am going to call a routine a few thousand times, I tend to stick with whatever
- I come up with first that works. Also, I usually avoid using the conditional
- expression operator, for the reasons explained in last month's column.
- As far as prototypes, I usually do not include them unless they are required
- by ANSI C or they are essential to the answer. The information contained in
- prototypes is mostly redundant. The case of the variable parameter function
- (as in Listing 10) requires one. When I do need prototypes (e.g. for C++), I
- let the compiler or PC-Lint generate a file of them.
- Thank you for your feedback.(KP)
-
-
- typedefs and lint
-
-
- In my column a few months ago, I answered a question regarding typedefs. At
- the recent C-Forum sponsored by the Wang Institute of Boston University, I
- bumped into Jim Gimpel, the author of PC-Lint. He told me that the latest
- version of PC-Lint has an option for strong typing. This means that it can
- report errors in the use of typedefed variables which the compiler would just
- ignore. For example, given the code in Listing 13, the assignment of
- short_distance = high_speed;
- is accepted by the compiler without question since both variables are declared
- to be type double, once the typedefs are resolved into the underlying types.
- However PC-Lint can yield an error message, if strong typing is turned on.
- As another example, a function declared as shown in Listing 14 will give a
- PC-Lint error if it is passed the code in Listing 15.
- There are many options available for strong typing, which are all described in
- the manual. In addition, you can declare that particular arrays can only be
- indexed by variables of particular types. For example, arrays of type
- HISTOGRAM can only be indexed by variables of type INDEX. This would look
- something like Listing 16.
- The not_good_ index reference to my_array would yield an output error.
- For those who are considering changing to C++ purely for its type-checking
- abilities, I suggest you look at PC-Lint as an alternative.(KP)
-
- Listing 1
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
-
- void myfunction1(char *format, ...)
- {
- va_list arg_ptr;
- va_start(arg_ptr,format);
- vfprintf(stdout,format,arg_ptr);
- va_end(arg_ptr);
- }
-
- void myfunction2(char *format, ...)
- {
- FILE *fp;
- va_list arg_ptr;
- fp = fopen("TEST.DAT","a+");
- va_start(arg_ptr,format);
- vfprintf(fp,format,arg_ptr);
- va_end(arg_ptr);
- fclose(fp);
- }
-
- void myfunction3(char *format, ...)
- {
- va_list arg_ptr1;
- va_list arg_ptr2;
- va_start(arg_ptr1,format);
-
-
- myfunction1(format,arg_ptr1);
-
- /* here I want to use the arguments of myfunction3(),
- va_end(arg_ptr1); but this code does not work */
-
- va_start(arg_ptr2,format);
- myfunction2(format,arg_ptr2);
-
- /* here I want to use the arguments of myfunction3(),
- va_end(arg_ptr2); but this code does not work */
- }
-
- int main()
- {
- char msg[]="message";
- myfunction1("\n%s=%d=%f", msg, 2, 3.0); /* this works
- fine */
- myfunction2("\n%s=%d=%f", msg, 2, 3.0); /* this works
- fine */
-
- /* the following call of myfunction3() does not work.
- I want to have the same result, as
- if I call myfunction1() and myfunction2() isolated */
-
- myfunction3("\n%s=%d=%f", msg, 2, 3.0);
- }
-
- /* End of File */
-
-
- Listing 2
- typedef void *va_list;
- #define va_start(ap,v) ap = (va_list)&v + sizeof(v)
- #define va_arg(ap,t) ((t*)(ap += sizeof(t)))[-1]
- #define va_end(ap) ap = NULL
- /* End of File */
-
-
- Listing 3
- void myfunction1_a(char *format, va_list arg_ptr)
- {
- vfprintf(stdout,format,arg_ptr);
- }
-
- void myfunction2_a(char *format, va_list arg_ptr)
- {
- FILE *fp;
-
- fp = fopen("TEST.DAT","a+");
- vfprintf(fp,format,arg_ptr);
- fclose(fp);
- }
-
- void myfunction3(char *format, ...)
- {
- va_list arg_ptr1;
- va_list arg_ptr2;
-
- va_start(arg_ptr1,format);
-
- myfunction1_a(format, arg_ptr1);
- myfunction2_a(format,arg_ptr1);
- va_end(arg_ptr1);
- }
- /* End of File */
-
-
- Listing 4
- DCL 1 RECIN,
- 2 NAME CHAR(30),
- 2 ADDRESS CHAR(20),
- 2 city CHAR(15),
- 2 STATE CHAR(2),
- 2 ZIP CHAR(5);
-
- ...
-
- READ FILE (FILIN) INTO (INN);
- STRING(RECIN) = INN;
- ...
- /* End of File */
-
-
- Listing 5
- #define MAX 1000
-
- void load_struct(instruc, instr, ...);
-
- /************** main function ********/
-
- load_struct(instruc, instr, va_alist)
- char *instruc;
- char *instr;
- {
- int k=0;
- int strnglen;
- va_list ap;
- va_start(ap);
-
- while((strnglen = va_arg(ap, int)) != NULL)
- {
- for(k = 0; k < strnglen-1; k++)
- *instruc++ = *instr++;
-
- *instruc++ = 0;
- }
- va_end(ap);
- }
- /* End of File */
-
-
- Listing 6
- struct rec {
- char name[31];
- char address[21];
- char city[16];
- char state[3];
- char zip[6];
- };
-
- static int field_sizes[] = {31, 21, 16, 3, 6};
- /*
- Alternatively, this could be written as:
-
- static int field_sizes[] = { sizeof(rec.name),
- sizeof(rec.address),
- ...
- };
- */
- #define field_count (sizeof(field_sizes)/sizeof(int));
-
- main()
- {
- char record_in[MAX];
- struct rec record;
- ...
- while(fgets(record_inn, MAX, filin) != NULL)
- {
- load_struct(&record, record_in, field_sizes,
- field_count, TRUE);
- print_struct(&record, field_sizes, field_count, TRUE);
- ...
- }
- }
-
- print_struct(record, field_sizes, field_count, nul_terminated)
- char *record; /* Record to print */
- int field_sizes[]; /* Size of fields */
- int field_count; /* Number of fields */
- int nul_terminated; /* If fields are nul terminated */
- {
- int field;
- int length;
- char *pc;
- printf("\n");
- pc = record;
- for (field = 0; field < field_count; field++)
- {
- if (nul_terminated)
- printf("%s:",pc);
- else
- {
- length = field_sizes[field];
- printf("%.*s:", length, pc);
- }
- pc += field_sizes[field];
- }
- }
-
- load_struct(record, record_in, field_sizes, field_count,
- nul_terminated)
- char *record;
- char *record_in;
- int field_sizes[];
- int field_count;
- int nul_terminated; /* If fields out should be nul terminated */
- {
- int field;
- int length;
-
- char *pc;
- char *pc_in;
-
- pc = record;
- pc_in = record_in;
- for (field = 0; field < field_count; field++)
- {
- length = field_sizes[field];
- strncpy(pc, pc_in, length);
- if (nul_terminated)
- {
- pc[length-1] = '\0';
- pc_in += length - 1;
- }
- else
- pc_in += length;
- pc += field_sizes[field];
- }
- }
-
- /* End of File */
-
-
- Listing 7
- int find_maximum(one, two, three)
- int one, two, three;
- {
- if (one > two)
- if (one > three)
- return one;
- else
- return three;
- if (two > three)
- return two;
- else
- return three;
- }
-
- ... or ...
-
- int find_maximum(one, two, three)
- int one, two, three; /* This could be a macro if you're
- careful about side effects. */
- return ((one>two)?((one>three)?one:three):((two>three)?two:three));
-
- /* End of File */
-
-
- Listing 8
- int find_maximum(one, two, three)
- int one, two, three;
- {
- int ret;
- if (one > two)
- if (one > three)
- {
- ret = one;
- goto end;
- }
-
- else
- {
- ret = three;
- goto end;
- }
- ...
- end:
- printf("find_maximum returning %d", ret);
- return ret;
- }
- /* End of File */
-
-
- Listing 9
- if (one > three)
- {
- printf("Find maximum returning %d", one);
- return one;
- }
-
- /* End of File */
-
-
- Listing 10
- void get_low_high(int a, int b, int c, int *low, int *high)
- {
- *low = (a < b) ? a : b;
- if (c < *low) *low = c;
- *high = (a > b) ? a : b;
- if (c > *high) *high = c;
- }
-
- /* End of File */
-
-
- Listing 11
- #include <stdarg.h>
- int maximum(int count, int first,...);
- main()
- {
- int ret;
- ret = maximum(6, 2,3,4,5,9,8);
- printf("Maximum is %d\n", ret);
- }
-
- int maximum(int count,int first,...)
- {
- va_list arguments;
- int i;
- int value;
- int maximum = first;
- va_start(arguments, first);
- for (i = 0; i < count - 1; i++)
- {
- value = va_arg(arguments, int);
- if (value > maximum)
- maximum = value;
- }
- va_end(arguments);
-
- return maximum;
- }
- /* End of File */
-
-
- Listing 12
- void find_min_max(one, two, three, minimum, maximum)
- int one, two, three;
- int *minimum;
- int *maximum;
- {
- if (one > two)
- *minimum = two;
- else
- *minimum = one;
- if (three < *minimum)
- *minimum = three;
-
- if (one < two)
- *maximum = two;
- else
- *maximum = one;
- if (three > *maxmium)
- *maximum = three;
-
- return;
- }
- /* End of File */
-
-
- Listing 13
- typedef double SPEED;
- typedef double DISTANCE;
- typeder double TIME;
-
- SPEED low_speed, high_speed;
- DISTANCE short_distance, long_distance;
- TIME brief_time, long_time;
-
- /* End of File */
-
-
- Listing 14
- SPEED compute_speed(DISTANCE distance, TIME time);
-
- /* End of File */
-
-
- Listing 15
- low_speed = compute_speed(long_time, short_distance);
-
- /* End of File */
-
-
- Listing 16
- /* lint -index(c,INDEX,HISTOGRAM) */
-
- typedef unsigned int INDEX;
- typedef int HISTOGRAM
-
- #define SIZE (INDEX) 10
-
- HISTOGRAM my_array[SIZE];
- INDEX good_index;
- int not_good_index;
-
- my_array[good_index] = (HISTOGRAM) 5;
- my_array[not_good_index] = (HISTOGRAM) 7;
-
- /* End of File */
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- CUG New Releases
-
-
- Gadgets
-
-
-
-
- Kenji Hino
-
-
- Kenji Hino is a member of The User's Group technical staff. He holds a B.S.C.S
- from McPherson College and an undergraduate degree in metallurgy from a
- Japanese university. He enjoys playing drums in a reggae band.
-
-
-
-
- Update
-
-
-
-
- CUG297 Small Prolog
-
-
- Henri de Feraudy (FRANCE) has released v2.0 of his Small Prolog. The new
- version offers much better debugging facilities, and a 32-bit executable
- compiled by GCC-386 (CUG359).
-
-
- CUG327 Panels for C
-
-
- J. Brown (KS) has released v2.3 update of his shareware window package, Panels
- for C. This version includes these new features: OS/2 support, Turbo C
- support, utilizing the PATH environment variables to find panel definition
- files, allowing the inclusion of panel definitions in the C source program,
- Interactive Panel Design (IPD) utility.
-
-
- CUG335 UltraWin
-
-
- Kevin L. Huck (MO) has released v2.10 of his shareware, Ultrawin. This new
- version includes new features: unlimited overlapping windows, background
- printing, PC timer control, mouse and graphic support, and enhanced data entry
- capabilities. Also included are a hypertext help engine and and EGA/VGA font
- editor. Also released is InTUItion 1.10, a textual user-interface library that
- includes an interface contruction program that allows UltraWin users to
- interactively create dialog boxes, menus, pick lists, forms and more using a
- mouse. Source code can be automatically generated to perform processing on
- each item, saving hours of tedious hand coding and debugging.
-
-
- New Releases
-
-
-
-
- CUG361 Gadgets and Term
-
-
- Jack E. Ekwall has contributed a function library Gadgets, a group of
- UNIX-like tools for DOS; and Term, a collection of computer buzz-words.
- Gadgets provides functions such as popup/dropdown window, drawing box, screen
- and cursor manipulation, keyboard input, color, date, printer and mouse
- control, and file manipulation. Some of the functions are lifted from CUG273
- Turbo C Utilities. The library is linkable to Turbo C v2.0. These UNIX-like
- tools offer a solution to the DOS command line interface pipeline problem.
- Term includes 634 topics and 32 historical notes/ observations about computer
- buzzwords. This text is in a text-indexed sequential form which can be read by
- a display program, VU. The distribution disk includes source code for the
- library and documentation.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Zinc Interface Library
-
-
- David Brumbaugh
-
-
- David Brumbaugh is a project manager at Advanced Information Services,
- asystems integrator in Peoria, IL. He has been programming in C for over
- fiveyears and in C++ for over a year. He can be reached by mail at 2807 N.
- Renwood Ave., Peoria, IL 61604.
-
-
-
-
- Introduction
-
-
- The Zinc Interface Library is a C++ user interface library from Zinc Software
- Inc. for PC compatible computers. It supports MS-DOS text mode, MS-DOS
- graphics mode and MS-Windows 3.x interfaces. I'm reporting on Zinc Version 2.0
- for Borland C++. Zinc also supports the Zortech C++ complier.
- C programmers moving to C++ and experienced C++ programmers will find the Zinc
- Interface Library helpful. I've been using it for over a year on projects I've
- been working on at home.
-
-
- Features
-
-
- Zinc's primary feature is a C++ class library. It consists of class
- definitions, object code and optionally, source code for those classes. The
- class library is designed so that your application only needs to have one set
- of source code for writing applications in text mode for MS-DOS, graphics mode
- for MS-DOS (CGA, EGA, VGA, Hercules compatible) and MS-Windows 3.x.
- The user interface created in all three modes is SAA compliant. This means it
- has windows, menus, dialogue boxes, list boxes, optional mouse support, and
- all the other features users have come to expect in modern software.
- All three modes, text, graphics and MS-Windows have the same basic look and
- feel. The UI_DISPLAY class encapsulates the three display types in its
- descendants. The UI_DISPLAY class defines all the things that a program can do
- to a user's display.
- Text mode applications use the PC extended ASCII graphics set for windows. The
- programmer has several options when using text mode:
- 1. The application can automatically detect the current text mode and use it.
- This is the default.
- 2. The programmer can explicitly use 25x80, 25x40 or 43x80 (which gives 50x80
- on VGA).
- 3. The programmer can give the user a choice on which mode to use.
- Graphics mode MS-DOS display classes use complier specific libraries. The
- library I have uses BGI (Borland Graphic Interface). The documentation
- indicates that there is similar support for Zortech's graphic library. An
- application can switch from graphics to text mode and back without loosing the
- information in the user's windows. Besides the text mode features, the
- graphics display classes have support for graphic specific features like arcs,
- polygons, bitmaps, etc.
- While one MS-DOS application can support both text and graphics, you must
- recompile your program to support MS-Windows. You also must add a couple of
- #ifdef statements to call WinMain instead of main, and to redefine how colors
- are used. Other than that, all my MS-DOS applications, including graphic
- applications, ran without modification on MS-Windows.
- The strong points of the class library far out-weigh the weak points. Because
- the class library is set up in a very logical hierarchy, it is easy to learn.
- The field validation capability is one of my favorite features. All user input
- can be validated by the program. Most common validations are included and the
- programmer can define his own easily.
- The library is fairly robust. Most of the errors I found in my programs were
- my own. When they weren't there was usually a fix on their BBS. The library is
- complete and generally well designed.
- The only weak points that I found were in the MS-Windows mode. The first is
- that since it always displays its bitmaps one pixel at a time, bitmap displays
- in MS-Windows are very slow.
- When I tried to work around this by using Windows bitmaps, I discovered that
- there is no obvious way to use Windows resources with Zinc. Zinc has its own
- version of resources, and the BBS contains some programs to convert Windows
- bitmaps to Zinc bitmaps and Windows icons to Zinc icons.
- Finally, Zinc doesn't have any direct support for MS-Windows Multiple Document
- Interface (MDI). MDI applications keep all the child windows of a main window
- confined within the boundary of the main window. Some programmer's will see
- this as a bigger problem than others.
- The other major feature that comes with Zinc is the Zinc Designer. The Zinc
- Designer is a program that allows the programmer to draw windows, dialogues,
- menus, bitmaps etc. Using the Designer is faster than writing the equivalent
- source code. It allows the programmer to make better "Look and Feel"
- decisions.
- I found several weak points with the Designer. Some of the features that Zinc
- supports, like radio buttons and check boxes, are not supported by the
- Designer. The menu items in the Designer are cumbersome to edit. My final
- complaint is that the Designer generates only binary objects, not source code.
- I would like to see both.
-
-
- Documentation
-
-
- The Zinc documentation consists of three books: The Programmer's Guide, The
- Programmer's Tutorial, and The Programmer's Reference. It also includes a
- Quick Reference Guide containing a list of the most common constructors,
- flags, event information, and a class hierarchy.
- The Programmer's Guide provides a good overview of the concepts in the Zinc
- Interface Library. It is a short book that hits the most important points of
- the library. It also contains a user's guide to the Zinc Designer.
- The Programmer's Tutorial is a clear and simple book to help the programmer
- get started. It is short enough to stay interesting. That is a major
- accomplishment when you consider that it not only contains lessons on using
- the Zinc Library, but a C++ and object-oriented design tutorial as well. The
- examples are excellent. They are clear and many are useful building blocks for
- your own applications.
- The Programmer's Reference is a well organized book that covers most of the
- classes that come with Zinc. It could be more complete in its handling of
- specific classes. For example, the width parameter in the Line method of
- UI_DOS_BGI_DISPLAY is ignored. I spent about two hours trying to find the bug
- in my code before I checked with technical support. They agreed that it should
- have been documented.
-
-
- Support
-
-
- I have found Zinc technical support to be absolutely terrific. I usually use
- the Zinc BBS for support. It is well organized and well maintained. It
- contains corrections, news, user contributions and additional examples. The
- message base provides contact with the people at Zinc and with other users. I
- usually search the message base and find the answer I'm looking for without
- having to post a question.
- When I can't wait for the BBS I call Zinc's voice line. I've never had a long
- wait when I've called. The support people are very friendly, helpful and
- knowledgeable. They usually understood my problem better than I did and had
- helpful suggestions in addition to the answers to my questions.
-
-
- Competition
-
-
-
- Borland's ObjectWindows and Turbo Vision are the other C++ user interface
- libraries I am familiar with. Borland chose to use separate class hierarchies
- for MS-DOS text and MS-Windows user interface. That means that if you want to
- write one application for both MS-DOS and Windows, you have to write two
- separate programs.
- ObjectWindows has more direct support for MS-Windows than Zinc. It includes
- classes that can use Windows resources, for example. But, it lacks the field
- specific editing features, like dates, that Zinc has.
- Turbo Vision is a text mode user interface class library. It has features Zinc
- doesn't, like the THistory class that allows the user to keep a list of data
- entry choices. It also lacks certain features, like the extensive field
- support, that Zinc has. Turbo Vision also lacks the MS-DOS graphics support
- that Zinc has.
- Generally, if you're doing strictly MS-Windows programming, Borland's OWL has
- a slight edge because it is strictly for Windows. Its bitmaps are displayed
- faster, it can use resources from Borland's Resource Workshop (or other
- sources) and there is full MDI support.
- If you want to write strictly text mode programs, Zinc has a slight edge over
- Turbo Vision because it has more support for formatted and validated data
- entry fields. If you want to write MS-DOS graphics mode programs, Zinc wins
- hands down because neither Turbo Vision or OWL supports that mode.
- If you want to write applications that can be used across all three platforms,
- I recommend Zinc because you'll be doing less rewriting of code. The Zinc
- library fulfills the promise of code reuseablity much better than either
- ObjectWindows or Turbo Vision.
-
-
- Conclusion
-
-
- The Zinc Interface Library is a good value. There is no doubt that Zinc will
- save time for C++ programmers who write programs for the PC. It lets you
- program for three PC platforms in the time it usually takes to write code for
- one. The examples of C++ code will be of great help to the new C++ programmer.
- The library is an example of good Object Oriented Design.
- I have only two suggestions for improvements. I would like to see a more
- complete reference manual. I also would like to see the Windows library have
- more support for the features of Windows 3.x.
- I would recommend the Zinc Interface Library to any C++ programmer who wants
- to write applications for the PC.
-
-
- Editor's Note:
-
-
- As this issue went to press, we were informed that Zinc Interface Library v3.0
- is now available. According to a Zinc representative, version 3.0 addresses
- some of the problems noted in this User Report. New features include direct
- use of Windows bitmap functions; MDI support for Windows and DOS; new window
- objects such as toolbar, combo box, checkbox, radio button, and buttons with
- associated bitmaps; Zinc Designer adds a toolbar and access to all library
- features including user functions and validation routines. For more
- information contact Zinc Software Incorporated, 405 South 100 St. Suite 201,
- Pleasant Grove, UT 84062, (801) 785-8900, FAX (801) 785-8996.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- C Express: 250+ Ready-To-Run Assembly-Language Routines for Turbo C, Microsoft
- C, and QuickC
-
-
- Stephen Patten
-
-
- Stephen Patten is a senior analyst with the Lincoln Savings Bank in New York
- and teaches C at New York University. He can be reached at (516) 932-3484.
-
-
- C Express: 250+ Ready-To-Run Assembly-Language Routines for Turbo C, Microsoft
- C and QuickC, a book and accompanying diskettes, was written (both the
- assembly source code and text) by Robert Jourdain and published by Brady
- Books, a division of Simon & Schuster, Inc. The copy I reviewed was dated
- 1989.
-
-
- Companion Diskettes
-
-
- Two 5.25" diskettes accompany the 412-page book. One diskette contains the
- eight library files which house the 250 plus routines in object code form
- along with eight parallel header files for function prototyping. The other
- diskette contains the assembly language source of the routines in compressed
- form. (You can exchange the 5.25" disks for 3.5").
- You install the libraries and header files on a hard drive by a simple copy,
- and then call routines as you would any C function inside source code. A
- global variable is also set which is used by the compiler to configure the
- correct memory model.
-
-
- Book Contents
-
-
- Chapter 1 provides a short introduction to using the library. Chapters 2-9
- document the library files included on the companion disk. Chapter 10 includes
- a discussion on writing your own assembly subroutines to link with C programs.
- This is followed by indices of general topics, the routines, and their object
- files.
- The routines or functions are documented pretty much as you would expect, each
- described in terms of its purpose, parameters, globals required, kinds of
- errors checked, and relevant peculiarities. Examples of calls are also amply
- provided.
- The book also presents background information on groups of routines. For
- example, functions that implement expanded memory are preceded by a
- description of the LIM specification, not in great detail, but enough for the
- programmer to understand what the routines do and why.
- Interestingly, video routines which can be implemented in either ROM BIOS or
- memory-mapped form are presented both ways, with the ROM BIOS routines
- suffixed with an underscore and lower case b. Depending upon the application,
- the programmer can choose between speed and portability.
-
-
- Overview of Functions
-
-
- The equipment configuration routines access the usual details of installed
- hardware and are only notable in that they eliminate the bit manipulations
- required when using the compiler library to extract the same information.
- The memory management routines test for extended and expanded memory, and
- allow programming of expanded memory applications. Pages can be allocated,
- switched and deallocated, and data exchanged between expanded memory and RAM
- quickly and easily. This can be a real plus to a C programmer restricted by
- the one megabyte limitation of the MS-DOS environment.
- C Express routines, like hex_to_char, hex_to_int, binary_to_char, and
- binary_to_int, interpret such strings as numbers, producing values which can
- then be entered as numeric data in C programs. Combined with existing stdlib.h
- utility functions, like itoa, they can also be used to convert binary to hex,
- or vice versa, a nice library facility.
- There are string manipulation routines to add or delete characters to or from
- strings, change string characters, and perform various substring operations.
- There are also keyboard routines that provide for fast operating system calls
- to check, read, clear, wait on and change characters, and scan codes in the
- keyboard buffer.
- filter_in, for example, captures the next keystroke in the buffer then looks
- for the same character in a predefined table. If found, the keystroke is
- accepted; otherwise, it's rejected. A related routine, filter_out, does the
- opposite, rejecting a character found in the table.
- key_pause allows a key to act like an ON-OFF switch. Hold it down and, let's
- say, a help screen appears. Release it, and the help screen disappears. This
- can replace long and complicated if and switch statements with cleaner, faster
- code.
- Mouse routines, based on Microsoft's driver, position the mouse cursor, turn
- it on and off, capture a button press, report mouse positioning and control
- mouse motion to cursor movement. With pixel_ratio, you can set the exact
- movement ratio between the mouse and its cursor motion. With
- define_graphics_cursor, you can set the precise pixel or "hot spot" inside the
- pattern of pixels th